cbc_interface.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 #include "cbc_interface.hpp"
26 
27 #include "CbcSOS.hpp"
28 #include "casadi/core/nlp_tools.hpp"
29 
30 namespace casadi {
31 
32  extern "C"
33  int CASADI_CONIC_CBC_EXPORT
34  casadi_register_conic_cbc(Conic::Plugin* plugin) {
35  plugin->creator = CbcInterface::creator;
36  plugin->name = "cbc";
37  plugin->doc = CbcInterface::meta_doc.c_str();
38  plugin->version = CASADI_VERSION;
39  plugin->options = &CbcInterface::options_;
40  plugin->deserialize = &CbcInterface::deserialize;
41  return 0;
42  }
43 
44  extern "C"
45  void CASADI_CONIC_CBC_EXPORT casadi_load_conic_cbc() {
47  }
48 
49 
50  CbcInterface::CbcInterface(const std::string& name,
51  const std::map<std::string, Sparsity>& st)
52  : Conic(name, st) {
53 
54  hot_start_ = false;
55  }
56 
58  = {{&Conic::options_},
59  {{"cbc",
60  {OT_DICT,
61  "Options to be passed to CBC."
62  "Three sets of options are supported. "
63  "The first can be found in OsiSolverParameters.hpp. "
64  "The second can be found in CbcModel.hpp. "
65  "The third are options that can be passed to CbcMain1."
66  }},
67  {"sos_groups",
69  "Definition of SOS groups by indices."}},
70  {"sos_weights",
72  "Weights corresponding to SOS entries."}},
73  {"sos_types",
74  {OT_INTVECTOR,
75  "Specify 1 or 2 for each SOS group."}},
76  {"hot_start",
77  {OT_BOOL,
78  "Hot start with x0 [Default false]."}},
79  }
80  };
81 
82  inline std::string return_status_string(int status) {
83  switch (status) {
84  case -1:
85  return "before branchAndBound";
86  case 0:
87  return "finished";
88  case 1:
89  return "stopped - on maxnodes, maxsols, maxtime";
90  case 2:
91  return "difficulties so run was abandoned";
92  case 5:
93  return "stopped by event handler";
94  default:
95  return "unknown";
96  }
97  }
98 
99  std::map<std::string, CbcModel::CbcIntParam> CbcInterface::param_map_int = {
100  {"MaxNumNode", CbcModel::CbcMaxNumNode},
101  {"MaxNumSol", CbcModel::CbcMaxNumSol},
102  {"FathomDiscipline", CbcModel::CbcFathomDiscipline},
103  {"Printing", CbcModel::CbcPrinting},
104  {"NumberBranches", CbcModel::CbcNumberBranches},
105  };
106 
107  std::map<std::string, CbcModel::CbcDblParam> CbcInterface::param_map_double = {
108  {"IntegerTolerance", CbcModel::CbcIntegerTolerance},
109  {"InfeasibilityWeight", CbcModel::CbcInfeasibilityWeight},
110  {"CutoffIncrement", CbcModel::CbcCutoffIncrement},
111  {"AllowableGap", CbcModel::CbcAllowableGap},
112  {"AllowableFractionGap", CbcModel::CbcAllowableFractionGap},
113  {"MaximumSeconds", CbcModel::CbcMaximumSeconds},
114  {"CurrentCutoff", CbcModel::CbcCurrentCutoff},
115  {"OptimizationDirection", CbcModel::CbcOptimizationDirection},
116  {"CurrentObjectiveValue", CbcModel::CbcCurrentObjectiveValue},
117  {"CurrentMinimizationObjectiveValue", CbcModel::CbcCurrentMinimizationObjectiveValue},
118  {"StartSeconds", CbcModel::CbcStartSeconds},
119  {"HeuristicGap", CbcModel::CbcHeuristicGap},
120  {"HeuristicFractionGap", CbcModel::CbcHeuristicFractionGap},
121  {"SmallestChange", CbcModel::CbcSmallestChange},
122  {"SumChange", CbcModel::CbcSumChange},
123  {"LargestChange", CbcModel::CbcLargestChange},
124  {"SmallChange", CbcModel::CbcSmallChange}
125  };
126 
127  std::map<std::string, OsiIntParam> CbcInterface::osi_param_map_int = {
128  {"MaxNumIteration", OsiMaxNumIteration},
129  {"MaxNumIterationHotStart", OsiMaxNumIterationHotStart},
130  {"NameDiscipline", OsiNameDiscipline}
131  };
132 
133  std::map<std::string, OsiDblParam> CbcInterface::osi_param_map_double = {
134  {"DualObjectiveLimit", OsiDualObjectiveLimit},
135  {"PrimalObjectiveLimit", OsiPrimalObjectiveLimit},
136  {"DualTolerance", OsiDualTolerance},
137  {"PrimalTolerance", OsiPrimalTolerance},
138  {"ObjOffset", OsiObjOffset}
139  };
140 
141  inline std::string return_secondary_status_string(int status) {
142  switch (status) {
143  case -1:
144  return "unset";
145  case 0:
146  return "search completed with solution";
147  case 1:
148  return "linear relaxation not feasible (or worse than cutoff)";
149  case 2:
150  return "stopped on gap";
151  case 3:
152  return "stopped on nodes";
153  case 4:
154  return "stopped on time";
155  case 5:
156  return "stopped on user event";
157  case 6:
158  return "stopped on solutions";
159  case CbcEventHandler::CbcEvent::node:
160  return "node";
161  case CbcEventHandler::CbcEvent::treeStatus:
162  return "treeStatus";
163  case CbcEventHandler::CbcEvent::solution:
164  return "solution";
165  case CbcEventHandler::CbcEvent::heuristicSolution:
166  return "heuristicSolution";
167  case CbcEventHandler::CbcEvent::beforeSolution1:
168  return "beforeSolution1";
169  case CbcEventHandler::CbcEvent::beforeSolution2:
170  return "beforeSolution2";
171  case CbcEventHandler::CbcEvent::afterHeuristic:
172  return "afterHeuristic";
173  case CbcEventHandler::CbcEvent::smallBranchAndBound:
174  return "smallBranchAndBound";
175  case CbcEventHandler::CbcEvent::heuristicPass:
176  return "heuristicPass";
177  case CbcEventHandler::CbcEvent::convertToCuts:
178  return "convertToCuts";
179  case CbcEventHandler::CbcEvent::endSearch:
180  return "endSearch";
181  default:
182  return "unknown";
183  }
184  }
185 
186  class CasadiHandler : public CoinMessageHandler {
187  public:
188  virtual int print() ;
189  };
190 
191  int CasadiHandler::print() {
192  uout() << messageBuffer() << std::endl;
193  return 0;
194  }
195 
196  void CbcInterface::copy_cbc_results(const CbcModel& model, double** res) const {
197  // Primal solution
198  const double* x = model.getColSolution();
199  casadi_copy(x, nx_, res[CONIC_X]);
200 
201  // Dual solution (x)
202  const double* minus_lam_x = model.getReducedCost();
203  if (res[CONIC_LAM_X]) {
204  casadi_copy(minus_lam_x, nx_, res[CONIC_LAM_X]);
205  casadi_scal(nx_, -1., res[CONIC_LAM_X]);
206  }
207 
208  // Dual solution (A)
209  const double* minus_lam_a = model.getRowPrice();
210  if (res[CONIC_LAM_A]) {
211  casadi_copy(minus_lam_a, na_, res[CONIC_LAM_A]);
212  casadi_scal(na_, -1., res[CONIC_LAM_A]);
213  }
214 
215  // Optimal cost
216  double f = model.getObjValue();
217  if (res[CONIC_COST]) *res[CONIC_COST] = f;
218  }
219 
220  void CbcInterface::init(const Dict& opts) {
221  // Call the init method of the base class
222  Conic::init(opts);
223 
224  // Read user options
225  for (auto&& op : opts) {
226  if (op.first=="sos_groups") {
227  sos_groups_ = to_int(op.second.to_int_vector_vector());
228  } else if (op.first=="sos_weights") {
229  sos_weights_ = op.second.to_double_vector_vector();
230  } else if (op.first=="sos_types") {
231  sos_types_ = op.second.to_int_vector();
232  } else if (op.first=="hot_start") {
233  hot_start_ = op.second;
234  } else if (op.first=="cbc") {
235  opts_ = op.second;
236  }
237  }
238 
239  // Validaty SOS constraints
240  check_sos(nx_, sos_groups_, sos_weights_, sos_types_);
241 
242  // Default options
243  casadi_assert(H_.nnz()==0, "Not an LP");
244 
245  // Allocate work vectors
246  alloc_w(nx_, true); // g
247  alloc_w(nx_, true); // lbx
248  alloc_w(nx_, true); // ubx
249  alloc_w(na_, true); // lba
250  alloc_w(na_, true); // uba
251  alloc_w(nnz_in(CONIC_H), true); // H
252  alloc_w(nnz_in(CONIC_A), true); // A
253  }
254 
255  int CbcInterface::init_mem(void* mem) const {
256  if (Conic::init_mem(mem)) return 1;
257  if (!mem) return 1;
258  auto m = static_cast<CbcMemory*>(mem);
259 
260  m->add_stat("preprocessing");
261  m->add_stat("solver");
262  m->add_stat("postprocessing");
263 
264  m->colind.resize(A_.size2()+1);
265  m->row.resize(A_.nnz());
266 
267  return 0;
268  }
269 
271  solve(const double** arg, double** res, casadi_int* iw, double* w, void* mem) const {
272  auto m = static_cast<CbcMemory*>(mem);
273 
274  // Problem has not been solved at this point
275  m->return_status = -1;
276  m->secondary_return_status = -1;
277 
278  m->fstats.at("preprocessing").tic();
279 
280  // Get inputs
281  double* g=w; w += nx_;
282  casadi_copy(arg[CONIC_G], nx_, g);
283  double* lbx=w; w += nx_;
284  casadi_copy(arg[CONIC_LBX], nx_, lbx);
285  double* ubx=w; w += nx_;
286  casadi_copy(arg[CONIC_UBX], nx_, ubx);
287  double* lba=w; w += na_;
288  casadi_copy(arg[CONIC_LBA], na_, lba);
289  double* uba=w; w += na_;
290  casadi_copy(arg[CONIC_UBA], na_, uba);
291  double* H=w; w += nnz_in(CONIC_H);
292  casadi_copy(arg[CONIC_H], nnz_in(CONIC_H), H);
293  double* A=w; w += nnz_in(CONIC_A);
294  casadi_copy(arg[CONIC_A], nnz_in(CONIC_A), A);
295 
296  copy_vector(A_.colind(), m->colind);
297  copy_vector(A_.row(), m->row);
298 
299  // Create osi_model
300  OsiClpSolverInterface osi_model;
301 
302  osi_model.loadProblem(A_.size2(), A_.size1(), get_ptr(m->colind), get_ptr(m->row), A,
303  lbx, ubx, g, lba, uba);
304 
305  // Pass information on discreteness
306  if (!discrete_.empty()) {
307  for (casadi_int i=0; i<A_.size2();++i) {
308  if (discrete_[i]) osi_model.setInteger(i);
309  }
310  }
311 
312  CbcModel model(osi_model);
313 
314  if (hot_start_) {
315  model.setBestSolution(arg[CONIC_X0], nx_, COIN_DBL_MAX, true);
316 
317  // We store the result here already, because when CbcMain1 cannot do
318  // better than setBestSolution() it will return a bogus result.
319  copy_cbc_results(model, res);
320  }
321 
322  // Construct SOS constraints
323  std::vector<CbcSOS> sos_objects;
324  for (casadi_int i=0;i<sos_groups_.size();++i) {
325  const std::vector<int>& sos_group = sos_groups_[i];
326  sos_objects.emplace_back(&model, sos_group.size(), get_ptr(sos_group),
327  sos_weights_.empty() ? nullptr : get_ptr(sos_weights_[i]), i, sos_types_[i]);
328  }
329  std::vector<CbcObject*> sos_objects_ptr;
330  for (casadi_int i=0;i<sos_groups_.size();++i) {
331  sos_objects_ptr.push_back(&sos_objects[i]);
332  }
333  if (!sos_objects.empty()) {
334  model.addObjects(sos_objects.size(), get_ptr(sos_objects_ptr));
335  }
336 
337  // Reset options
338  CbcMain0(model);
339 
340  std::vector<std::string> main1_options(1, "CbcInterface");
341 
342  // Read Osi options
343  for (auto&& op : opts_) {
344  {
345  // Check for double params
346  auto it = param_map_double.find(op.first);
347  if (it!=param_map_double.end()) {
348  casadi_assert(model.setDblParam(it->second, op.second.to_double()),
349  "Error setting option '" + op.first + "'.");
350  continue;
351  }
352  }
353  {
354  // Check for integer params
355  auto it = param_map_int.find(op.first);
356  if (it!=param_map_int.end()) {
357  casadi_assert(model.setIntParam(it->second, op.second.to_int()),
358  "Error setting option '" + op.first + "'.");
359  continue;
360  }
361  }
362  {
363  // Check for double params
364  auto it = osi_param_map_double.find(op.first);
365  if (it!=osi_param_map_double.end()) {
366  casadi_assert(model.solver()->setDblParam(it->second, op.second.to_double()),
367  "Error setting option '" + op.first + "'.");
368  continue;
369  }
370  }
371  {
372  // Check for integer params
373  auto it = osi_param_map_int.find(op.first);
374  if (it!=osi_param_map_int.end()) {
375  casadi_assert(model.solver()->setIntParam(it->second, op.second.to_int()),
376  "Error setting option '" + op.first + "'.");
377  continue;
378  }
379  }
380  if (op.first=="startalg") {
381  std::string startalg = op.second.to_string();
382  main1_options.push_back("-" + op.second.to_string());
383  } else {
384  main1_options.push_back("-" + op.first);
385  main1_options.push_back(str(op.second));
386  }
387  }
388 
389  main1_options.push_back("-solve");
390  main1_options.push_back("-quit");
391 
392  std::vector<const char*> main_options_char;
393  for (const auto& s : main1_options) main_options_char.push_back(s.c_str());
394 
395  CasadiHandler ch;
396  model.passInMessageHandler(&ch);
397 
398  m->fstats.at("preprocessing").toc();
399  m->fstats.at("solver").tic();
400 
401  CbcMain1(main_options_char.size(), get_ptr(main_options_char), model);
402 
403  m->fstats.at("solver").toc();
404  m->fstats.at("postprocessing").tic();
405 
406  if (hot_start_ && model.status() == 0 &&
407  model.isProvenOptimal() && model.secondaryStatus() == 1) {
408  // Solution found by setBestSolution is best and only correct one.
409  } else {
410  copy_cbc_results(model, res);
411  }
412 
413  m->fstats.at("postprocessing").toc();
414 
415  m->return_status = model.status();
416  m->d_qp.success = m->return_status==0 &&
417  model.isProvenOptimal() &&
418  model.secondaryStatus() <= 1;
419  m->secondary_return_status = model.secondaryStatus();
420  m->iter_count = model.getIterationCount();
421  m->node_count = model.getNodeCount();
422  if (m->return_status==1) m->d_qp.unified_return_status = SOLVER_RET_LIMITED;
423 
424  if (verbose_) casadi_message("CBC return status: " + return_status_string(m->return_status));
425  if (verbose_) casadi_message(
426  "CBC secondary return status: " + return_secondary_status_string(m->secondary_return_status));
427 
428  return 0;
429  }
430 
432  clear_mem();
433  }
434 
436  }
437 
439  }
440 
441 
442  Dict CbcInterface::get_stats(void* mem) const {
443  Dict stats = Conic::get_stats(mem);
444  auto m = static_cast<CbcMemory*>(mem);
445  stats["return_status"] = return_status_string(m->return_status);
446  stats["secondary_return_status"] = return_secondary_status_string(m->secondary_return_status);
447  stats["iter_count"] = m->iter_count;
448  stats["node_count"] = m->node_count;
449  return stats;
450  }
451 
453  s.version("CbcInterface", 1);
454  s.unpack("CbcInterface::opts", opts_);
455  s.unpack("CbcInterface::sos_groups", sos_groups_);
456  s.unpack("CbcInterface::sos_weights", sos_weights_);
457  s.unpack("CbcInterface::sos_types", sos_types_);
458  s.unpack("CbcInterface::hot_start", hot_start_);
459  }
460 
463 
464  s.version("CbcInterface", 1);
465  s.pack("CbcInterface::opts", opts_);
466  s.pack("CbcInterface::sos_groups", sos_groups_);
467  s.pack("CbcInterface::sos_weights", sos_weights_);
468  s.pack("CbcInterface::sos_types", sos_types_);
469  s.pack("CbcInterface::hot_start", hot_start_);
470  }
471 
472 } // end namespace casadi
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
int init_mem(void *mem) const override
Initalize memory block.
static const std::string meta_doc
A documentation string.
static Conic * creator(const std::string &name, const std::map< std::string, Sparsity > &st)
Create a new QP Solver.
CbcInterface(const std::string &name, const std::map< std::string, Sparsity > &st)
Constructor using sparsity patterns.
static const Options options_
Options.
void init(const Dict &opts) override
Initialize.
Dict opts_
All CBC options.
int solve(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Solve the QP.
Dict get_stats(void *mem) const override
Get all statistics.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
~CbcInterface() override
Destructor.
Internal class.
Definition: conic_impl.hpp:44
static const Options options_
Options.
Definition: conic_impl.hpp:83
casadi_int nx_
Number of decision variables.
Definition: conic_impl.hpp:169
int init_mem(void *mem) const override
Initalize memory block.
Definition: conic.cpp:451
casadi_int na_
The number of constraints (counting both equality and inequality) == A.size1()
Definition: conic_impl.hpp:172
Sparsity H_
Problem structure.
Definition: conic_impl.hpp:166
void init(const Dict &opts) override
Initialize.
Definition: conic.cpp:412
std::vector< bool > discrete_
Options.
Definition: conic_impl.hpp:161
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: conic.cpp:738
Dict get_stats(void *mem) const override
Get all statistics.
Definition: conic.cpp:711
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
casadi_int nnz_in() const
Number of input/output nonzeros.
void alloc_w(size_t sz_w, bool persistent=false)
Ensure required length of w field.
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
bool verbose_
Verbose printout.
void clear_mem()
Clear all memory (called from destructor)
Helper class for Serialization.
void version(const std::string &name, int v)
void pack(const Sparsity &e)
Serializes an object to the output stream.
casadi_int size1() const
Get the number of rows.
Definition: sparsity.cpp:124
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
casadi_int size2() const
Get the number of columns.
Definition: sparsity.cpp:128
const casadi_int * row() const
Get a reference to row-vector,.
Definition: sparsity.cpp:164
const casadi_int * colind() const
Get a reference to the colindex of all column element (see class description)
Definition: sparsity.cpp:168
The casadi namespace.
Definition: archiver.cpp:28
void copy_vector(const std::vector< S > &s, std::vector< D > &d)
@ CONIC_UBA
dense, (nc x 1)
Definition: conic.hpp:181
@ CONIC_X0
dense, (n x 1)
Definition: conic.hpp:187
@ CONIC_A
The matrix A: sparse, (nc x n) - product with x must be dense.
Definition: conic.hpp:177
@ CONIC_G
The vector g: dense, (n x 1)
Definition: conic.hpp:175
@ CONIC_LBA
dense, (nc x 1)
Definition: conic.hpp:179
@ CONIC_UBX
dense, (n x 1)
Definition: conic.hpp:185
@ CONIC_H
Definition: conic.hpp:173
@ CONIC_LBX
dense, (n x 1)
Definition: conic.hpp:183
int to_int(casadi_int rhs)
Definition: casadi_misc.cpp:56
void casadi_copy(const T1 *x, casadi_int n, T1 *y)
COPY: y <-x.
const char * return_status_string(Bonmin::TMINLP::SolverReturn status)
@ OT_INTVECTOR
@ OT_DOUBLEVECTORVECTOR
@ OT_INTVECTORVECTOR
std::string str(const T &v)
String representation, any type.
void check_sos(casadi_int nx, const std::vector< std::vector< T > > &groups, std::vector< std::vector< double > > &weights, std::vector< casadi_int > &types)
Check sos structure and generate defaults.
Definition: nlp_tools.hpp:79
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
void CASADI_CONIC_CBC_EXPORT casadi_load_conic_cbc()
void casadi_scal(casadi_int n, T1 alpha, T1 *x)
SCAL: x <- alpha*x.
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
int CASADI_CONIC_CBC_EXPORT casadi_register_conic_cbc(Conic::Plugin *plugin)
std::ostream & uout()
@ SOLVER_RET_LIMITED
std::string return_secondary_status_string(int status)
@ CONIC_X
The primal solution.
Definition: conic.hpp:201
@ CONIC_LAM_A
The dual solution corresponding to linear bounds.
Definition: conic.hpp:205
@ CONIC_COST
The optimal cost.
Definition: conic.hpp:203
@ CONIC_LAM_X
The dual solution corresponding to simple bounds.
Definition: conic.hpp:207
CbcMemory()
Constructor.
~CbcMemory()
Destructor.
Options metadata for a class.
Definition: options.hpp:40
void add_stat(const std::string &s)