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