OR-Tools  9.1
cbc_interface.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
16#include <cstdint>
17#include <limits>
18#include <memory>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/status/status.h"
24#include "absl/strings/str_format.h"
26#include "ortools/base/hash.h"
29#include "ortools/base/timer.h"
31
32#if defined(USE_CBC)
33
34#undef PACKAGE
35#undef VERSION
36#include "CbcConfig.h"
37#include "CbcMessage.hpp"
38#include "CbcModel.hpp"
39#include "CoinModel.hpp"
40#include "OsiClpSolverInterface.hpp"
41
42// Heuristics
43
44namespace operations_research {
45
47 public:
48 // Constructor that takes a name for the underlying glpk solver.
49 explicit CBCInterface(MPSolver* const solver);
50 ~CBCInterface() override;
51
52 // ----- Reset -----
53 void Reset() override;
54
55 // Sets the optimization direction (min/max).
56 void SetOptimizationDirection(bool maximize) override;
57
58 // ----- Parameters -----
59
60 absl::Status SetNumThreads(int num_threads) override {
61 CHECK_GE(num_threads, 1);
62 num_threads_ = num_threads;
63 return absl::OkStatus();
64 }
65
66 // ----- Solve -----
67 // Solve the problem using the parameter values specified.
68 MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;
69
70 // TODO(user): separate the solve from the model extraction.
71 virtual void ExtractModel() {}
72
73 // Query problem type.
74 bool IsContinuous() const override { return false; }
75 bool IsLP() const override { return false; }
76 bool IsMIP() const override { return true; }
77
78 // Modify bounds.
79 void SetVariableBounds(int var_index, double lb, double ub) override;
80 void SetVariableInteger(int var_index, bool integer) override;
81 void SetConstraintBounds(int row_index, double lb, double ub) override;
82
83 // Add constraint incrementally.
84 void AddRowConstraint(MPConstraint* const ct) override;
85 // Add variable incrementally.
86 void AddVariable(MPVariable* const var) override;
87 // Change a coefficient in a constraint.
88 void SetCoefficient(MPConstraint* const constraint,
89 const MPVariable* const variable, double new_value,
90 double old_value) override {
92 }
93 // Clear a constraint from all its terms.
94 void ClearConstraint(MPConstraint* const constraint) override {
96 }
97
98 // Change a coefficient in the linear objective.
99 void SetObjectiveCoefficient(const MPVariable* const variable,
100 double coefficient) override {
102 }
103 // Change the constant term in the linear objective.
105 // Clear the objective from all its terms.
107
108 // Number of simplex iterations
109 int64_t iterations() const override;
110 // Number of branch-and-bound nodes. Only available for discrete problems.
111 int64_t nodes() const override;
112
113 // Returns the basis status of a row.
114 MPSolver::BasisStatus row_status(int constraint_index) const override {
115 LOG(FATAL) << "Basis status only available for continuous problems";
116 return MPSolver::FREE;
117 }
118 // Returns the basis status of a column.
119 MPSolver::BasisStatus column_status(int variable_index) const override {
120 LOG(FATAL) << "Basis status only available for continuous problems";
121 return MPSolver::FREE;
122 }
123
124 void ExtractNewVariables() override {}
125 void ExtractNewConstraints() override {}
126 void ExtractObjective() override {}
127
128 std::string SolverVersion() const override { return "Cbc " CBC_VERSION; }
129
130 // TODO(user): Maybe we should expose the CbcModel build from osi_
131 // instead, but a new CbcModel is built every time Solve is called,
132 // so it is not possible right now.
133 void* underlying_solver() override { return reinterpret_cast<void*>(&osi_); }
134
135 private:
136 // Reset best objective bound to +/- infinity depending on the
137 // optimization direction.
138 void ResetBestObjectiveBound();
139
140 // Set all parameters in the underlying solver.
141 void SetParameters(const MPSolverParameters& param) override;
142 // Set each parameter in the underlying solver.
143 void SetRelativeMipGap(double value) override;
144 void SetPrimalTolerance(double value) override;
145 void SetDualTolerance(double value) override;
146 void SetPresolveMode(int value) override;
147 void SetScalingMode(int value) override;
148 void SetLpAlgorithm(int value) override;
149
150 OsiClpSolverInterface osi_;
151 // TODO(user): remove and query number of iterations directly from CbcModel
152 int64_t iterations_;
153 int64_t nodes_;
154 // Special way to handle the relative MIP gap parameter.
155 double relative_mip_gap_;
156 int num_threads_ = 1;
157};
158
159// ----- Solver -----
160
161// Creates a LP/MIP instance with the specified name and minimization objective.
163 : MPSolverInterface(solver),
164 iterations_(0),
165 nodes_(0),
166 relative_mip_gap_(MPSolverParameters::kDefaultRelativeMipGap) {
167 osi_.setStrParam(OsiProbName, solver_->name_);
168 osi_.setObjSense(1);
169}
170
172
173// Reset the solver.
175 osi_.reset();
176 osi_.setObjSense(maximize_ ? -1 : 1);
177 osi_.setStrParam(OsiProbName, solver_->name_);
179}
180
181void CBCInterface::ResetBestObjectiveBound() {
182 if (maximize_) {
183 best_objective_bound_ = std::numeric_limits<double>::infinity();
184 } else {
185 best_objective_bound_ = -std::numeric_limits<double>::infinity();
186 }
187}
188
192 osi_.setObjSense(maximize ? -1 : 1);
193 } else {
195 }
196}
197
198namespace {
199// CBC adds a "dummy" variable with index 0 to represent the objective offset.
200int MPSolverVarIndexToCbcVarIndex(int var_index) { return var_index + 1; }
201} // namespace
202
203void CBCInterface::SetVariableBounds(int var_index, double lb, double ub) {
206 osi_.setColBounds(MPSolverVarIndexToCbcVarIndex(var_index), lb, ub);
207 } else {
209 }
210}
211
212void CBCInterface::SetVariableInteger(int var_index, bool integer) {
214 // TODO(user) : Check if this is actually a change.
216 if (integer) {
217 osi_.setInteger(MPSolverVarIndexToCbcVarIndex(var_index));
218 } else {
219 osi_.setContinuous(MPSolverVarIndexToCbcVarIndex(var_index));
220 }
221 } else {
223 }
224}
225
226void CBCInterface::SetConstraintBounds(int index, double lb, double ub) {
229 osi_.setRowBounds(index, lb, ub);
230 } else {
232 }
233}
234
237}
238
241}
242
243// Solve the LP/MIP. Returns true only if the optimal solution was revealed.
244// Returns the status of the search.
246 // CBC requires unique variable and constraint names. By using Lookup*, we
247 // generate variable and constraint indices and ensure the duplicate name
248 // crash will happen here with a readable error message.
249 if (!solver_->variables_.empty()) {
250 solver_->LookupVariableOrNull(solver_->variables_[0]->name());
251 }
252 if (!solver_->constraints_.empty()) {
253 solver_->LookupConstraintOrNull(solver_->constraints_[0]->name());
254 }
255
256 WallTimer timer;
257 timer.Start();
258
259 // Note that CBC does not provide any incrementality.
262 Reset();
263 }
264
265 // Special case if the model is empty since CBC is not able to
266 // handle this special case by itself.
267 if (solver_->variables_.empty() && solver_->constraints_.empty()) {
272 return result_status_;
273 }
274
275 // Finish preparing the problem.
276 // Define variables.
277 switch (sync_status_) {
278 case MUST_RELOAD: {
279 Reset();
280 CoinModel build;
281 // Create dummy variable for objective offset.
282 build.addColumn(0, nullptr, nullptr, 1.0, 1.0,
283 solver_->Objective().offset(), "dummy", false);
284 const int nb_vars = solver_->variables_.size();
285 for (int i = 0; i < nb_vars; ++i) {
286 MPVariable* const var = solver_->variables_[i];
288 const double obj_coeff = solver_->Objective().GetCoefficient(var);
289 if (var->name().empty()) {
290 build.addColumn(0, nullptr, nullptr, var->lb(), var->ub(), obj_coeff,
291 nullptr, var->integer());
292 } else {
293 build.addColumn(0, nullptr, nullptr, var->lb(), var->ub(), obj_coeff,
294 var->name().c_str(), var->integer());
295 }
296 }
297
298 // Define constraints.
299 int max_row_length = 0;
300 for (int i = 0; i < solver_->constraints_.size(); ++i) {
301 MPConstraint* const ct = solver_->constraints_[i];
303 if (ct->coefficients_.size() > max_row_length) {
304 max_row_length = ct->coefficients_.size();
305 }
306 }
307 std::unique_ptr<int[]> indices(new int[max_row_length]);
308 std::unique_ptr<double[]> coefs(new double[max_row_length]);
309
310 for (int i = 0; i < solver_->constraints_.size(); ++i) {
311 MPConstraint* const ct = solver_->constraints_[i];
312 const int size = ct->coefficients_.size();
313 int j = 0;
314 for (const auto& entry : ct->coefficients_) {
315 const int index = MPSolverVarIndexToCbcVarIndex(entry.first->index());
316 indices[j] = index;
317 coefs[j] = entry.second;
318 j++;
319 }
320 if (ct->name().empty()) {
321 build.addRow(size, indices.get(), coefs.get(), ct->lb(), ct->ub());
322 } else {
323 build.addRow(size, indices.get(), coefs.get(), ct->lb(), ct->ub(),
324 ct->name().c_str());
325 }
326 }
327 osi_.loadFromCoinModel(build);
328 break;
329 }
330 case MODEL_SYNCHRONIZED: {
331 break;
332 }
334 break;
335 }
336 }
337
338 // Changing optimization direction through OSI so that the model file
339 // (written through OSI) has the correct optimization duration.
340 osi_.setObjSense(maximize_ ? -1 : 1);
341
343 VLOG(1) << absl::StrFormat("Model built in %.3f seconds.", timer.Get());
344
345 ResetBestObjectiveBound();
346
347 // Solve
348 CbcModel model(osi_);
349
350 // Set log level.
351 CoinMessageHandler message_handler;
352 model.passInMessageHandler(&message_handler);
353 if (quiet_) {
354 message_handler.setLogLevel(0, 0); // Coin messages
355 message_handler.setLogLevel(1, 0); // Clp messages
356 message_handler.setLogLevel(2, 0); // Presolve messages
357 message_handler.setLogLevel(3, 0); // Cgl messages
358 } else {
359 message_handler.setLogLevel(0, 1); // Coin messages
360 message_handler.setLogLevel(1, 1); // Clp messages
361 message_handler.setLogLevel(2, 1); // Presolve messages
362 message_handler.setLogLevel(3, 1); // Cgl messages
363 }
364
365 // Time limit.
366 if (solver_->time_limit() != 0) {
367 VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
368 model.setMaximumSeconds(solver_->time_limit_in_secs());
369 }
370
371 // And solve.
372 timer.Restart();
373
374 // Here we use the default function from the command-line CBC solver.
375 // This enables to activate all the features and get the same performance
376 // as the CBC stand-alone executable. The syntax is ugly, however.
377 SetParameters(param);
378 // Always turn presolve on (it's the CBC default and it consistently
379 // improves performance).
380 model.setTypePresolve(0);
381 // Special way to set the relative MIP gap parameter as it cannot be set
382 // through callCbc.
383 model.setAllowableFractionGap(relative_mip_gap_);
384 // NOTE: Trailing space is required to avoid buffer overflow in cbc.
385 int return_status =
386 num_threads_ == 1
387 ? callCbc("-solve ", model)
388 : callCbc(absl::StrCat("-threads ", num_threads_, " -solve "), model);
389 const int kBadReturnStatus = 777;
390 CHECK_NE(kBadReturnStatus, return_status); // Should never happen according
391 // to the CBC source
392
393 VLOG(1) << absl::StrFormat("Solved in %.3f seconds.", timer.Get());
394
395 // Check the status: optimal, infeasible, etc.
396 int tmp_status = model.status();
397
398 VLOG(1) << "cbc result status: " << tmp_status;
399 /* Final status of problem
400 (info from cbc/.../CbcSolver.cpp,
401 See http://cs?q="cbc+status"+file:CbcSolver.cpp)
402 Some of these can be found out by is...... functions
403 -1 before branchAndBound
404 0 finished - check isProvenOptimal or isProvenInfeasible to see
405 if solution found
406 (or check value of best solution)
407 1 stopped - on maxnodes, maxsols, maxtime
408 2 difficulties so run was abandoned
409 (5 event user programmed event occurred)
410 */
411 switch (tmp_status) {
412 case 0:
413 // Order of tests counts; if model.isContinuousUnbounded() returns true,
414 // then so does model.isProvenInfeasible()!
415 if (model.isProvenOptimal()) {
417 } else if (model.isContinuousUnbounded()) {
419 } else if (model.isProvenInfeasible()) {
421 } else if (model.isAbandoned()) {
423 } else {
425 }
426 break;
427 case 1:
428 if (model.bestSolution() != nullptr) {
430 } else {
432 }
433 break;
434 default:
436 break;
437 }
438
441 // Get the results
442 objective_value_ = model.getObjValue();
443 VLOG(1) << "objective=" << objective_value_;
444 const double* const values = model.bestSolution();
445 if (values != nullptr) {
446 // if optimal or feasible solution is found.
447 for (int i = 0; i < solver_->variables_.size(); ++i) {
448 MPVariable* const var = solver_->variables_[i];
449 const int var_index = MPSolverVarIndexToCbcVarIndex(var->index());
450 const double val = values[var_index];
451 var->set_solution_value(val);
452 VLOG(3) << var->name() << "=" << val;
453 }
454 } else {
455 VLOG(1) << "No feasible solution found.";
456 }
457 }
458
459 iterations_ = model.getIterationCount();
460 nodes_ = model.getNodeCount();
461 best_objective_bound_ = model.getBestPossibleObjValue();
462 VLOG(1) << "best objective bound=" << best_objective_bound_;
463
465
466 return result_status_;
467}
468
469// ------ Query statistics on the solution and the solve ------
470
473 return iterations_;
474}
475
476int64_t CBCInterface::nodes() const {
478 return nodes_;
479}
480
481// ----- Parameters -----
482
483// The support for parameters in CBC is intentionally sparse. There is
484// a memory leak in callCbc that prevents to pass parameters through
485// it, so handling parameters would require an comprehensive rewrite
486// of the code. I will improve the parameter support only if there is
487// a relevant use case.
488
489void CBCInterface::SetParameters(const MPSolverParameters& param) {
490 SetCommonParameters(param);
491 SetMIPParameters(param);
492}
493
494void CBCInterface::SetRelativeMipGap(double value) {
495 relative_mip_gap_ = value;
496}
497
498void CBCInterface::SetPrimalTolerance(double value) {
499 // Skip the warning for the default value as it coincides with
500 // the default value in CBC.
503 }
504}
505
506void CBCInterface::SetDualTolerance(double value) {
507 // Skip the warning for the default value as it coincides with
508 // the default value in CBC.
511 }
512}
513
514void CBCInterface::SetPresolveMode(int value) {
515 switch (value) {
517 // CBC presolve is always on.
518 break;
519 }
520 default: {
522 }
523 }
524}
525
526void CBCInterface::SetScalingMode(int value) {
528}
529
530void CBCInterface::SetLpAlgorithm(int value) {
532}
533
535 return new CBCInterface(solver);
536}
537
538} // namespace operations_research
539#endif // #if defined(USE_CBC)
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
#define LOG(severity)
Definition: base/logging.h:416
#define VLOG(verboselevel)
Definition: base/logging.h:979
void Start()
Definition: timer.h:31
void Restart()
Definition: timer.h:35
double Get() const
Definition: timer.h:45
void AddRowConstraint(MPConstraint *const ct) override
bool IsContinuous() const override
void SetConstraintBounds(int row_index, double lb, double ub) override
MPSolver::ResultStatus Solve(const MPSolverParameters &param) override
void ClearConstraint(MPConstraint *const constraint) override
void SetObjectiveCoefficient(const MPVariable *const variable, double coefficient) override
void SetCoefficient(MPConstraint *const constraint, const MPVariable *const variable, double new_value, double old_value) override
MPSolver::BasisStatus row_status(int constraint_index) const override
void SetVariableInteger(int var_index, bool integer) override
void SetObjectiveOffset(double value) override
std::string SolverVersion() const override
absl::Status SetNumThreads(int num_threads) override
void AddVariable(MPVariable *const var) override
CBCInterface(MPSolver *const solver)
int64_t nodes() const override
void SetVariableBounds(int var_index, double lb, double ub) override
int64_t iterations() const override
void SetOptimizationDirection(bool maximize) override
MPSolver::BasisStatus column_status(int variable_index) const override
The class for constraints of a Mathematical Programming (MP) model.
double GetCoefficient(const MPVariable *const var) const
Gets the coefficient of a given variable in the objective.
double offset() const
Gets the constant term in the objective.
This mathematical programming (MP) solver class is the main class though which users build and solve ...
MPVariable * LookupVariableOrNull(const std::string &var_name) const
Looks up a variable by name, and returns nullptr if it does not exist.
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.
MPConstraint * LookupConstraintOrNull(const std::string &constraint_name) const
Looks up a constraint by name, and returns nullptr if it does not exist.
const MPObjective & Objective() const
Returns the objective object.
BasisStatus
Advanced usage: possible basis status values for a variable and the slack variable of a linear constr...
void SetUnsupportedDoubleParam(MPSolverParameters::DoubleParam param)
void set_constraint_as_extracted(int ct_index, bool extracted)
void SetMIPParameters(const MPSolverParameters &param)
static constexpr int64_t kUnknownNumberOfNodes
static constexpr int64_t kUnknownNumberOfIterations
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
void set_variable_as_extracted(int var_index, bool extracted)
void SetCommonParameters(const MPSolverParameters &param)
This class stores parameter settings for LP and MIP solvers.
@ INCREMENTALITY_OFF
Start solve from scratch.
@ DUAL_TOLERANCE
Advanced usage: tolerance for dual feasibility of basic solutions.
@ PRIMAL_TOLERANCE
Advanced usage: tolerance for primal feasibility of basic solutions.
@ 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.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
The class for variables of a Mathematical Programming (MP) model.
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
const int FATAL
Definition: log_severity.h:32
Collection of objects used to extend the Constraint Solver library.
MPSolverInterface * BuildCBCInterface(MPSolver *const solver)
int index
Definition: pack.cc:509
int64_t coefficient