clp_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 "clp_interface.hpp"
26 
27 namespace casadi {
28 
29  extern "C"
30  int CASADI_CONIC_CLP_EXPORT
31  casadi_register_conic_clp(Conic::Plugin* plugin) {
32  plugin->creator = ClpInterface::creator;
33  plugin->name = "clp";
34  plugin->doc = ClpInterface::meta_doc.c_str();
35  plugin->version = CASADI_VERSION;
36  plugin->options = &ClpInterface::options_;
37  plugin->deserialize = &ClpInterface::deserialize;
38  return 0;
39  }
40 
41  extern "C"
42  void CASADI_CONIC_CLP_EXPORT casadi_load_conic_clp() {
44  }
45 
46 
47  ClpInterface::ClpInterface(const std::string& name,
48  const std::map<std::string, Sparsity>& st)
49  : Conic(name, st) {
50  }
51 
53  = {{&Conic::options_},
54  {{"clp",
55  {OT_DICT,
56  "Options to be passed to CLP. "
57  "A first set of options can be found in ClpParameters.hpp. eg. 'PrimalTolerance'. "
58  "There are other options in additions. "
59  "'AutomaticScaling' (bool) is recognised. "
60  "'initial_solve' (default off) activates the use of Clp's initialSolve. "
61  "'initial_solve_options' takes a dictionary with following keys (see ClpSolve.hpp): "
62  " SolveType (string), PresolveType (string), "
63  " NumberPasses, SpecialOptions (intvectorvector), IndependentOptions (intvectorvector)."
64  }}
65  }
66  };
67 
68  inline std::string return_status_string(int status) {
69  switch (status) {
70  case 0:
71  return "optimal";
72  case 1:
73  return "primal infeasible";
74  case 2:
75  return "dual infeasible";
76  case 3:
77  return "stopped on iterations or time";
78  case 4:
79  return "stopped due to errors";
80  case 5:
81  return "stopped by event handler";
82  default:
83  return "unknown";
84  }
85  }
86 
87  std::map<std::string, ClpIntParam> ClpInterface::param_map_int = {
88  {"MaxNumIteration", ClpMaxNumIteration},
89  {"MaxNumIterationHotStart", ClpMaxNumIterationHotStart},
90  {"NameDiscipline", ClpMaxNumIteration},
91  };
92 
93  std::map<std::string, ClpDblParam> ClpInterface::param_map_double = {
94  {"DualObjectiveLimit", ClpDualObjectiveLimit},
95  {"PrimalObjectiveLimit", ClpPrimalObjectiveLimit},
96  {"DualTolerance", ClpDualTolerance},
97  {"PrimalTolerance", ClpPrimalTolerance},
98  {"ObjOffset", ClpObjOffset},
99  {"MaxSeconds", ClpMaxSeconds},
100  {"MaxWallSeconds", ClpMaxWallSeconds},
101  {"PresolveTolerance", ClpPresolveTolerance}
102  };
103 
104  std::map<std::string, ClpSolve::SolveType> ClpInterface::param_map_solvetype = {
105  {"useDual", ClpSolve::useDual},
106  {"usePrimal", ClpSolve::usePrimal},
107  {"usePrimalorSprint", ClpSolve::usePrimal},
108  {"useBarrier", ClpSolve::useBarrier},
109  {"useBarrierNoCross", ClpSolve::useBarrierNoCross},
110  {"automatic", ClpSolve::automatic},
111  {"tryDantzigWolfe", ClpSolve::tryDantzigWolfe},
112  {"tryBenders", ClpSolve::tryBenders},
113  };
114 
115  std::map<std::string, ClpSolve::PresolveType> ClpInterface::param_map_presolvetype = {
116  {"presolveOn", ClpSolve::presolveOn},
117  {"presolveOff", ClpSolve::presolveOff},
118  {"presolveNumber", ClpSolve::presolveNumber},
119  {"presolveNumberCost", ClpSolve::presolveNumberCost}
120  };
121 
122 
123  inline std::string return_secondary_status_string(int status) {
124  switch (status) {
125  case 0:
126  return "none";
127  case 1:
128  return "primal infeasible because dual limit reached OR (probably primal"
129  " infeasible but can't prove it - main status was 4)";
130  case 2:
131  return "scaled problem optimal - unscaled problem has primal infeasibilities";
132  case 3:
133  return "scaled problem optimal - unscaled problem has dual infeasibilities";
134  case 4:
135  return "scaled problem optimal - unscaled problem has primal and dual infeasibilities";
136  case 5:
137  return "giving up in primal with flagged variables";
138  case 6:
139  return "failed due to empty problem check";
140  case 7:
141  return "postSolve says not optimal";
142  case 8:
143  return "failed due to bad element check";
144  case 9:
145  return "status was 3 and stopped on time";
146  case 10:
147  return "status was 3 but stopped as primal feasibles";
148  case ClpEventHandler::Event::endOfIteration:
149  return "endOfIteration";
150  case ClpEventHandler::Event::endOfFactorization:
151  return "endOfFactorization";
152  case ClpEventHandler::Event::endOfValuesPass:
153  return "endOfValuesPass";
154  case ClpEventHandler::Event::node:
155  return "node";
156  case ClpEventHandler::Event::treeStatus:
157  return "treeStatus";
158  case ClpEventHandler::Event::solution:
159  return "solution";
160  case ClpEventHandler::Event::theta:
161  return "theta";
162  case ClpEventHandler::Event::pivotRow:
163  return "pivotRow";
164  case ClpEventHandler::Event::presolveStart:
165  return "presolveStart";
166  case ClpEventHandler::Event::presolveSize:
167  return "presolveSize";
168  case ClpEventHandler::Event::presolveInfeasible:
169  return "presolveInfeasible";
170  case ClpEventHandler::Event::presolveBeforeSolve:
171  return "presolveBeforeSolve";
172  case ClpEventHandler::Event::presolveAfterFirstSolve:
173  return "presolveAfterFirstSolve";
174  case ClpEventHandler::Event::presolveAfterSolve:
175  return "presolveAfterSolve";
176  case ClpEventHandler::Event::presolveEnd:
177  return "presolveEnd";
178  case ClpEventHandler::Event::goodFactorization:
179  return "goodFactorization";
180  case ClpEventHandler::Event::complicatedPivotIn:
181  return "complicatedPivotIn";
182  case ClpEventHandler::Event::noCandidateInPrimal:
183  return "noCandidateInPrimal";
184  case ClpEventHandler::Event::looksEndInPrimal:
185  return "looksEndInPrimal";
186  case ClpEventHandler::Event::endInPrimal:
187  return "endInPrimal";
188  case ClpEventHandler::Event::beforeStatusOfProblemInPrimal:
189  return "beforeStatusOfProblemInPrimal";
190  case ClpEventHandler::Event::startOfStatusOfProblemInPrimal:
191  return "startOfStatusOfProblemInPrimal";
192  case ClpEventHandler::Event::complicatedPivotOut:
193  return "complicatedPivotOut";
194  case ClpEventHandler::Event::noCandidateInDual:
195  return "noCandidateInDual";
196  case ClpEventHandler::Event::looksEndInDual:
197  return "looksEndInDual";
198  case ClpEventHandler::Event::endInDual:
199  return "endInDual";
200  case ClpEventHandler::Event::beforeStatusOfProblemInDual:
201  return "beforeStatusOfProblemInDual";
202  case ClpEventHandler::Event::startOfStatusOfProblemInDual:
203  return "startOfStatusOfProblemInDual";
204  case ClpEventHandler::Event::startOfIterationInDual:
205  return "startOfIterationInDual";
206  case ClpEventHandler::Event::updateDualsInDual:
207  return "updateDualsInDual";
208  case ClpEventHandler::Event::endOfCreateRim:
209  return "endOfCreateRim";
210  case ClpEventHandler::Event::slightlyInfeasible:
211  return "slightlyInfeasible";
212  case ClpEventHandler::Event::modifyMatrixInMiniPresolve:
213  return "modifyMatrixInMiniPresolve";
214  case ClpEventHandler::Event::moreMiniPresolve:
215  return "moreMiniPresolve";
216  case ClpEventHandler::Event::modifyMatrixInMiniPostsolve:
217  return "modifyMatrixInMiniPostsolve";
218  case ClpEventHandler::Event::startOfCrossover:
219  return "startOfCrossover";
220  case ClpEventHandler::Event::noTheta:
221  return "noTheta";
222  default:
223  return "unknown";
224  }
225  }
226 
227  class CasadiHandler : public CoinMessageHandler {
228  public:
229  virtual int print() ;
230  };
231 
232  int CasadiHandler::print() {
233  uout() << messageBuffer() << std::endl;
234  return 0;
235  }
236 
237  void ClpInterface::init(const Dict& opts) {
238  // Call the init method of the base class
239  Conic::init(opts);
240 
241  // Default options
242  casadi_assert(H_.nnz()==0, "Not an LP");
243 
244  // Read options
245  for (auto&& op : opts) {
246  if (op.first=="clp") {
247  opts_ = op.second;
248  }
249  }
250 
251  // Allocate work vectors
252  alloc_w(nx_, true); // g
253  alloc_w(nx_, true); // lbx
254  alloc_w(nx_, true); // ubx
255  alloc_w(na_, true); // lba
256  alloc_w(na_, true); // uba
257  alloc_w(nnz_in(CONIC_H), true); // H
258  alloc_w(nnz_in(CONIC_A), true); // A
259  }
260 
261  int ClpInterface::init_mem(void* mem) const {
262  if (Conic::init_mem(mem)) return 1;
263  if (!mem) return 1;
264  auto m = static_cast<ClpMemory*>(mem);
265 
266  m->add_stat("preprocessing");
267  m->add_stat("solver");
268  m->add_stat("postprocessing");
269 
270  m->colind.resize(A_.size2()+1);
271  m->row.resize(A_.nnz());
272 
273  return 0;
274  }
275 
277  solve(const double** arg, double** res, casadi_int* iw, double* w, void* mem) const {
278  auto m = static_cast<ClpMemory*>(mem);
279 
280  // Problem has not been solved at this point
281  m->return_status = -1;
282  m->secondary_return_status = -1;
283 
284  // Statistics
285  m->fstats.at("preprocessing").tic();
286 
287  // Get inputs
288  double* g=w; w += nx_;
289  casadi_copy(arg[CONIC_G], nx_, g);
290  double* lbx=w; w += nx_;
291  casadi_copy(arg[CONIC_LBX], nx_, lbx);
292  double* ubx=w; w += nx_;
293  casadi_copy(arg[CONIC_UBX], nx_, ubx);
294  double* lba=w; w += na_;
295  casadi_copy(arg[CONIC_LBA], na_, lba);
296  double* uba=w; w += na_;
297  casadi_copy(arg[CONIC_UBA], na_, uba);
298  double* H=w; w += nnz_in(CONIC_H);
299  casadi_copy(arg[CONIC_H], nnz_in(CONIC_H), H);
300  double* A=w; w += nnz_in(CONIC_A);
301  casadi_copy(arg[CONIC_A], nnz_in(CONIC_A), A);
302 
303  copy_vector(A_.colind(), m->colind);
304  copy_vector(A_.row(), m->row);
305 
306  // Create model
307  ClpSimplex model;
308 
309  bool initial_solve = false;
310  Dict initial_solve_options;
311 
312  // Read options
313  for (auto&& op : opts_) {
314  // Check for double params
315  auto it = param_map_double.find(op.first);
316  if (it!=param_map_double.end()) {
317  casadi_assert(model.setDblParam(it->second, op.second.to_double()),
318  "Error setting option '" + op.first + "'.");
319  continue;
320  }
321  // Check for integer params
322  auto it2 = param_map_int.find(op.first);
323  if (it2!=param_map_int.end()) {
324  casadi_assert(model.setIntParam(it2->second, op.second.to_int()),
325  "Error setting option '" + op.first + "'.");
326  } else if (op.first=="AutomaticScaling") {
327  model.setAutomaticScaling(op.second.to_bool());
328  } else if (op.first=="initial_solve") {
329  initial_solve = op.second.to_bool();
330  } else if (op.first=="initial_solve_options") {
331  initial_solve_options = op.second.to_dict();
332  } else {
333  casadi_error("Unknown option '" + op.first + "'.");
334  }
335  }
336 
337  model.loadProblem(A_.size2(), A_.size1(), get_ptr(m->colind), get_ptr(m->row), A,
338  lbx, ubx, g, lba, uba, nullptr);
339 
340  CasadiHandler ch;
341  model.passInMessageHandler(&ch);
342 
343  m->fstats.at("preprocessing").toc();
344  m->fstats.at("solver").tic();
345 
346  if (initial_solve) {
347  ClpSolve solvectl;
348  int numberPasses = -1;
349  ClpSolve::SolveType solveType = ClpSolve::automatic;
350  ClpSolve::PresolveType presolveType = ClpSolve::presolveOn;
351  std::vector< std::vector<int> > specialOptions;
352  std::vector< std::vector<int> > independentOptions;
353 
354  for (auto&& op : initial_solve_options) {
355  if (op.first=="SolveType") {
356  auto it = param_map_solvetype.find(op.second.to_string());
357  casadi_assert(it!=param_map_solvetype.end(),
358  "SolveType ' " + op.second.to_string() + "' not recognised.");
359  solveType = it->second;
360  } else if (op.first=="PresolveType") {
361  auto it = param_map_presolvetype.find(op.second);
362  casadi_assert(it!=param_map_presolvetype.end(),
363  "PresolveType ' " + op.second.to_string() + "' not recognised.");
364  presolveType = it->second;
365  } else if (op.first=="NumberPasses") {
366  numberPasses = op.second.to_int();
367  } else if (op.first=="SpecialOptions") {
368  specialOptions = to_int(op.second.to_int_vector_vector());
369  } else if (op.first=="IndependentOptions") {
370  independentOptions = to_int(op.second.to_int_vector_vector());
371  } else {
372  casadi_error("Option not recognised: '" + op.first + "'");
373  }
374  }
375  solvectl.setSolveType(solveType);
376  solvectl.setPresolveType(presolveType, numberPasses);
377 
378  // Read special options
379  for (auto const& v : specialOptions) {
380  if (v.size()==2) {
381  solvectl.setSpecialOption(v[0], v[1]);
382  } else if (v.size()==3) {
383  solvectl.setSpecialOption(v[0], v[1], v[2]);
384  } else {
385  casadi_error("SpecialOptions entries must be of length 2 or 3.");
386  }
387  }
388  // Read independent options
389  for (auto const& v : independentOptions) {
390  if (v.size()==2) {
391  solvectl.setIndependentOption(v[0], v[1]);
392  } else {
393  casadi_error("SpecialOptions entries must be of length 2.");
394  }
395  }
396  model.initialSolve(solvectl);
397  } else {
398  // Solve the problem using the primal simplex algorithm
399  model.primal();
400  }
401 
402  m->fstats.at("solver").toc();
403  m->fstats.at("postprocessing").tic();
404 
405  // Primal solution
406  double* x = model.primalColumnSolution();
407  casadi_copy(x, nx_, res[CONIC_X]);
408 
409  // Dual solution (x)
410  double* minus_lam_x = model.dualColumnSolution();
411  if (res[CONIC_LAM_X]) {
412  casadi_copy(minus_lam_x, nx_, res[CONIC_LAM_X]);
413  casadi_scal(nx_, -1., res[CONIC_LAM_X]);
414  }
415 
416  // Dual solution (A)
417  double* minus_lam_a = model.dualRowSolution();
418  if (res[CONIC_LAM_A]) {
419  casadi_copy(minus_lam_a, na_, res[CONIC_LAM_A]);
420  casadi_scal(na_, -1., res[CONIC_LAM_A]);
421  }
422 
423  // Optimal cost
424  double f = model.rawObjectiveValue();
425  if (res[CONIC_COST]) *res[CONIC_COST] = f;
426 
427  m->fstats.at("postprocessing").toc();
428  m->return_status = model.status();
429  m->d_qp.success = m->return_status==0;
430  m->secondary_return_status = model.secondaryStatus();
431  if (m->return_status==3) m->d_qp.unified_return_status = SOLVER_RET_LIMITED;
432 
433  if (verbose_) casadi_message("CLP return status: " + return_status_string(m->return_status));
434  if (verbose_) casadi_message(
435  "CLP secondary return status: " + return_secondary_status_string(m->secondary_return_status));
436 
437  return 0;
438  }
439 
441  clear_mem();
442  }
443 
445  }
446 
448  }
449 
450 
451  Dict ClpInterface::get_stats(void* mem) const {
452  Dict stats = Conic::get_stats(mem);
453  auto m = static_cast<ClpMemory*>(mem);
454  stats["return_status"] = return_status_string(m->return_status);
455  stats["secondary_return_status"] = return_secondary_status_string(m->secondary_return_status);
456  return stats;
457  }
458 
460  s.version("ClpInterface", 1);
461  s.unpack("ClpInterface::opts", opts_);
462  }
463 
466 
467  s.version("ClpInterface", 1);
468  s.pack("ClpInterface::opts", opts_);
469  }
470 
471 } // end namespace casadi
ClpInterface(const std::string &name, const std::map< std::string, Sparsity > &st)
Constructor using sparsity patterns.
int solve(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Solve the QP.
static Conic * creator(const std::string &name, const std::map< std::string, Sparsity > &st)
Create a new QP Solver.
~ClpInterface() override
Destructor.
void init(const Dict &opts) override
Initialize.
int init_mem(void *mem) const override
Initalize memory block.
static const std::string meta_doc
A documentation string.
Dict opts_
All CLP options.
static const Options options_
Options.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Dict get_stats(void *mem) const override
Get all statistics.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
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
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_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)
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
void CASADI_CONIC_CLP_EXPORT casadi_load_conic_clp()
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.
std::ostream & uout()
@ SOLVER_RET_LIMITED
std::string return_secondary_status_string(int status)
int CASADI_CONIC_CLP_EXPORT casadi_register_conic_clp(Conic::Plugin *plugin)
@ 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
~ClpMemory()
Destructor.
ClpMemory()
Constructor.
Options metadata for a class.
Definition: options.hpp:40
void add_stat(const std::string &s)