Google OR-Tools v9.15
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-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#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/log/log.h"
39#include "absl/status/status.h"
40#include "absl/status/statusor.h"
41#include "absl/strings/ascii.h"
42#include "absl/strings/match.h"
43#include "absl/strings/str_cat.h"
44#include "absl/strings/str_format.h"
45#include "absl/strings/str_replace.h"
46#include "absl/strings/string_view.h"
47#include "absl/synchronization/mutex.h"
48#include "absl/synchronization/notification.h"
49#include "absl/time/clock.h"
50#include "absl/time/time.h"
51#include "google/protobuf/text_format.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
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
215void MPObjective::SetOffset(double value) {
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
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
377namespace {
378MPSolverInterface* BuildSolverInterface(MPSolver* const solver) {
379 DCHECK(solver != nullptr);
380 MPSolverInterface* interface =
382 QCHECK(interface != nullptr)
383 << "Unsupported problem type: '" << solver->ProblemType()
384 << "'. Did you forget to link the library for this solver?";
385 return interface;
386}
387} // namespace
388
389namespace {
390int NumDigits(int n) {
391// Number of digits needed to write a non-negative integer in base 10.
392// Note(user): max(1, log(0) + 1) == max(1, -inf) == 1.
393#if defined(_MSC_VER)
394 return static_cast<int>(std::max(1.0L, log(1.0L * n) / log(10.0L) + 1.0));
395#else
396 return static_cast<int>(std::max(1.0, log10(static_cast<double>(n)) + 1.0));
397#endif
398}
399} // namespace
400
401MPSolver::MPSolver(const std::string& name,
402 OptimizationProblemType problem_type)
403 : name_(name),
404 problem_type_(problem_type),
405 construction_time_(absl::Now()) {
406 interface_.reset(BuildSolverInterface(this));
407 if (absl::GetFlag(FLAGS_linear_solver_enable_verbose_output)) {
408 EnableOutput();
409 }
410 objective_.reset(new MPObjective(interface_.get()));
411}
412
414
415// static
420
421// TODO(user): post c++ 14, instead use
422// std::pair<MPSolver::OptimizationProblemType, const absl::string_view>
423// once pair gets a constexpr constructor.
424namespace {
425struct NamedOptimizationProblemType {
427 absl::string_view name;
428};
429} // namespace
430
431#if defined(_MSC_VER)
432const
433#else
434constexpr
435#endif
456// static
457bool MPSolver::ParseSolverType(absl::string_view solver_id,
459 // Normalize the solver id.
460 const std::string id =
461 absl::StrReplaceAll(absl::AsciiStrToUpper(solver_id), {{"-", "_"}});
462
463 // Support the full enum name
464 MPModelRequest::SolverType solver_type;
465 if (MPModelRequest::SolverType_Parse(id, &solver_type)) {
466 *type = static_cast<MPSolver::OptimizationProblemType>(solver_type);
467 return true;
468 }
469
470 // Names are stored in lower case.
471 std::string lower_id = absl::AsciiStrToLower(id);
472
473 // Remove any "_mip" suffix, since they are optional.
474 if (absl::EndsWith(lower_id, "_mip")) {
475 lower_id = lower_id.substr(0, lower_id.size() - 4);
476 }
477
478 // Rewrite CP-SAT into SAT.
479 if (lower_id == "cp_sat") {
480 lower_id = "sat";
481 }
482
483 // Reverse lookup in the kOptimizationProblemTypeNames[] array.
484 for (auto& named_solver : kOptimizationProblemTypeNames) {
485 if (named_solver.name == lower_id) {
486 *type = named_solver.problem_type;
487 return true;
488 }
489 }
490
491 return false;
492}
493
494absl::string_view ToString(
495 MPSolver::OptimizationProblemType optimization_problem_type) {
496 for (const auto& named_solver : kOptimizationProblemTypeNames) {
497 if (named_solver.problem_type == optimization_problem_type) {
498 return named_solver.name;
499 }
500 }
501 LOG(FATAL) << "Unrecognized solver type: "
502 << static_cast<int>(optimization_problem_type);
503}
504
505bool AbslParseFlag(const absl::string_view text,
507 std::string* error) {
508 DCHECK(solver_type != nullptr);
509 DCHECK(error != nullptr);
510 const bool result = MPSolver::ParseSolverType(text, solver_type);
511 if (!result) {
512 *error = absl::StrCat("Solver type: ", text, " does not exist.");
513 }
514 return result;
515}
516
517/* static */
519 const std::string& solver_id) {
521 CHECK(MPSolver::ParseSolverType(solver_id, &problem_type)) << solver_id;
522 return problem_type;
523}
524
525/* static */
526MPSolver* MPSolver::CreateSolver(const std::string& solver_id) {
528 if (!MPSolver::ParseSolverType(solver_id, &problem_type)) {
529 LOG(WARNING) << "Unrecognized solver type: " << solver_id;
530 return nullptr;
531 }
532 if (!MPSolver::SupportsProblemType(problem_type)) {
533 LOG(WARNING) << "Support for " << solver_id
534 << " not linked in, or the license was not found.";
535 return nullptr;
536 }
537 MPSolver* solver = new MPSolver("", problem_type);
538 return solver;
539}
540
541MPVariable* MPSolver::LookupVariableOrNull(const std::string& var_name) const {
542 if (!variable_name_to_index_) GenerateVariableNameIndex();
543
544 absl::flat_hash_map<std::string, int>::const_iterator it =
545 variable_name_to_index_->find(var_name);
546 if (it == variable_name_to_index_->end()) return nullptr;
547 return variables_[it->second];
548}
549
551 const std::string& constraint_name) const {
552 if (!constraint_name_to_index_) GenerateConstraintNameIndex();
553
554 const auto it = constraint_name_to_index_->find(constraint_name);
555 if (it == constraint_name_to_index_->end()) return nullptr;
556 return constraints_[it->second];
557}
558
559// ----- Methods using protocol buffers -----
560
562 const MPModelProto& input_model, std::string* error_message,
563 bool clear_names) {
564 Clear();
565
566 // The variable and constraint names are dropped, because we allow
567 // duplicate names in the proto (they're not considered as 'ids'),
568 // unlike the MPSolver C++ API which crashes if there are duplicate names.
569 // Clearing the names makes the MPSolver generate unique names.
570 return LoadModelFromProtoInternal(
571 input_model,
572 /*name_policy=*/
573 clear_names ? DEFAULT_CLEAR_NAMES
574 : INVALID_MODEL_ON_DUPLICATE_NONEMPTY_NAMES,
575 /*check_model_validity=*/true, error_message);
576}
577
579 const MPModelProto& input_model, std::string* error_message) {
580 Clear();
581
582 // Force variable and constraint name indexing (which CHECKs name uniqueness).
583 GenerateVariableNameIndex();
584 GenerateConstraintNameIndex();
585
586 return LoadModelFromProtoInternal(
587 input_model, /*name_policy=*/DIE_ON_DUPLICATE_NONEMPTY_NAMES,
588 /*check_model_validity=*/true, error_message);
589}
590
591namespace {
592// Iterates over all the variable names. See usage.
593class MPVariableNamesIterator {
594 public:
595 explicit MPVariableNamesIterator(const MPModelProto& model) : model_(model) {}
596 int index() const { return index_; }
597 absl::string_view name() const { return model_.variable(index_).name(); }
598 static std::string DescribeIndex(int index) {
599 return absl::StrFormat("variable[%d]", index);
600 }
601 void Advance() { ++index_; }
602 bool AtEnd() const { return index_ == model_.variable_size(); }
603
604 private:
605 const MPModelProto& model_;
606 int index_ = 0;
607};
608
609// Iterates over all the constraint and general_constraint names. See usage.
610class MPConstraintNamesIterator {
611 public:
612 explicit MPConstraintNamesIterator(const MPModelProto& model)
613 : model_(model),
614 // To iterate both on the constraint[] and the general_constraint[]
615 // field, we use the bit trick that i ≥ 0 corresponds to constraint[i],
616 // and i < 0 corresponds to general_constraint[~i = -i-1].
617 index_(model_.constraint().empty() ? ~0 : 0) {}
618 int index() const { return index_; }
619 absl::string_view name() const {
620 return index_ >= 0 ? model_.constraint(index_).name()
621 // As of 2023-04, the only names of MPGeneralConstraints
622 // that are actually ingested are the
623 // MPIndicatorConstraint.constraint.name.
624 : model_.general_constraint(~index_)
625 .indicator_constraint()
626 .constraint()
627 .name();
628 }
629 static std::string DescribeIndex(int index) {
630 return index >= 0
631 ? absl::StrFormat("constraint[%d]", index)
632 : absl::StrFormat(
633 "general_constraint[%d].indicator_constraint.constraint",
634 ~index);
635 }
636 void Advance() {
637 if (index_ >= 0) {
638 if (++index_ == model_.constraint_size()) index_ = ~0;
639 } else {
640 --index_;
641 }
642 }
643 bool AtEnd() const { return ~index_ == model_.general_constraint_size(); }
644
645 private:
646 const MPModelProto& model_;
647 int index_ = 0;
648};
649
650// Looks at the `name` field of all the given MPModelProto fields, and if there
651// is a duplicate name, returns a descriptive error message.
652// If no duplicate is found, returns the empty string.
653template <class NameIterator>
654std::string FindDuplicateNamesError(NameIterator name_iterator) {
655 absl::flat_hash_map<absl::string_view, int> name_to_index;
656 for (; !name_iterator.AtEnd(); name_iterator.Advance()) {
657 if (name_iterator.name().empty()) continue;
658 const int index =
659 name_to_index.insert({name_iterator.name(), name_iterator.index()})
660 .first->second;
661 if (index != name_iterator.index()) {
662 return absl::StrFormat(
663 "Duplicate name '%s' in %s.name() and %s.name()",
664 name_iterator.name(), NameIterator::DescribeIndex(index),
665 NameIterator::DescribeIndex(name_iterator.index()));
666 }
667 }
668 return ""; // No duplicate found.
669}
670} // namespace
671
672MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal(
673 const MPModelProto& input_model, ModelProtoNamesPolicy name_policy,
674 bool check_model_validity, std::string* error_message) {
675 CHECK(error_message != nullptr);
676 std::string error;
677 if (check_model_validity) {
678 error = FindErrorInMPModelProto(input_model);
679 }
680 // We preemptively check for duplicate names even for the
681 // DIE_ON_DUPLICATE_NONEMPTY_NAMES policy, because it yields more informative
682 // error messages than if we wait for InsertIfNotPresent() to crash.
683 if (error.empty() && name_policy != DEFAULT_CLEAR_NAMES) {
684 error = FindDuplicateNamesError(MPVariableNamesIterator(input_model));
685 if (error.empty()) {
686 error = FindDuplicateNamesError(MPConstraintNamesIterator(input_model));
687 }
688 if (!error.empty() && name_policy == DIE_ON_DUPLICATE_NONEMPTY_NAMES) {
689 LOG(FATAL) << error;
690 }
691 }
692 const bool clear_names = name_policy == DEFAULT_CLEAR_NAMES;
693
694 if (!error.empty()) {
695 *error_message = error;
696 LOG_IF(INFO, OutputIsEnabled())
697 << "Invalid model given to LoadModelFromProto(): " << error;
698 if (absl::GetFlag(FLAGS_mpsolver_bypass_model_validation)) {
699 LOG_IF(INFO, OutputIsEnabled())
700 << "Ignoring the model error(s) because of"
701 << " --mpsolver_bypass_model_validation.";
702 } else {
703 return absl::StrContains(error, "Infeasible") ? MPSOLVER_INFEASIBLE
705 }
706 }
707
708 if (input_model.has_quadratic_objective()) {
709 *error_message =
710 "Optimizing a quadratic objective is only supported through direct "
711 "proto solves. Please use MPSolver::SolveWithProto, or the solver's "
712 "direct proto solve function.";
714 }
715
716 MPObjective* const objective = MutableObjective();
717 // Passing empty names makes the MPSolver generate unique names.
718 const std::string empty;
719 for (int i = 0; i < input_model.variable_size(); ++i) {
720 const MPVariableProto& var_proto = input_model.variable(i);
721 MPVariable* variable =
722 MakeNumVar(var_proto.lower_bound(), var_proto.upper_bound(),
723 clear_names ? empty : var_proto.name());
724 variable->SetInteger(var_proto.is_integer());
725 if (var_proto.branching_priority() != 0) {
726 variable->SetBranchingPriority(var_proto.branching_priority());
727 }
728 objective->SetCoefficient(variable, var_proto.objective_coefficient());
729 }
730
731 for (const MPConstraintProto& ct_proto : input_model.constraint()) {
732 if (ct_proto.lower_bound() == -infinity() &&
733 ct_proto.upper_bound() == infinity()) {
734 continue;
735 }
736
737 MPConstraint* const ct =
738 MakeRowConstraint(ct_proto.lower_bound(), ct_proto.upper_bound(),
739 clear_names ? empty : ct_proto.name());
740 ct->set_is_lazy(ct_proto.is_lazy());
741 for (int j = 0; j < ct_proto.var_index_size(); ++j) {
742 ct->SetCoefficient(variables_[ct_proto.var_index(j)],
743 ct_proto.coefficient(j));
744 }
745 }
746
747 for (const MPGeneralConstraintProto& general_constraint :
748 input_model.general_constraint()) {
749 switch (general_constraint.general_constraint_case()) {
751 const auto& proto =
752 general_constraint.indicator_constraint().constraint();
753 if (proto.lower_bound() == -infinity() &&
754 proto.upper_bound() == infinity()) {
755 continue;
756 }
757
758 const int constraint_index = NumConstraints();
759 MPConstraint* const constraint = new MPConstraint(
760 constraint_index, proto.lower_bound(), proto.upper_bound(),
761 clear_names ? "" : proto.name(), interface_.get());
762 if (constraint_name_to_index_) {
763 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
764 constraint_index);
765 }
766 constraints_.push_back(constraint);
767 constraint_is_extracted_.push_back(false);
768
769 constraint->set_is_lazy(proto.is_lazy());
770 for (int j = 0; j < proto.var_index_size(); ++j) {
771 constraint->SetCoefficient(variables_[proto.var_index(j)],
772 proto.coefficient(j));
773 }
774
775 MPVariable* const variable =
776 variables_[general_constraint.indicator_constraint().var_index()];
777 constraint->indicator_variable_ = variable;
778 constraint->indicator_value_ =
779 general_constraint.indicator_constraint().var_value();
780
781 if (!interface_->AddIndicatorConstraint(constraint)) {
782 *error_message = "Solver doesn't support indicator constraints";
784 }
785 break;
786 }
787 default:
788 *error_message = absl::StrFormat(
789 "Optimizing general constraints of type %i is only supported "
790 "through direct proto solves. Please use MPSolver::SolveWithProto, "
791 "or the solver's direct proto solve function.",
792 general_constraint.general_constraint_case());
794 }
795 }
796
797 objective->SetOptimizationDirection(input_model.maximize());
798 if (input_model.has_objective_offset()) {
799 objective->SetOffset(input_model.objective_offset());
800 }
801
802 // Stores any hints about where to start the solve.
803 solution_hint_.clear();
804 for (int i = 0; i < input_model.solution_hint().var_index_size(); ++i) {
805 solution_hint_.push_back(
806 std::make_pair(variables_[input_model.solution_hint().var_index(i)],
807 input_model.solution_hint().var_value(i)));
808 }
810}
811
812namespace {
813MPSolverResponseStatus ResultStatusToMPSolverResponseStatus(
814 MPSolver::ResultStatus status) {
815 switch (status) {
817 return MPSOLVER_OPTIMAL;
819 return MPSOLVER_FEASIBLE;
821 return MPSOLVER_INFEASIBLE;
823 return MPSOLVER_UNBOUNDED;
825 return MPSOLVER_ABNORMAL;
829 return MPSOLVER_NOT_SOLVED;
830 }
832}
833} // namespace
834
836 CHECK(response != nullptr);
837 response->Clear();
838 response->set_status(
839 ResultStatusToMPSolverResponseStatus(interface_->result_status_));
841 1000.0);
842 if (interface_->result_status_ == MPSolver::OPTIMAL ||
843 interface_->result_status_ == MPSolver::FEASIBLE) {
844 response->set_objective_value(Objective().Value());
845 for (MPVariable* variable : variables_) {
846 response->add_variable_value(variable->solution_value());
847 }
848
849 if (interface_->IsMIP()) {
850 response->set_best_objective_bound(interface_->best_objective_bound());
851 } else {
852 // Dual values have no meaning in MIP.
853 for (MPConstraint* constraint : constraints_) {
854 response->add_dual_value(constraint->dual_value());
855 }
856 // Reduced cost have no meaning in MIP.
857 for (MPVariable* variable : variables_) {
858 response->add_reduced_cost(variable->reduced_cost());
859 }
860 }
861 }
862}
863
864namespace {
865bool InCategory(int status, int category) {
866 if (category == MPSOLVER_OPTIMAL) return status == MPSOLVER_OPTIMAL;
867 while (status > category) status >>= 4;
868 return status == category;
869}
870
871void AppendStatusStr(absl::string_view msg, MPSolutionResponse* response) {
872 response->set_status_str(
873 absl::StrCat(response->status_str(),
874 (response->status_str().empty() ? "" : "\n"), msg));
875}
876
877} // namespace
878
879// static
880void MPSolver::SolveWithProto(const MPModelRequest& model_request,
881 MPSolutionResponse* response,
882 std::atomic<bool>* interrupt) {
883 return SolveLazyMutableRequest(model_request, response, interrupt);
884}
885
886// static
888 MPSolutionResponse* response,
889 std::atomic<bool>* interrupt) {
890 CHECK(response != nullptr);
891
892 if (interrupt != nullptr &&
893 !SolverTypeSupportsInterruption(request->solver_type())) {
895 response->set_status_str(
896 "Called MPSolver::SolveWithProto with an underlying solver that "
897 "doesn't support interruption.");
898 return;
899 }
900
901 MPSolver solver(
902 request->model().name(),
903 static_cast<MPSolver::OptimizationProblemType>(request->solver_type()));
904 if (request->enable_internal_solver_output()) {
905 solver.EnableOutput();
906 std::cout << "MPModelRequest info:\n"
907 << GetMPModelRequestLoggingInfo(*request) << std::endl;
908 }
909
910 // If the solver supports it, we can std::move() the request since we will
911 // return right after this in all cases.
912 if (solver.interface_->SupportsDirectlySolveProto(interrupt)) {
913 *response =
914 solver.interface_->DirectlySolveProto(std::move(request), interrupt);
915 return;
916 }
917
918 // Validate and extract model delta. Also deal with trivial problems.
919 const std::optional<LazyMutableCopy<MPModelProto>> optional_model =
920 GetMPModelOrPopulateResponse(request, response);
921 if (!optional_model) return;
922
923 std::string error_message;
924 response->set_status(solver.LoadModelFromProtoInternal(
925 **optional_model, /*name_policy=*/DEFAULT_CLEAR_NAMES,
926 /*check_model_validity=*/false, &error_message));
927 // Even though we don't re-check model validity here, there can be some
928 // problems found by LoadModelFromProto, eg. unsupported features.
929 if (response->status() != MPSOLVER_MODEL_IS_VALID) {
930 response->set_status_str(error_message);
931 LOG_IF(WARNING, request->enable_internal_solver_output())
932 << "LoadModelFromProtoInternal() failed even though the model was "
933 << "valid! Status: "
935 << response->status() << "); Error: " << error_message;
936 return;
937 }
938 if (request->has_solver_time_limit_seconds()) {
939 solver.SetTimeLimit(absl::Seconds(request->solver_time_limit_seconds()));
940 }
941 std::string warning_message;
942 if (request->has_solver_specific_parameters()) {
944 request->solver_specific_parameters())) {
945 if (request->ignore_solver_specific_parameters_failure()) {
946 // We'll add a warning message in status_str after the solve.
947 warning_message =
948 "Warning: the solver specific parameters were not successfully "
949 "applied";
950 } else {
952 return;
953 }
954 }
955 }
956
957 if (interrupt == nullptr) {
958 // If we don't need interruption support, we can save some overhead by
959 // running the solve in the current thread.
960 solver.Solve();
961 solver.FillSolutionResponseProto(response);
962 } else {
963 const absl::Time start_time = absl::Now();
964 absl::Time interrupt_time;
965 bool interrupted_by_user = false;
966 {
967 absl::Notification solve_finished;
968 auto polling_func = [&interrupt, &solve_finished, &solver,
969 &interrupted_by_user, &interrupt_time, &request]() {
970 constexpr absl::Duration kPollDelay = absl::Microseconds(100);
971 constexpr absl::Duration kMaxInterruptionDelay = absl::Seconds(10);
972
973 while (!interrupt->load()) {
974 if (solve_finished.HasBeenNotified()) return;
975 absl::SleepFor(kPollDelay);
976 }
977
978 // If we get here, we received an interruption notification before the
979 // solve finished "naturally".
980 solver.InterruptSolve();
981 interrupt_time = absl::Now();
982 interrupted_by_user = true;
983
984 // SUBTLE: our call to InterruptSolve() can be ignored by the
985 // underlying solver for several reasons:
986 // 1) The solver thread doesn't poll its 'interrupted' bit often
987 // enough and takes too long to realize that it should return, or
988 // its mere return + FillSolutionResponse() takes too long.
989 // 2) The user interrupted the solve so early that Solve() hadn't
990 // really started yet when we called InterruptSolve().
991 // In case 1), we should just wait a little longer. In case 2), we
992 // should call InterruptSolve() again, maybe several times. To both
993 // accommodate cases where the solver takes really a long time to
994 // react to the interruption, while returning as quickly as possible,
995 // we poll the solve_finished notification with increasing durations
996 // and call InterruptSolve again, each time.
997 for (absl::Duration poll_delay = kPollDelay;
998 absl::Now() <= interrupt_time + kMaxInterruptionDelay;
999 poll_delay *= 2) {
1000 if (solve_finished.WaitForNotificationWithTimeout(poll_delay)) {
1001 return;
1002 } else {
1003 solver.InterruptSolve();
1004 }
1005 }
1006
1007 LOG(DFATAL)
1008 << "MPSolver::InterruptSolve() seems to be ignored by the "
1009 "underlying solver, despite repeated calls over at least "
1010 << absl::FormatDuration(kMaxInterruptionDelay)
1011 << ". Solver type used: "
1012 << MPModelRequest_SolverType_Name(request->solver_type());
1013
1014 // Note that in opt builds, the polling thread terminates here with an
1015 // error message, but we let Solve() finish, ignoring the user
1016 // interruption request.
1017 };
1018
1019 // The choice to do polling rather than solving in the second thread is
1020 // not arbitrary, as we want to maintain any custom thread options set by
1021 // the user. They shouldn't matter for polling, but for solving we might
1022 // e.g. use a larger stack.
1023 ThreadPool thread_pool(/*num_threads=*/1);
1024 thread_pool.Schedule(polling_func);
1025
1026 // Make sure the interruption notification didn't arrive while waiting to
1027 // be scheduled.
1028 if (!interrupt->load()) {
1029 solver.Solve();
1030 solver.FillSolutionResponseProto(response);
1031 } else { // *interrupt == true
1033 response->set_status_str(
1034 "Solve not started, because the user set the atomic<bool> in "
1035 "MPSolver::SolveWithProto() to true before solving could "
1036 "start.");
1037 }
1038 solve_finished.Notify();
1039
1040 // We block until the thread finishes when thread_pool goes out of scope.
1041 }
1042
1043 if (interrupted_by_user) {
1044 // Despite the interruption, the solver might still have found a useful
1045 // result. If so, don't overwrite the status.
1046 if (InCategory(response->status(), MPSOLVER_NOT_SOLVED)) {
1048 }
1049 AppendStatusStr(
1050 absl::StrFormat(
1051 "User interrupted MPSolver::SolveWithProto() by setting the "
1052 "atomic<bool> to true at %s (%s after solving started.)",
1053 absl::FormatTime(interrupt_time),
1054 absl::FormatDuration(interrupt_time - start_time)),
1055 response);
1056 }
1057 }
1058
1059 if (!warning_message.empty()) {
1060 AppendStatusStr(warning_message, response);
1061 }
1062}
1063
1065 DCHECK(output_model != nullptr);
1066 output_model->Clear();
1067 // Name
1068 output_model->set_name(Name());
1069 // Variables
1070 for (const MPVariable* var : variables_) {
1071 MPVariableProto* const variable_proto = output_model->add_variable();
1072 // TODO(user): Add option to avoid filling the var name to avoid overly
1073 // large protocol buffers.
1074 variable_proto->set_name(var->name());
1075 variable_proto->set_lower_bound(var->lb());
1076 variable_proto->set_upper_bound(var->ub());
1077 variable_proto->set_is_integer(var->integer());
1078 if (objective_->GetCoefficient(var) != 0.0) {
1079 variable_proto->set_objective_coefficient(
1080 objective_->GetCoefficient(var));
1081 }
1082 if (var->branching_priority() != 0) {
1083 variable_proto->set_branching_priority(var->branching_priority());
1084 }
1085 }
1086
1087 // Map the variables to their indices. This is needed to output the
1088 // variables in the order they were created, which in turn is needed to have
1089 // repeatable results with ExportModelAsLpFormat and ExportModelAsMpsFormat.
1090 // This step is needed as long as the variable indices are given by the
1091 // underlying solver at the time of model extraction.
1092 // TODO(user): remove this step.
1093 absl::flat_hash_map<const MPVariable*, int> var_to_index;
1094 for (int j = 0; j < static_cast<int>(variables_.size()); ++j) {
1095 var_to_index[variables_[j]] = j;
1096 }
1097
1098 // Constraints
1099 for (MPConstraint* const constraint : constraints_) {
1100 MPConstraintProto* constraint_proto;
1101 if (constraint->indicator_variable() != nullptr) {
1102 MPGeneralConstraintProto* const general_constraint_proto =
1103 output_model->add_general_constraint();
1104 general_constraint_proto->set_name(constraint->name());
1105 MPIndicatorConstraint* const indicator_constraint_proto =
1106 general_constraint_proto->mutable_indicator_constraint();
1107 indicator_constraint_proto->set_var_index(
1108 constraint->indicator_variable()->index());
1109 indicator_constraint_proto->set_var_value(constraint->indicator_value());
1110 constraint_proto = indicator_constraint_proto->mutable_constraint();
1111 } else {
1112 constraint_proto = output_model->add_constraint();
1113 }
1114 constraint_proto->set_name(constraint->name());
1115 constraint_proto->set_lower_bound(constraint->lb());
1116 constraint_proto->set_upper_bound(constraint->ub());
1117 constraint_proto->set_is_lazy(constraint->is_lazy());
1118 // Vector linear_term will contain pairs (variable index, coeff), that will
1119 // be sorted by variable index.
1120 std::vector<std::pair<int, double>> linear_term;
1121 for (const auto& entry : constraint->coefficients_) {
1122 const MPVariable* const var = entry.first;
1123 const int var_index = gtl::FindWithDefault(var_to_index, var, -1);
1124 DCHECK_NE(-1, var_index);
1125 const double coeff = entry.second;
1126 linear_term.push_back(std::pair<int, double>(var_index, coeff));
1127 }
1128 // The cost of sort is expected to be low as constraints usually have very
1129 // few terms.
1130 std::sort(linear_term.begin(), linear_term.end());
1131 // Now use linear term.
1132 for (const std::pair<int, double>& var_and_coeff : linear_term) {
1133 constraint_proto->add_var_index(var_and_coeff.first);
1134 constraint_proto->add_coefficient(var_and_coeff.second);
1135 }
1136 }
1137
1138 output_model->set_maximize(Objective().maximization());
1139 output_model->set_objective_offset(Objective().offset());
1140
1141 if (!solution_hint_.empty()) {
1142 PartialVariableAssignment* const hint =
1143 output_model->mutable_solution_hint();
1144 for (const auto& var_value_pair : solution_hint_) {
1145 hint->add_var_index(var_value_pair.first->index());
1146 hint->add_var_value(var_value_pair.second);
1147 }
1148 }
1149}
1150
1152 double tolerance) {
1153 interface_->result_status_ = static_cast<ResultStatus>(response.status());
1154 if (response.status() != MPSOLVER_OPTIMAL &&
1155 response.status() != MPSOLVER_FEASIBLE) {
1156 return absl::InvalidArgumentError(absl::StrCat(
1157 "Cannot load a solution unless its status is OPTIMAL or FEASIBLE"
1158 " (status was: ",
1160 }
1161 // Before touching the variables, verify that the solution looks legit:
1162 // each variable of the MPSolver must have its value listed exactly once, and
1163 // each listed solution should correspond to a known variable.
1164 if (static_cast<size_t>(response.variable_value_size()) !=
1165 variables_.size()) {
1166 return absl::InvalidArgumentError(absl::StrCat(
1167 "Trying to load a solution whose number of variables (",
1168 response.variable_value_size(),
1169 ") does not correspond to the Solver's (", variables_.size(), ")"));
1170 }
1171 interface_->ExtractModel();
1172
1173 if (tolerance != infinity()) {
1174 // Look further: verify that the variable values are within the bounds.
1175 double largest_error = 0;
1176 int num_vars_out_of_bounds = 0;
1177 int last_offending_var = -1;
1178 for (int i = 0; i < response.variable_value_size(); ++i) {
1179 const double var_value = response.variable_value(i);
1180 MPVariable* var = variables_[i];
1181 // TODO(user): Use parameter when they become available in this class.
1182 const double lb_error = var->lb() - var_value;
1183 const double ub_error = var_value - var->ub();
1184 if (lb_error > tolerance || ub_error > tolerance) {
1185 ++num_vars_out_of_bounds;
1186 largest_error = std::max(largest_error, std::max(lb_error, ub_error));
1187 last_offending_var = i;
1188 }
1189 }
1190 if (num_vars_out_of_bounds > 0) {
1191 return absl::InvalidArgumentError(absl::StrCat(
1192 "Loaded a solution whose variables matched the solver's, but ",
1193 num_vars_out_of_bounds, " of ", variables_.size(),
1194 " variables were out of their bounds, by more than the primal"
1195 " tolerance which is: ",
1196 tolerance, ". Max error: ", largest_error, ", last offender var is #",
1197 last_offending_var, ": '", variables_[last_offending_var]->name(),
1198 "'"));
1199 }
1200 }
1201 for (int i = 0; i < response.variable_value_size(); ++i) {
1202 variables_[i]->set_solution_value(response.variable_value(i));
1203 }
1204 if (response.dual_value_size() > 0) {
1205 if (static_cast<size_t>(response.dual_value_size()) !=
1206 constraints_.size()) {
1207 return absl::InvalidArgumentError(absl::StrCat(
1208 "Trying to load a dual solution whose number of entries (",
1209 response.dual_value_size(), ") does not correspond to the Solver's (",
1210 constraints_.size(), ")"));
1211 }
1212 for (int i = 0; i < response.dual_value_size(); ++i) {
1213 constraints_[i]->set_dual_value(response.dual_value(i));
1214 }
1215 }
1216 if (response.reduced_cost_size() > 0) {
1217 if (static_cast<size_t>(response.reduced_cost_size()) !=
1218 variables_.size()) {
1219 return absl::InvalidArgumentError(absl::StrCat(
1220 "Trying to load a reduced cost solution whose number of entries (",
1221 response.reduced_cost_size(),
1222 ") does not correspond to the Solver's (", variables_.size(), ")"));
1223 }
1224 for (int i = 0; i < response.reduced_cost_size(); ++i) {
1225 variables_[i]->set_reduced_cost(response.reduced_cost(i));
1226 }
1227 }
1228 // Set the objective value, if is known.
1229 // NOTE(user): We do not verify the objective, even though we could!
1230 if (response.has_objective_value()) {
1231 interface_->objective_value_ = response.objective_value();
1232 }
1233 if (response.has_best_objective_bound()) {
1234 interface_->best_objective_bound_ = response.best_objective_bound();
1235 }
1236 // Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the
1237 // solution normally.
1238 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1239 return absl::OkStatus();
1240}
1241
1243 {
1244 absl::MutexLock lock(global_count_mutex_);
1245 global_num_variables_ += variables_.size();
1246 global_num_constraints_ += constraints_.size();
1247 }
1249 gtl::STLDeleteElements(&variables_);
1250 gtl::STLDeleteElements(&constraints_);
1251 if (variable_name_to_index_) {
1252 variable_name_to_index_->clear();
1253 }
1254 variable_is_extracted_.clear();
1255 if (constraint_name_to_index_) {
1256 constraint_name_to_index_->clear();
1257 }
1258 constraint_is_extracted_.clear();
1259 interface_->Reset();
1260 solution_hint_.clear();
1261}
1262
1263void MPSolver::Reset() { interface_->Reset(); }
1264
1265bool MPSolver::InterruptSolve() { return interface_->InterruptSolve(); }
1266
1268 const std::vector<BasisStatus>& variable_statuses,
1269 const std::vector<BasisStatus>& constraint_statuses) {
1270 interface_->SetStartingLpBasis(variable_statuses, constraint_statuses);
1271}
1272
1273double MPSolver::solver_infinity() { return interface_->infinity(); }
1274
1275MPVariable* MPSolver::MakeVar(double lb, double ub, bool integer,
1276 const std::string& name) {
1277 const int var_index = NumVariables();
1278 MPVariable* v =
1279 new MPVariable(var_index, lb, ub, integer, name, interface_.get());
1280 if (variable_name_to_index_) {
1281 gtl::InsertOrDie(&*variable_name_to_index_, v->name(), var_index);
1282 }
1283 variables_.push_back(v);
1284 variable_is_extracted_.push_back(false);
1285 interface_->AddVariable(v);
1286 return v;
1287}
1288
1289MPVariable* MPSolver::MakeNumVar(double lb, double ub,
1290 const std::string& name) {
1291 return MakeVar(lb, ub, false, name);
1292}
1293
1294MPVariable* MPSolver::MakeIntVar(double lb, double ub,
1295 const std::string& name) {
1296 return MakeVar(lb, ub, true, name);
1297}
1298
1299MPVariable* MPSolver::MakeBoolVar(const std::string& name) {
1300 return MakeVar(0.0, 1.0, true, name);
1301}
1302
1303void MPSolver::MakeVarArray(int nb, double lb, double ub, bool integer,
1304 const std::string& name,
1305 std::vector<MPVariable*>* vars) {
1306 DCHECK_GE(nb, 0);
1307 if (nb <= 0) return;
1308 const int num_digits = NumDigits(nb);
1309 for (int i = 0; i < nb; ++i) {
1310 if (name.empty()) {
1311 vars->push_back(MakeVar(lb, ub, integer, name));
1312 } else {
1313 std::string vname =
1314 absl::StrFormat("%s%0*d", name.c_str(), num_digits, i);
1315 vars->push_back(MakeVar(lb, ub, integer, vname));
1316 }
1317 }
1318}
1319
1320void MPSolver::MakeNumVarArray(int nb, double lb, double ub,
1321 const std::string& name,
1322 std::vector<MPVariable*>* vars) {
1323 MakeVarArray(nb, lb, ub, false, name, vars);
1324}
1325
1326void MPSolver::MakeIntVarArray(int nb, double lb, double ub,
1327 const std::string& name,
1328 std::vector<MPVariable*>* vars) {
1329 MakeVarArray(nb, lb, ub, true, name, vars);
1330}
1331
1332void MPSolver::MakeBoolVarArray(int nb, const std::string& name,
1333 std::vector<MPVariable*>* vars) {
1334 MakeVarArray(nb, 0.0, 1.0, true, name, vars);
1335}
1336
1338 return MakeRowConstraint(lb, ub, "");
1339}
1340
1344
1346 const std::string& name) {
1347 const int constraint_index = NumConstraints();
1348 MPConstraint* const constraint =
1349 new MPConstraint(constraint_index, lb, ub, name, interface_.get());
1350 if (constraint_name_to_index_) {
1351 gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
1352 constraint_index);
1353 }
1354 constraints_.push_back(constraint);
1355 constraint_is_extracted_.push_back(false);
1356 interface_->AddRowConstraint(constraint);
1357 return constraint;
1358}
1359
1361 return MakeRowConstraint(-infinity(), infinity(), name);
1362}
1363
1365 return MakeRowConstraint(range, "");
1366}
1367
1369 const std::string& name) {
1370 CheckLinearExpr(*this, range.linear_expr());
1372 MakeRowConstraint(range.lower_bound(), range.upper_bound(), name);
1373 for (const auto& kv : range.linear_expr().terms()) {
1374 constraint->SetCoefficient(kv.first, kv.second);
1375 }
1376 return constraint;
1377}
1378
1379int MPSolver::ComputeMaxConstraintSize(int min_constraint_index,
1380 int max_constraint_index) const {
1381 int max_constraint_size = 0;
1382 DCHECK_GE(min_constraint_index, 0);
1383 DCHECK_LE(max_constraint_index, constraints_.size());
1384 for (int i = min_constraint_index; i < max_constraint_index; ++i) {
1385 MPConstraint* const ct = constraints_[i];
1386 if (static_cast<int>(ct->coefficients_.size()) > max_constraint_size) {
1387 max_constraint_size = ct->coefficients_.size();
1388 }
1389 }
1390 return max_constraint_size;
1391}
1392
1393bool MPSolver::HasInfeasibleConstraints() const {
1394 bool hasInfeasibleConstraints = false;
1395 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1396 if (constraints_[i]->lb() > constraints_[i]->ub()) {
1397 LOG(WARNING) << "Constraint " << constraints_[i]->name() << " (" << i
1398 << ") has contradictory bounds:" << " lower bound = "
1399 << constraints_[i]->lb()
1400 << " upper bound = " << constraints_[i]->ub();
1401 hasInfeasibleConstraints = true;
1402 }
1403 }
1404 return hasInfeasibleConstraints;
1405}
1406
1407bool MPSolver::HasIntegerVariables() const {
1408 for (const MPVariable* const variable : variables_) {
1409 if (variable->integer()) return true;
1410 }
1411 return false;
1412}
1413
1415 MPSolverParameters default_param;
1416 return Solve(default_param);
1417}
1418
1420 // Special case for infeasible constraints so that all solvers have
1421 // the same behavior.
1422 // TODO(user): replace this by model extraction to proto + proto validation
1423 // (the proto has very low overhead compared to the wrapper, both in
1424 // performance and memory, so it's ok).
1425 if (HasInfeasibleConstraints()) {
1426 interface_->result_status_ = MPSolver::INFEASIBLE;
1427 return interface_->result_status_;
1428 }
1429
1430 MPSolver::ResultStatus status = interface_->Solve(param);
1431 if (absl::GetFlag(FLAGS_verify_solution)) {
1432 if (status != MPSolver::OPTIMAL && status != MPSolver::FEASIBLE) {
1433 VLOG(1) << "--verify_solution enabled, but the solver did not find a"
1434 << " solution: skipping the verification.";
1435 } else if (!VerifySolution(
1437 absl::GetFlag(FLAGS_log_verification_errors))) {
1438 status = MPSolver::ABNORMAL;
1439 interface_->result_status_ = status;
1440 }
1441 }
1442 DCHECK_EQ(interface_->result_status_, status);
1443 return status;
1444}
1445
1446void MPSolver::Write(const std::string& file_name) {
1447 interface_->Write(file_name);
1448}
1449
1450namespace {
1451std::string PrettyPrintVar(const MPVariable& var) {
1452 const std::string prefix = "Variable '" + var.name() + "': domain = ";
1453 if (var.lb() >= MPSolver::infinity() || var.ub() <= -MPSolver::infinity() ||
1454 var.lb() > var.ub()) {
1455 return prefix + "∅"; // Empty set.
1456 }
1457 // Special case: integer variable with at most two possible values
1458 // (and potentially none).
1459 if (var.integer() && var.ub() - var.lb() <= 1) {
1460 const int64_t lb = static_cast<int64_t>(ceil(var.lb()));
1461 const int64_t ub = static_cast<int64_t>(floor(var.ub()));
1462 if (lb > ub) {
1463 return prefix + "∅";
1464 } else if (lb == ub) {
1465 return absl::StrFormat("%s{ %d }", prefix.c_str(), lb);
1466 } else {
1467 return absl::StrFormat("%s{ %d, %d }", prefix.c_str(), lb, ub);
1468 }
1469 }
1470 // Special case: single (non-infinite) real value.
1471 if (var.lb() == var.ub()) {
1472 return absl::StrFormat("%s{ %f }", prefix.c_str(), var.lb());
1473 }
1474 return prefix + (var.integer() ? "Integer" : "Real") + " in " +
1475 (var.lb() <= -MPSolver::infinity()
1476 ? std::string("]-∞")
1477 : absl::StrFormat("[%f", var.lb())) +
1478 ", " +
1479 (var.ub() >= MPSolver::infinity() ? std::string("+∞[")
1480 : absl::StrFormat("%f]", var.ub()));
1481}
1482
1483std::string PrettyPrintConstraint(const MPConstraint& constraint) {
1484 std::string prefix = "Constraint '" + constraint.name() + "': ";
1485 if (constraint.lb() >= MPSolver::infinity() ||
1486 constraint.ub() <= -MPSolver::infinity() ||
1487 constraint.lb() > constraint.ub()) {
1488 return prefix + "ALWAYS FALSE";
1489 }
1490 if (constraint.lb() <= -MPSolver::infinity() &&
1491 constraint.ub() >= MPSolver::infinity()) {
1492 return prefix + "ALWAYS TRUE";
1493 }
1494 prefix += "<linear expr>";
1495 // Equality.
1496 if (constraint.lb() == constraint.ub()) {
1497 return absl::StrFormat("%s = %f", prefix.c_str(), constraint.lb());
1498 }
1499 // Inequalities.
1500 if (constraint.lb() <= -MPSolver::infinity()) {
1501 return absl::StrFormat("%s ≤ %f", prefix.c_str(), constraint.ub());
1502 }
1503 if (constraint.ub() >= MPSolver::infinity()) {
1504 return absl::StrFormat("%s ≥ %f", prefix.c_str(), constraint.lb());
1505 }
1506 return absl::StrFormat("%s ∈ [%f, %f]", prefix.c_str(), constraint.lb(),
1507 constraint.ub());
1508}
1509} // namespace
1510
1512 interface_->ExtractModel();
1513 for (MPVariable* const variable : variables_) {
1514 const double value = variable->solution_value();
1515 if (std::isnan(value)) {
1516 return absl::InvalidArgumentError(
1517 absl::StrCat("NaN value for ", PrettyPrintVar(*variable)));
1518 }
1519 if (value < variable->lb()) {
1520 variable->set_solution_value(variable->lb());
1521 } else if (value > variable->ub()) {
1522 variable->set_solution_value(variable->ub());
1523 }
1524 }
1525 interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED;
1526 return absl::OkStatus();
1527}
1528
1529std::vector<double> MPSolver::ComputeConstraintActivities() const {
1530 // TODO(user): test this failure case.
1531 if (!interface_->CheckSolutionIsSynchronizedAndExists()) return {};
1532 std::vector<double> activities(constraints_.size(), 0.0);
1533 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1534 const MPConstraint& constraint = *constraints_[i];
1536 for (const auto& entry : constraint.coefficients_) {
1537 sum.Add(entry.first->solution_value() * entry.second);
1538 }
1539 activities[i] = sum.Value();
1540 }
1541 return activities;
1542}
1543
1544// TODO(user): split.
1545bool MPSolver::VerifySolution(double tolerance, bool log_errors) const {
1546 double max_observed_error = 0;
1547 if (tolerance < 0) tolerance = infinity();
1548 int num_errors = 0;
1549
1550 // Verify variables.
1551 for (MPVariable* variable : variables_) {
1552 const MPVariable& var = *variable;
1553 const double value = var.solution_value();
1554 // Check for NaN.
1555 if (std::isnan(value)) {
1556 ++num_errors;
1557 max_observed_error = infinity();
1558 LOG_IF(ERROR, log_errors) << "NaN value for " << PrettyPrintVar(var);
1559 continue;
1560 }
1561 // Check lower bound.
1562 if (var.lb() != -infinity()) {
1563 if (value < var.lb() - tolerance) {
1564 ++num_errors;
1565 max_observed_error = std::max(max_observed_error, var.lb() - value);
1566 LOG_IF(ERROR, log_errors)
1567 << "Value " << value << " too low for " << PrettyPrintVar(var);
1568 }
1569 }
1570 // Check upper bound.
1571 if (var.ub() != infinity()) {
1572 if (value > var.ub() + tolerance) {
1573 ++num_errors;
1574 max_observed_error = std::max(max_observed_error, value - var.ub());
1575 LOG_IF(ERROR, log_errors)
1576 << "Value " << value << " too high for " << PrettyPrintVar(var);
1577 }
1578 }
1579 // Check integrality.
1580 if (IsMIP() && var.integer()) {
1581 if (fabs(value - round(value)) > tolerance) {
1582 ++num_errors;
1583 max_observed_error =
1584 std::max(max_observed_error, fabs(value - round(value)));
1585 LOG_IF(ERROR, log_errors)
1586 << "Non-integer value " << value << " for " << PrettyPrintVar(var);
1587 }
1588 }
1589 }
1590 if (!IsMIP() && HasIntegerVariables()) {
1591 LOG_IF(INFO, log_errors) << "Skipped variable integrality check, because "
1592 << "a continuous relaxation of the model was "
1593 << "solved (i.e., the selected solver does not "
1594 << "support integer variables).";
1595 }
1596
1597 // Verify constraints.
1598 const std::vector<double> activities = ComputeConstraintActivities();
1599 for (int i = 0; i < static_cast<int>(constraints_.size()); ++i) {
1600 const MPConstraint& constraint = *constraints_[i];
1601 const double activity = activities[i];
1602 // Re-compute the activity with a inaccurate summing algorithm.
1603 double inaccurate_activity = 0.0;
1604 for (const auto& entry : constraint.coefficients_) {
1605 inaccurate_activity += entry.first->solution_value() * entry.second;
1606 }
1607 // Catch NaNs.
1608 if (std::isnan(activity) || std::isnan(inaccurate_activity)) {
1609 ++num_errors;
1610 max_observed_error = infinity();
1611 LOG_IF(ERROR, log_errors)
1612 << "NaN value for " << PrettyPrintConstraint(constraint);
1613 continue;
1614 }
1615 // Check bounds.
1616 if (constraint.indicator_variable() == nullptr ||
1617 std::round(constraint.indicator_variable()->solution_value()) ==
1618 constraint.indicator_value()) {
1619 if (constraint.lb() != -infinity()) {
1620 if (activity < constraint.lb() - tolerance) {
1621 ++num_errors;
1622 max_observed_error =
1623 std::max(max_observed_error, constraint.lb() - activity);
1624 LOG_IF(ERROR, log_errors)
1625 << "Activity " << activity << " too low for "
1626 << PrettyPrintConstraint(constraint);
1627 } else if (inaccurate_activity < constraint.lb() - tolerance) {
1628 LOG_IF(WARNING, log_errors)
1629 << "Activity " << activity << ", computed with the (inaccurate)"
1630 << " standard sum of its terms, is too low for "
1631 << PrettyPrintConstraint(constraint);
1632 }
1633 }
1634 if (constraint.ub() != infinity()) {
1635 if (activity > constraint.ub() + tolerance) {
1636 ++num_errors;
1637 max_observed_error =
1638 std::max(max_observed_error, activity - constraint.ub());
1639 LOG_IF(ERROR, log_errors)
1640 << "Activity " << activity << " too high for "
1641 << PrettyPrintConstraint(constraint);
1642 } else if (inaccurate_activity > constraint.ub() + tolerance) {
1643 LOG_IF(WARNING, log_errors)
1644 << "Activity " << activity << ", computed with the (inaccurate)"
1645 << " standard sum of its terms, is too high for "
1646 << PrettyPrintConstraint(constraint);
1647 }
1648 }
1649 }
1650 }
1651
1652 // Verify that the objective value wasn't reported incorrectly.
1653 const MPObjective& objective = Objective();
1654 AccurateSum<double> objective_sum;
1655 objective_sum.Add(objective.offset());
1656 double inaccurate_objective_value = objective.offset();
1657 for (const auto& entry : objective.coefficients_) {
1658 const double term = entry.first->solution_value() * entry.second;
1659 objective_sum.Add(term);
1660 inaccurate_objective_value += term;
1661 }
1662 const double actual_objective_value = objective_sum.Value();
1664 objective.Value(), actual_objective_value, tolerance, tolerance)) {
1665 ++num_errors;
1666 max_observed_error = std::max(
1667 max_observed_error, fabs(actual_objective_value - objective.Value()));
1668 LOG_IF(ERROR, log_errors)
1669 << "Objective value " << objective.Value() << " isn't accurate"
1670 << ", it should be " << actual_objective_value
1671 << " (delta=" << actual_objective_value - objective.Value() << ").";
1672 } else if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(),
1673 inaccurate_objective_value,
1674 tolerance, tolerance)) {
1675 LOG_IF(WARNING, log_errors)
1676 << "Objective value " << objective.Value() << " doesn't correspond"
1677 << " to the value computed with the standard (and therefore inaccurate)"
1678 << " sum of its terms.";
1679 }
1680 if (num_errors > 0) {
1681 LOG_IF(ERROR, log_errors)
1682 << "There were " << num_errors << " errors above the tolerance ("
1683 << tolerance << "), the largest was " << max_observed_error;
1684 return false;
1685 }
1686 return true;
1687}
1688
1689bool MPSolver::OutputIsEnabled() const { return !interface_->quiet(); }
1690
1691void MPSolver::EnableOutput() { interface_->set_quiet(false); }
1692
1693void MPSolver::SuppressOutput() { interface_->set_quiet(true); }
1694
1695int64_t MPSolver::iterations() const { return interface_->iterations(); }
1696
1697int64_t MPSolver::nodes() const { return interface_->nodes(); }
1698
1700 return interface_->ComputeExactConditionNumber();
1701}
1702
1703bool MPSolver::OwnsVariable(const MPVariable* var) const {
1704 if (var == nullptr) return false;
1705 if (var->index() >= 0 && var->index() < static_cast<int>(variables_.size())) {
1706 // Then, verify that the variable with this index has the same address.
1707 return variables_[var->index()] == var;
1708 }
1709 return false;
1710}
1711
1713 std::string* model_str) const {
1714 MPModelProto proto;
1715 ExportModelToProto(&proto);
1716 MPModelExportOptions options;
1717 options.obfuscate = obfuscate;
1718 const auto status_or =
1720 *model_str = status_or.value_or("");
1721 return status_or.ok();
1722}
1723
1724bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate,
1725 std::string* model_str) const {
1726 MPModelProto proto;
1727 ExportModelToProto(&proto);
1728 MPModelExportOptions options;
1729 options.obfuscate = obfuscate;
1730 const auto status_or =
1732 *model_str = status_or.value_or("");
1733 return status_or.ok();
1734}
1735
1736void MPSolver::SetHint(std::vector<std::pair<const MPVariable*, double>> hint) {
1737 for (const auto& var_value_pair : hint) {
1738 CHECK(OwnsVariable(var_value_pair.first))
1739 << "hint variable does not belong to this solver";
1740 }
1741 solution_hint_ = std::move(hint);
1742}
1743
1744void MPSolver::GenerateVariableNameIndex() const {
1745 if (variable_name_to_index_) return;
1746 variable_name_to_index_ = absl::flat_hash_map<std::string, int>();
1747 for (const MPVariable* const var : variables_) {
1748 gtl::InsertOrDie(&*variable_name_to_index_, var->name(), var->index());
1749 }
1750}
1751
1752void MPSolver::GenerateConstraintNameIndex() const {
1753 if (constraint_name_to_index_) return;
1754 constraint_name_to_index_ = absl::flat_hash_map<std::string, int>();
1755 for (const MPConstraint* const cst : constraints_) {
1756 gtl::InsertOrDie(&*constraint_name_to_index_, cst->name(), cst->index());
1757 }
1758}
1759
1760bool MPSolver::NextSolution() { return interface_->NextSolution(); }
1761
1763 interface_->SetCallback(mp_callback);
1764}
1765
1767 return interface_->SupportsCallbacks();
1768}
1769
1770// Global counters.
1771absl::Mutex MPSolver::global_count_mutex_(absl::kConstInit);
1772int64_t MPSolver::global_num_variables_ = 0;
1773int64_t MPSolver::global_num_constraints_ = 0;
1774
1775// static
1777 absl::MutexLock lock(global_count_mutex_);
1778 return global_num_variables_;
1779}
1780
1781// static
1783 absl::MutexLock lock(global_count_mutex_);
1784 return global_num_constraints_;
1785}
1786
1788 switch (status) {
1789 // Cases that don't yield an RPC error when they happen on the server.
1790 case MPSOLVER_OPTIMAL:
1791 case MPSOLVER_FEASIBLE:
1794 case MPSOLVER_UNBOUNDED:
1795 case MPSOLVER_ABNORMAL:
1797 return false;
1798 // Cases that should never happen with the linear solver server. We prefer
1799 // to consider those as "not RPC errors".
1802 return false;
1803 // Cases that yield an RPC error when they happen on the server.
1809 return true;
1810 }
1811 LOG(DFATAL)
1812 << "MPSolverResponseStatusIsRpcError() called with invalid status "
1813 << "(value: " << status << ")";
1814 return false;
1815}
1816
1817// ---------- MPSolverInterface ----------
1818
1820
1821// TODO(user): Initialize objective value and bound to +/- inf (depending on
1822// optimization direction).
1824 : solver_(solver),
1826 result_status_(MPSolver::NOT_SOLVED),
1827 maximize_(false),
1830 objective_value_(0.0),
1832 quiet_(true) {}
1833
1835
1836void MPSolverInterface::Write(const std::string& filename) {
1837 LOG(WARNING) << "Writing model not implemented in this solver interface.";
1838}
1839
1841 switch (sync_status_) {
1842 case MUST_RELOAD: {
1846
1847 last_constraint_index_ = solver_->constraints_.size();
1848 last_variable_index_ = solver_->variables_.size();
1850 break;
1851 }
1852 case MODEL_SYNCHRONIZED: {
1853 // Everything has already been extracted.
1854 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1855 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1856 break;
1857 }
1858 case SOLUTION_SYNCHRONIZED: {
1859 // Nothing has changed since last solve.
1860 DCHECK_EQ(last_constraint_index_, solver_->constraints_.size());
1861 DCHECK_EQ(last_variable_index_, solver_->variables_.size());
1862 break;
1863 }
1864 }
1865}
1866
1867// TODO(user): remove this method.
1872 solver_->variable_is_extracted_.assign(solver_->variables_.size(), false);
1873 solver_->constraint_is_extracted_.assign(solver_->constraints_.size(), false);
1874}
1875
1878 LOG(DFATAL)
1879 << "The model has been changed since the solution was last computed."
1880 << " MPSolverInterface::sync_status_ = " << sync_status_;
1881 return false;
1882 }
1883 return true;
1884}
1885
1886// Default version that can be overwritten by a solver-specific
1887// version to accommodate for the quirks of each solver.
1891 LOG(DFATAL) << "No solution exists. MPSolverInterface::result_status_ = "
1892 << result_status_;
1893 return false;
1894 }
1895 return true;
1896}
1897
1899 if (!CheckSolutionIsSynchronizedAndExists()) return 0;
1900 return objective_value_;
1901}
1902
1904 const double trivial_worst_bound =
1905 maximize_ ? -std::numeric_limits<double>::infinity()
1906 : std::numeric_limits<double>::infinity();
1907 if (!IsMIP()) {
1908 VLOG(1) << "Best objective bound only available for discrete problems.";
1909 return trivial_worst_bound;
1910 }
1912 return trivial_worst_bound;
1913 }
1914 // Special case for empty model.
1915 if (solver_->variables_.empty() && solver_->constraints_.empty()) {
1916 return solver_->Objective().offset();
1917 }
1918 return best_objective_bound_;
1919}
1920
1926
1928 // Override this method in interfaces that actually support it.
1929 LOG(DFATAL) << "ComputeExactConditionNumber not implemented for "
1931 static_cast<MPModelRequest::SolverType>(
1932 solver_->ProblemType()));
1933 return 0.0;
1934}
1935
1937 // TODO(user): Overhaul the code that sets parameters to enable changing
1938 // GLOP parameters without issuing warnings.
1939 // By default, we let GLOP keep its own default tolerance, much more accurate
1940 // than for the rest of the solvers.
1941 //
1942 if (solver_->ProblemType() != MPSolver::GLOP_LINEAR_PROGRAMMING) {
1946 }
1948 // TODO(user): In the future, we could distinguish between the
1949 // algorithm to solve the root LP and the algorithm to solve node
1950 // LPs. Not sure if underlying solvers support it.
1953 SetLpAlgorithm(value);
1954 }
1955}
1956
1963
1966 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
1967}
1970 LOG(WARNING) << "Trying to set an unsupported parameter: " << param << ".";
1971}
1973 MPSolverParameters::DoubleParam param, double value) {
1974 LOG(WARNING) << "Trying to set a supported parameter: " << param
1975 << " to an unsupported value: " << value;
1976}
1978 MPSolverParameters::IntegerParam param, int value) {
1979 LOG(WARNING) << "Trying to set a supported parameter: " << param
1980 << " to an unsupported value: " << value;
1981}
1982
1983absl::Status MPSolverInterface::SetNumThreads(int num_threads) {
1984 return absl::UnimplementedError(
1985 absl::StrFormat("SetNumThreads() not supported by %s.", SolverVersion()));
1986}
1987
1989 const std::string& parameters) {
1990 if (parameters.empty()) {
1991 return true;
1992 }
1993
1994 LOG(WARNING) << "SetSolverSpecificParametersAsString() not supported by "
1995 << SolverVersion();
1996 return false;
1997}
1998
1999// ---------- MPSolverParameters ----------
2000
2002// For the primal and dual tolerances, choose the same default as CLP and GLPK.
2011
2016
2017// The constructor sets all parameters to their default value.
2019 : relative_mip_gap_value_(kDefaultRelativeMipGap),
2020 primal_tolerance_value_(kDefaultPrimalTolerance),
2021 dual_tolerance_value_(kDefaultDualTolerance),
2022 presolve_value_(kDefaultPresolve),
2023 scaling_value_(kDefaultIntegerParamValue),
2024 lp_algorithm_value_(kDefaultIntegerParamValue),
2025 incrementality_value_(kDefaultIncrementality),
2026 lp_algorithm_is_default_(true) {}
2027
2029 double value) {
2030 switch (param) {
2031 case RELATIVE_MIP_GAP: {
2032 relative_mip_gap_value_ = value;
2033 break;
2034 }
2035 case PRIMAL_TOLERANCE: {
2036 primal_tolerance_value_ = value;
2037 break;
2038 }
2039 case DUAL_TOLERANCE: {
2040 dual_tolerance_value_ = value;
2041 break;
2042 }
2043 default: {
2044 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2045 }
2046 }
2047}
2048
2050 int value) {
2051 switch (param) {
2052 case PRESOLVE: {
2053 if (value != PRESOLVE_OFF && value != PRESOLVE_ON) {
2054 LOG(ERROR) << "Trying to set a supported parameter: " << param
2055 << " to an unknown value: " << value;
2056 }
2057 presolve_value_ = value;
2058 break;
2059 }
2060 case SCALING: {
2061 if (value != SCALING_OFF && value != SCALING_ON) {
2062 LOG(ERROR) << "Trying to set a supported parameter: " << param
2063 << " to an unknown value: " << value;
2064 }
2065 scaling_value_ = value;
2066 break;
2067 }
2068 case LP_ALGORITHM: {
2069 if (value != DUAL && value != PRIMAL && value != BARRIER) {
2070 LOG(ERROR) << "Trying to set a supported parameter: " << param
2071 << " to an unknown value: " << value;
2072 }
2073 lp_algorithm_value_ = value;
2074 lp_algorithm_is_default_ = false;
2075 break;
2076 }
2077 case INCREMENTALITY: {
2078 if (value != INCREMENTALITY_OFF && value != INCREMENTALITY_ON) {
2079 LOG(ERROR) << "Trying to set a supported parameter: " << param
2080 << " to an unknown value: " << value;
2081 }
2082 incrementality_value_ = value;
2083 break;
2084 }
2085 default: {
2086 LOG(ERROR) << "Trying to set an unknown parameter: " << param << ".";
2087 }
2088 }
2089}
2090
2093 switch (param) {
2094 case RELATIVE_MIP_GAP: {
2095 relative_mip_gap_value_ = kDefaultRelativeMipGap;
2096 break;
2097 }
2098 case PRIMAL_TOLERANCE: {
2099 primal_tolerance_value_ = kDefaultPrimalTolerance;
2100 break;
2101 }
2102 case DUAL_TOLERANCE: {
2103 dual_tolerance_value_ = kDefaultDualTolerance;
2104 break;
2105 }
2106 default: {
2107 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2108 }
2109 }
2110}
2111
2114 switch (param) {
2115 case PRESOLVE: {
2116 presolve_value_ = kDefaultPresolve;
2117 break;
2118 }
2119 case SCALING: {
2120 scaling_value_ = kDefaultIntegerParamValue;
2121 break;
2122 }
2123 case LP_ALGORITHM: {
2124 lp_algorithm_is_default_ = true;
2125 break;
2126 }
2127 case INCREMENTALITY: {
2128 incrementality_value_ = kDefaultIncrementality;
2129 break;
2130 }
2131 default: {
2132 LOG(ERROR) << "Trying to reset an unknown parameter: " << param << ".";
2133 }
2134 }
2135}
2136
2146
2148 MPSolverParameters::DoubleParam param) const {
2149 switch (param) {
2150 case RELATIVE_MIP_GAP: {
2151 return relative_mip_gap_value_;
2152 }
2153 case PRIMAL_TOLERANCE: {
2154 return primal_tolerance_value_;
2155 }
2156 case DUAL_TOLERANCE: {
2157 return dual_tolerance_value_;
2158 }
2159 default: {
2160 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2162 }
2163 }
2164}
2165
2168 switch (param) {
2169 case PRESOLVE: {
2170 return presolve_value_;
2171 }
2172 case LP_ALGORITHM: {
2173 if (lp_algorithm_is_default_) return kDefaultIntegerParamValue;
2174 return lp_algorithm_value_;
2175 }
2176 case INCREMENTALITY: {
2177 return incrementality_value_;
2178 }
2179 case SCALING: {
2180 return scaling_value_;
2181 }
2182 default: {
2183 LOG(ERROR) << "Trying to get an unknown parameter: " << param << ".";
2185 }
2186 }
2187}
2188
2189// static
2191 const MPModelRequest& request) {
2192 std::string out;
2193#if !defined(__PORTABLE_PLATFORM__)
2194 MPModelRequest abbreviated_request;
2195 abbreviated_request = request;
2196 abbreviated_request.clear_model();
2197 abbreviated_request.clear_model_delta();
2198 google::protobuf::TextFormat::PrintToString(abbreviated_request, &out);
2199#else // __PORTABLE_PLATFORM__
2200 out = "<Info unavailable because: __PORTABLE_PLATFORM__>\n";
2201#endif // __PORTABLE_PLATFORM__
2202 if (request.model().has_name()) {
2203 absl::StrAppendFormat(&out, "model_name: '%s'\n", request.model().name());
2204 }
2205 return out;
2206}
2207
2210 static auto* const kInstance = new MPSolverInterfaceFactoryRepository();
2211 return kInstance;
2212}
2213
2214// This can't be covered by unit test coverage framework, because the Singleton
2215// destruction occurs after it finished collecting coverage.
2216// COV_NF_START
2217MPSolverInterfaceFactoryRepository::~MPSolverInterfaceFactoryRepository() {
2218 absl::MutexLock lock(mutex_);
2219 map_.clear();
2220}
2221// COV_NF_END
2222
2226 std::function<bool()> is_runtime_ready) {
2227 absl::MutexLock lock(mutex_);
2228 if (!is_runtime_ready) is_runtime_ready = []() { return true; };
2229 map_[problem_type] = Entry{
2230 .factory = std::move(factory),
2231 .is_runtime_ready = std::move(is_runtime_ready),
2232 };
2233}
2234
2236 MPSolver::OptimizationProblemType problem_type) {
2237 absl::MutexLock lock(mutex_);
2238 return map_.erase(problem_type) == 1;
2239}
2240
2242 MPSolver* solver) const {
2243 absl::MutexLock lock(mutex_);
2244 const Entry* entry = gtl::FindOrNull(map_, solver->ProblemType());
2245 CHECK(entry != nullptr) << "No factory registered for problem type "
2246 << ToString(solver->ProblemType());
2247 CHECK(entry->is_runtime_ready())
2248 << "Solver for problem type " << ToString(solver->ProblemType())
2249 << " is not ready.";
2250 return entry->factory(solver);
2251}
2252
2254 MPSolver::OptimizationProblemType problem_type) const {
2255 const Entry* entry = gtl::FindOrNull(map_, problem_type);
2256 if (entry == nullptr) return false;
2257 return entry->is_runtime_ready();
2258}
2259
2260std::vector<MPSolver::OptimizationProblemType>
2262 std::vector<MPSolver::OptimizationProblemType> out;
2263 for (auto it = map_.begin(); it != map_.end(); ++it) {
2264 out.push_back(it->first);
2265 }
2266 return out;
2267}
2268
2269std::string MPSolverInterfaceFactoryRepository // NOLINT
2271 std::string out;
2272 for (auto it = map_.begin(); it != map_.end(); ++it) {
2274 static_cast<MPModelRequest::SolverType>(it->first)) +
2275 "\n";
2276 }
2277 return out;
2278}
2279
2280} // namespace operations_research
void Add(const FpNumber &value)
const absl::flat_hash_map< const MPVariable *, double > & terms() const
const LinearExpr & linear_expr() const
void set_name(Arg_ &&arg, Args_... args)
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
double GetCoefficient(const MPVariable *var) const
void SetBounds(double lb, double ub)
Sets both the lower and upper bounds.
::operations_research::MPIndicatorConstraint *PROTOBUF_NONNULL mutable_indicator_constraint()
void set_name(Arg_ &&arg, Args_... args)
::operations_research::MPConstraintProto *PROTOBUF_NONNULL mutable_constraint()
::operations_research::MPConstraintProto *PROTOBUF_NONNULL add_constraint()
::operations_research::MPVariableProto *PROTOBUF_NONNULL add_variable()
::operations_research::MPGeneralConstraintProto *PROTOBUF_NONNULL add_general_constraint()
::operations_research::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
void set_name(Arg_ &&arg, Args_... args)
const ::std::string & name() const
static constexpr SolverType CPLEX_LINEAR_PROGRAMMING
static constexpr SolverType CBC_MIXED_INTEGER_PROGRAMMING
MPModelRequest_SolverType SolverType
static constexpr SolverType CLP_LINEAR_PROGRAMMING
static constexpr SolverType HIGHS_LINEAR_PROGRAMMING
static constexpr SolverType SAT_INTEGER_PROGRAMMING
static constexpr SolverType HIGHS_MIXED_INTEGER_PROGRAMMING
static constexpr SolverType BOP_INTEGER_PROGRAMMING
static constexpr SolverType GUROBI_MIXED_INTEGER_PROGRAMMING
static constexpr SolverType PDLP_LINEAR_PROGRAMMING
static constexpr SolverType XPRESS_LINEAR_PROGRAMMING
static constexpr SolverType KNAPSACK_MIXED_INTEGER_PROGRAMMING
static constexpr SolverType GLPK_MIXED_INTEGER_PROGRAMMING
static bool SolverType_Parse(::absl::string_view name, SolverType *PROTOBUF_NONNULL value)
static constexpr SolverType GLPK_LINEAR_PROGRAMMING
static constexpr SolverType GUROBI_LINEAR_PROGRAMMING
static constexpr SolverType CPLEX_MIXED_INTEGER_PROGRAMMING
const ::operations_research::MPModelProto & model() const
static constexpr SolverType GLOP_LINEAR_PROGRAMMING
static constexpr SolverType SCIP_MIXED_INTEGER_PROGRAMMING
static constexpr SolverType XPRESS_MIXED_INTEGER_PROGRAMMING
A class to express a linear objective.
void SetCoefficient(const MPVariable *var, double coeff)
double GetCoefficient(const MPVariable *var) const
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.
::operations_research::MPSolveInfo *PROTOBUF_NONNULL mutable_solve_info()
::operations_research::MPSolverResponseStatus status() const
void set_status_str(Arg_ &&arg, Args_... args)
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
void set_status(::operations_research::MPSolverResponseStatus value)
void set_solve_wall_time_seconds(double value)
MPSolverInterface * Create(MPSolver *solver) const
std::vector< MPSolver::OptimizationProblemType > ListAllRegisteredProblemTypes() const
static MPSolverInterfaceFactoryRepository * GetInstance()
bool Unregister(MPSolver::OptimizationProblemType problem_type)
bool Supports(MPSolver::OptimizationProblemType problem_type) const
void Register(MPSolverInterfaceFactory factory, MPSolver::OptimizationProblemType problem_type, std::function< bool()> is_runtime_ready={})
virtual void SetPrimalTolerance(double value)=0
virtual void SetRelativeMipGap(double value)=0
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
virtual void SetPresolveMode(int value)=0
virtual std::string SolverVersion() const =0
virtual absl::Status SetNumThreads(int num_threads)
virtual bool SetSolverSpecificParametersAsString(const std::string &parameters)
virtual void SetLpAlgorithm(int value)=0
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
bool variable_is_extracted(int var_index) const
void SetUnsupportedDoubleParam(MPSolverParameters::DoubleParam param)
void SetDoubleParamToUnsupportedValue(MPSolverParameters::DoubleParam param, double value)
virtual double ComputeExactConditionNumber() const
virtual void Write(const std::string &filename)
void SetMIPParameters(const MPSolverParameters &param)
virtual void SetDualTolerance(double value)=0
void SetCommonParameters(const MPSolverParameters &param)
static const PresolveValues kDefaultPresolve
void ResetIntegerParam(MPSolverParameters::IntegerParam param)
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.
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.
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)
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)
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)
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)
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)
MPVariable * variable(int index) const
bool OwnsVariable(const MPVariable *var) const
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)
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()
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()
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 void SolveWithProto(const MPModelRequest &model_request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
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.
void set_name(Arg_ &&arg, Args_... args)
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.
MPSolver::BasisStatus basis_status() const
int index() const
Returns the index of the variable in the MPSolver::variables_.
void Schedule(absl::AnyInvocable< void() && > callback)
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.")
void STLDeleteElements(T *container)
Definition stl_util.h:369
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition map_util.h:159
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition map_util.h:65
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
OR-Tools root namespace.
const ::std::string & MPModelRequest_SolverType_Name(T value)
constexpr double kDefaultPrimalTolerance
std::function< MPSolverInterface *(MPSolver *)> MPSolverInterfaceFactory
bool AreWithinAbsoluteOrRelativeTolerances(FloatType x, FloatType y, FloatType relative_tolerance, FloatType absolute_tolerance)
Definition fp_utils.h:131
absl::StatusOr< std::string > ExportModelAsLpFormat(const MPModelProto &model, const MPModelExportOptions &options)
bool SolverTypeSupportsInterruption(const MPModelRequest::SolverType solver)
bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status)
bool SolverTypeIsMip(MPModelRequest::SolverType solver_type)
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:63
constexpr NamedOptimizationProblemType kOptimizationProblemTypeNames[]
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)
bool obfuscate
Obfuscates variable and constraint names.