gurobi_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 
26 #include "gurobi_interface.hpp"
27 #include "casadi/core/casadi_misc.hpp"
28 #include "casadi/core/nlp_tools.hpp"
29 
30 namespace casadi {
31 
32  // Helper functions for cleaner C API usage
33  // Error handling macro
34  #define GUROBI_CALL_WARN(func, msg) do { \
35  int error = (func); \
36  if (error) { \
37  casadi_warning(msg ": Gurobi error " + std::to_string(error)); \
38  return; \
39  } \
40  } while (0)
41 
42  // Convert string sense to Gurobi sense
43  char sense_to_gurobi(const std::string& sense) {
44  if (sense == "<=") return GRB_LESS_EQUAL;
45  if (sense == ">=") return GRB_GREATER_EQUAL;
46  if (sense == "=") return GRB_EQUAL;
47  return GRB_LESS_EQUAL; // default
48  }
49 
50  // RAII wrapper for callback data access
51  class CallbackDataHelper {
52  private:
53  void* cbdata_;
54  int where_;
55 
56  public:
57  CallbackDataHelper(void* cbdata, int where) : cbdata_(cbdata), where_(where) {}
58 
59  bool getDouble(int what, double& value) {
60  int error = GRBcbget(cbdata_, where_, what, &value);
61  return error == 0;
62  }
63 
64  bool getInt(int what, int& value) {
65  int error = GRBcbget(cbdata_, where_, what, &value);
66  return error == 0;
67  }
68 
69  bool getSolution(std::vector<double>& solution) {
70  int error = GRBcbget(cbdata_, where_, GRB_CB_MIPSOL_SOL, solution.data());
71  return error == 0;
72  }
73 
74  bool addLazyConstraint(const std::vector<int>& ind,
75  const std::vector<double>& val,
76  char sense, double rhs) const {
77  if (where_ != GRB_CB_MIPSOL) {
78  casadi_warning("Can only add lazy constraints in MIPSOL context");
79  return 1;
80  }
81 
82  int error = GRBcblazy(cbdata_, static_cast<int>(ind.size()),
83  ind.data(), val.data(), sense, rhs);
84  if (error != 0) {
85  casadi_warning("GRBcblazy failed with code: " + std::to_string(error));
86  }
87  return error == 0;
88  }
89  };
90 
91  // Static C callback function
92  static int gurobi_callback_function(GRBmodel *model, void *cbdata, int where, void *usrdata) {
93  try {
94  if (where == GRB_CB_MIPSOL && usrdata) {
95  GurobiMemory* mem = static_cast<GurobiMemory*>(usrdata);
96  mem->interface->handle_lazy_constraints_callback(mem, model, cbdata, where);
97  }
98  } catch (const std::exception& e) {
99  // Don't let exceptions escape to C code
100  casadi_warning("Exception in Gurobi callback: " + std::string(e.what()));
101  }
102  return 0; // Continue optimization
103  }
104 
105  extern "C"
106  int CASADI_CONIC_GUROBI_EXPORT
107  casadi_register_conic_gurobi(Conic::Plugin* plugin) {
108  plugin->creator = GurobiInterface::creator;
109  plugin->name = "gurobi";
110  plugin->doc = GurobiInterface::meta_doc.c_str();
111  plugin->version = CASADI_VERSION;
112  plugin->options = &GurobiInterface::options_;
113  plugin->deserialize = &GurobiInterface::deserialize;
114  #ifdef GUROBI_ADAPTOR
115  char buffer[400];
116  int ret = gurobi_adaptor_load(buffer, sizeof(buffer));
117  if (ret!=0) {
118  casadi_warning("Failed to load Gurobi adaptor: " + std::string(buffer) + ".");
119  return 1;
120  }
121  #endif
122  return 0;
123  }
124 
125  extern "C"
126  void CASADI_CONIC_GUROBI_EXPORT casadi_load_conic_gurobi() {
128  }
129 
130  GurobiInterface::GurobiInterface(const std::string& name,
131  const std::map<std::string, Sparsity>& st)
132  : Conic(name, st),
133  lazy_constraints_callback_(Function()) {
134 
135  }
136 
138  clear_mem();
139  }
140 
142  = {{&Conic::options_},
143  {{"vtype",
145  "Type of variables: [CONTINUOUS|binary|integer|semicont|semiint]"}},
146  {"gurobi",
147  {OT_DICT,
148  "Options to be passed to gurobi."}},
149  {"sos_groups",
151  "Definition of SOS groups by indices."}},
152  {"sos_weights",
154  "Weights corresponding to SOS entries."}},
155  {"sos_types",
156  {OT_INTVECTOR,
157  "Specify 1 or 2 for each SOS group."}},
158  {"lazy_constraints_callback",
159  {OT_FUNCTION,
160  "User callback for adding LazyConstraints at MIPSOL. "
161  "Input: dict with solution data. Output: dict with lazy constraints."}},
162  }
163  };
164 
165  void GurobiInterface::init(const Dict& opts) {
166  // Initialize the base classes
167  Conic::init(opts);
168 
169  // Default options
170  std::vector<std::string> vtype;
171 
172  std::vector< std::vector<casadi_int> > sos_groups;
173  std::vector< std::vector<double> > sos_weights;
174  std::vector<casadi_int> sos_types;
175 
176  // Read options
177  for (auto&& op : opts) {
178  if (op.first=="vtype") {
179  vtype = op.second;
180  } else if (op.first=="gurobi") {
181  opts_ = op.second;
182  } else if (op.first=="sos_groups") {
183  sos_groups = op.second.to_int_vector_vector();
184  } else if (op.first=="sos_weights") {
185  sos_weights = op.second.to_double_vector_vector();
186  } else if (op.first=="sos_types") {
187  sos_types = op.second.to_int_vector();
188  } else if (op.first == "lazy_constraints_callback") {
189  try {
190  // Attempt to convert to function
191  lazy_constraints_callback_ = op.second;
192  casadi_message("Successfully obtained callback function");
193 
194  // Verify signature
195  if (lazy_constraints_callback_.n_in() != 5 ||
197  casadi_error("Callback function has wrong signature. "
198  "Expected 5 inputs, 3 outputs");
199  }
200  } catch (const std::exception& e) {
201  casadi_error("Failed to get callback function: " + std::string(e.what()));
202  }
203  }
204  }
205  // Final validation
207  casadi_message("Callback setup complete");
208  }
209 
210  // Validaty SOS constraints
211  check_sos(nx_, sos_groups, sos_weights, sos_types);
212 
213  // Populate SOS structures
215  if (!sos_weights.empty())
216  flatten_nested_vector(sos_weights, sos_weights_);
217 
218  sos_types_ = to_int(sos_types);
219 
220  // Variable types
221  if (!vtype.empty()) {
222  casadi_assert(vtype.size()==nx_, "Option 'vtype' has wrong length");
223  vtype_.resize(nx_);
224  for (casadi_int i=0; i<nx_; ++i) {
225  if (vtype[i]=="continuous") {
226  vtype_[i] = GRB_CONTINUOUS;
227  } else if (vtype[i]=="binary") {
228  vtype_[i] = GRB_BINARY;
229  } else if (vtype[i]=="integer") {
230  vtype_[i] = GRB_INTEGER;
231  } else if (vtype[i]=="semicont") {
232  vtype_[i] = GRB_SEMICONT;
233  } else if (vtype[i]=="semiint") {
234  vtype_[i] = GRB_SEMIINT;
235  } else {
236  casadi_error("No such variable type: " + vtype[i]);
237  }
238  }
239  }
240 
241  // Initialize SDP to SOCP memory
243 
244  // Temporary memory
245  alloc_w(sdp_to_socp_mem_.indval_size, true); // val
246  alloc_iw(sdp_to_socp_mem_.indval_size, true); // ind
247  alloc_iw(nx_, true); // ind2
248  alloc_iw(nx_, true); // vtypes
249  }
250 
251  int GurobiInterface::init_mem(void* mem) const {
252  if (Conic::init_mem(mem)) return 1;
253  auto m = static_cast<GurobiMemory*>(mem);
254 
255  // Load environment
256  casadi_int flag;
257 
258  auto output_flag = opts_.find("OutputFlag");
259  auto log_to_console = opts_.find("LogToConsole");
260 
261  if (output_flag != opts_.end() && output_flag->second.as_int() == 0 &&
262  log_to_console != opts_.end() && log_to_console->second.as_int() == 0) {
263  // casadi_message("Suppressing all Gurobi outputs since "
264  // "OutputFlag and LogToConsole are set to zero.")
265  flag = GRBemptyenv(&m->env);
266  casadi_assert(!flag && m->env,
267  "Failed to create empty GUROBI environment. Flag: " + str(flag));
268 
269  flag = GRBsetintparam(m->env, "OutputFlag", 0);
270  casadi_assert(!flag, GRBgeterrormsg(m->env));
271 
272  flag = GRBsetintparam(m->env, "LogToConsole", 0);
273  casadi_assert(!flag, GRBgeterrormsg(m->env));
274 
275  flag = GRBstartenv(m->env);
276  casadi_assert(!flag, GRBgeterrormsg(m->env));
277  } else {
278  flag = GRBloadenv(&m->env, nullptr); // Standard behavior - loading Gurobi options in solve
279  casadi_assert(!flag && m->env,
280  "Failed to create GUROBI environment. Flag: " + str(flag)
281  + ":" + GRBgeterrormsg(m->env));
282  }
283 
284  m->pool_sol_nr = 0;
285 
286  m->sos_weights = sos_weights_;
287  m->sos_beg = sos_beg_;
288  m->sos_ind = sos_ind_;
289  m->sos_types = sos_types_;
290 
291  LazyCallbackMemory* cb = &m->lazy_cb_mem;
292  cb->nx = nx_;
293 
295  cb->sz_arg = 0;
296  cb->sz_res = 0;
297  cb->sz_iw = 0;
298  cb->sz_w = 0;
299  } else {
304  }
305 
306  cb->x_vals.resize(nx_);
307  cb->obj_val = 0;
308  cb->obj_best = 0;
309  cb->obj_bound = 0;
310  cb->sol_count = 0;
311  cb->input_data.resize(nx_ + 4); // x + obj info
312 
313  cb->flag = 0.0;
314  cb->a_vec.resize(nx_);
315  cb->b_val = 0.0;
316 
317  cb->iw.resize(cb->sz_iw);
318  cb->w.resize(cb->sz_w);
319  cb->arg.resize(cb->sz_arg);
320  cb->res.resize(cb->sz_res);
321 
322  m->interface = this;
323 
324  m->add_stat("preprocessing");
325  m->add_stat("solver");
326  m->add_stat("postprocessing");
327  return 0;
328  }
329 
330  inline const char* return_status_string(casadi_int status) {
331  switch (status) {
332  case GRB_LOADED:
333  return "LOADED";
334  case GRB_OPTIMAL:
335  return "OPTIMAL";
336  case GRB_INFEASIBLE:
337  return "INFEASIBLE";
338  case GRB_INF_OR_UNBD:
339  return "INF_OR_UNBD";
340  case GRB_UNBOUNDED:
341  return "UNBOUNDED";
342  case GRB_CUTOFF:
343  return "CUTOFF";
344  case GRB_ITERATION_LIMIT:
345  return "ITERATION_LIMIT";
346  case GRB_NODE_LIMIT:
347  return "NODE_LIMIT";
348  case GRB_TIME_LIMIT:
349  return "TIME_LIMIT";
350  case GRB_SOLUTION_LIMIT:
351  return "SOLUTION_LIMIT";
352  case GRB_INTERRUPTED:
353  return "INTERRUPTED";
354  case GRB_NUMERIC:
355  return "NUMERIC";
356  case GRB_SUBOPTIMAL:
357  return "SUBOPTIMAL";
358  case GRB_INPROGRESS:
359  return "INPROGRESS";
360  }
361  return "Unknown";
362  }
363 
365  solve(const double** arg, double** res, casadi_int* iw, double* w, void* mem) const {
366  auto m = static_cast<GurobiMemory*>(mem);
367  const SDPToSOCPMem& sm = sdp_to_socp_mem_;
368 
369  // Statistics
370  m->fstats.at("preprocessing").tic();
371 
372  // Problem has not been solved at this point
373  m->return_status = -1;
374 
375  if (inputs_check_) {
376  check_inputs(arg[CONIC_LBX], arg[CONIC_UBX], arg[CONIC_LBA], arg[CONIC_UBA]);
377  }
378 
379  // Inputs
380  const double *h=arg[CONIC_H],
381  *g=arg[CONIC_G],
382  *a=arg[CONIC_A],
383  *lba=arg[CONIC_LBA],
384  *uba=arg[CONIC_UBA],
385  *lbx=arg[CONIC_LBX],
386  *ubx=arg[CONIC_UBX],
387  *p=arg[CONIC_P],
388  *q=arg[CONIC_Q],
389  *x0=arg[CONIC_X0];
390  //*lam_x0=arg[CONIC_LAM_X0];
391 
392  // Outputs
393  double *x=res[CONIC_X],
394  *cost=res[CONIC_COST],
395  *lam_a=res[CONIC_LAM_A],
396  *lam_x=res[CONIC_LAM_X];
397 
398  // Temporary memory
399  double *val=w; w+=sm.indval_size;
400  int *ind=reinterpret_cast<int*>(iw); iw+=sm.indval_size;
401  int *ind2=reinterpret_cast<int*>(iw); iw+=nx_;
402  char *vtypes=reinterpret_cast<char*>(iw); iw+=nx_;
403 
404  // Create an empty model
405  GRBmodel *model = nullptr;
406  try {
407  casadi_int flag = GRBnewmodel(m->env, &model, name_.c_str(), 0,
408  nullptr, nullptr, nullptr, nullptr, nullptr);
409  casadi_assert(!flag, GRBgeterrormsg(m->env));
410 
411  // Add variables
412  for (casadi_int i=0; i<nx_; ++i) {
413  // Get bounds
414  double lb = lbx ? lbx[i] : 0., ub = ubx ? ubx[i] : 0.;
415  if (isinf(lb)) lb = -GRB_INFINITY;
416  if (isinf(ub)) ub = GRB_INFINITY;
417 
418  // Get variable type
419  char vtype;
420  if (!vtype_.empty()) {
421  // Explicitly set 'vtype' takes precedence
422  vtype = vtype_.at(i);
423  } else if (!discrete_.empty() && discrete_.at(i)) {
424  // Variable marked as discrete (integer or binary)
425  vtype = lb==0 && ub==1 ? GRB_BINARY : GRB_INTEGER;
426  } else {
427  // Continuous variable
428  vtype = GRB_CONTINUOUS;
429  }
430  vtypes[i] = vtype;
431 
432  // Pass to model
433  flag = GRBaddvar(model, 0, nullptr, nullptr, g ? g[i] : 0., lb, ub, vtype, nullptr);
434  casadi_assert(!flag, GRBgeterrormsg(m->env));
435  }
436 
437  GRBupdatemodel(model);
438  for (casadi_int i=0; i<nx_; ++i) {
439  // If it is a discrete variable, we can pass the start guess
440  if (vtypes[i] != GRB_CONTINUOUS) {
441  flag = GRBsetdblattrelement(model, "Start", i, x0[i]);
442  casadi_assert(!flag, GRBgeterrormsg(m->env));
443  }
444  }
445 
446 
447  /* Treat SOCP constraints */
448 
449  // Add helper variables for SOCP
450  for (casadi_int i=0;i<sm.r.size()-1;++i) {
451  for (casadi_int k=0;k<sm.r[i+1]-sm.r[i]-1;++k) {
452  flag = GRBaddvar(model, 0, nullptr, nullptr, 0, -GRB_INFINITY, GRB_INFINITY,
453  GRB_CONTINUOUS, nullptr);
454  casadi_assert(!flag, GRBgeterrormsg(m->env));
455  }
456  flag = GRBaddvar(model, 0, nullptr, nullptr, 0, 0, GRB_INFINITY, GRB_CONTINUOUS, nullptr);
457  casadi_assert(!flag, GRBgeterrormsg(m->env));
458  }
459 
460  flag = GRBupdatemodel(model);
461  casadi_assert(!flag, GRBgeterrormsg(m->env));
462 
463  // Add quadratic terms
464  const casadi_int *H_colind=H_.colind(), *H_row=H_.row();
465  for (int i=0; i<nx_; ++i) {
466 
467  // Quadratic term nonzero indices
468  casadi_int numqnz = H_colind[1]-H_colind[0];
469  for (casadi_int k=0;k<numqnz;++k) ind[k]=H_row[k];
470  H_colind++;
471  H_row += numqnz;
472 
473  // Corresponding column
474  casadi_fill(ind2, numqnz, i);
475 
476  // Quadratic term nonzeros
477  if (h) {
478  casadi_copy(h, numqnz, val);
479  casadi_scal(numqnz, 0.5, val);
480  h += numqnz;
481  } else {
482  casadi_clear(val, numqnz);
483  }
484 
485  // Pass to model
486  flag = GRBaddqpterms(model, numqnz, ind, ind2, val);
487  casadi_assert(!flag, GRBgeterrormsg(m->env));
488  }
489 
490  std::vector<char> constraint_type(na_); // For each a entry: 0 absent, 1 linear
491  casadi_int npi = 0;
492 
493  // Add constraints
494  const casadi_int *AT_colind=sm.AT.colind(), *AT_row=sm.AT.row();
495  for (casadi_int i=0; i<na_; ++i) {
496  // Get bounds
497  double lb = lba ? lba[i] : 0., ub = uba ? uba[i] : 0.;
498 
499  casadi_int numnz = 0;
500  // Loop over rows
501  for (casadi_int k=AT_colind[i]; k<AT_colind[i+1]; ++k) {
502  casadi_int j = AT_row[k];
503 
504  ind[numnz] = j;
505  val[numnz] = a ? a[sm.A_mapping[k]] : 0;
506 
507  numnz++;
508  }
509 
510  constraint_type[i] = 1;
511  // Pass to model
512  if (isinf(lb)) {
513  if (isinf(ub)) {
514  constraint_type[i] = 0;
515  // Neither upper or lower bounds, skip
516  } else {
517  // Only upper bound
518  flag = GRBaddconstr(model, numnz, ind, val, GRB_LESS_EQUAL, ub, nullptr);
519  casadi_assert(!flag, GRBgeterrormsg(m->env));
520  npi++;
521  }
522  } else {
523  if (isinf(ub)) {
524  // Only lower bound
525  flag = GRBaddconstr(model, numnz, ind, val, GRB_GREATER_EQUAL, lb, nullptr);
526  casadi_assert(!flag, GRBgeterrormsg(m->env));
527  npi++;
528  } else if (lb==ub) {
529  // Upper and lower bounds equal
530  flag = GRBaddconstr(model, numnz, ind, val, GRB_EQUAL, lb, nullptr);
531  casadi_assert(!flag, GRBgeterrormsg(m->env));
532  npi++;
533  } else {
534  // Both upper and lower bounds
535  flag = GRBaddrangeconstr(model, numnz, ind, val, lb, ub, nullptr);
536  casadi_assert(!flag, GRBgeterrormsg(m->env));
537  npi++;
538  }
539  }
540  }
541  std::vector<double> pi(npi);
542 
543  // Add SOS constraints when applicable
544  if (!m->sos_ind.empty()) {
545  flag = GRBaddsos(model, m->sos_beg.size()-1, m->sos_ind.size(),
546  get_ptr(m->sos_types), get_ptr(m->sos_beg), get_ptr(m->sos_ind),
547  get_ptr(m->sos_weights));
548  casadi_assert(!flag, GRBgeterrormsg(m->env));
549  }
550 
551  // SOCP helper constraints
552  const Sparsity& sp = sm.map_Q.sparsity();
553  const casadi_int* colind = sp.colind();
554  const casadi_int* row = sp.row();
555  const casadi_int* data = sm.map_Q.ptr();
556 
557  // Loop over columns
558  for (casadi_int i=0; i<sp.size2(); ++i) {
559 
560  casadi_int numnz = 0;
561  // Loop over rows
562  for (casadi_int k=colind[i]; k<colind[i+1]; ++k) {
563  casadi_int j = row[k];
564 
565  ind[numnz] = j;
566  val[numnz] = (q && j<nx_) ? q[data[k]] : -1;
567 
568  numnz++;
569  }
570 
571  // Get bound
572  double bound = sm.map_P[i]==-1 ? 0 : -p[sm.map_P[i]];
573 
574  flag = GRBaddconstr(model, numnz, ind, val, GRB_EQUAL, bound, nullptr);
575  casadi_assert(!flag, GRBgeterrormsg(m->env));
576  }
577 
578  // Loop over blocks
579  for (casadi_int i=0; i<sm.r.size()-1; ++i) {
580  casadi_int block_size = sm.r[i+1]-sm.r[i];
581 
582  // Indicate x'x - y^2 <= 0
583  for (casadi_int j=0;j<block_size;++j) {
584  ind[j] = nx_ + sm.r[i] + j;
585  val[j] = j<block_size-1 ? 1 : -1;
586  }
587 
588  flag = GRBaddqconstr(model, 0, nullptr, nullptr,
589  block_size, ind, ind, val,
590  GRB_LESS_EQUAL, 0, nullptr);
591  casadi_assert(!flag, GRBgeterrormsg(m->env));
592  }
593 
594  flag = 0;
595  for (auto && op : opts_) {
596  int ret = GRBgetparamtype(m->env, op.first.c_str());
597  switch (ret) {
598  case -1:
599  casadi_error("Parameter '" + op.first + "' unknown to Gurobi.");
600  case 1:
601  {
602  flag = GRBsetintparam(GRBgetenv(model), op.first.c_str(), op.second);
603  break;
604  }
605  case 2:
606  flag = GRBsetdblparam(GRBgetenv(model), op.first.c_str(), op.second);
607  break;
608  case 3:
609  {
610  std::string s = op.second;
611  flag = GRBsetstrparam(GRBgetenv(model), op.first.c_str(), s.c_str());
612  break;
613  }
614  default:
615  casadi_error("Not implememented : " + str(ret));
616  }
617  casadi_assert(!flag, GRBgeterrormsg(m->env));
618  }
619 
620  // Enable lazy constraints and callback if needed
622  casadi_message("mipsol callback is not a null pointer")
623  // Enable lazy constraints
624  int error = GRBsetintparam(GRBgetenv(model), "LazyConstraints", 1);
625  if (error) {
626  casadi_warning("Failed to enable LazyConstraints parameter");
627  return 1;
628  }
629  // Set the callback function
630  error = GRBsetcallbackfunc(model, gurobi_callback_function, mem);
631  if (error) {
632  casadi_warning("Failed to set callback function");
633  return 1;
634  }
635  }
636 
637  m->fstats.at("preprocessing").toc();
638  m->fstats.at("solver").tic();
639 
640  // Solve the optimization problem
641  flag = GRBoptimize(model);
642  casadi_assert(!flag, GRBgeterrormsg(m->env));
643 
644  m->fstats.at("solver").toc();
645  m->fstats.at("postprocessing").tic();
646 
647  int optimstatus;
648  flag = GRBgetintattr(model, "Status", &optimstatus);
649  casadi_assert(!flag, GRBgeterrormsg(m->env));
650 
651  if (verbose_) uout() << "return status: " << return_status_string(optimstatus) <<
652  " (" << optimstatus <<")" << std::endl;
653 
654  m->return_status = optimstatus;
655  m->d_qp.success = optimstatus==GRB_OPTIMAL;
656  if (optimstatus==GRB_ITERATION_LIMIT || optimstatus==GRB_TIME_LIMIT
657  || optimstatus==GRB_NODE_LIMIT || optimstatus==GRB_SOLUTION_LIMIT)
658  m->d_qp.unified_return_status = SOLVER_RET_LIMITED;
659 
660  // Get the objective value, if requested
661  if (cost) {
662  flag = GRBgetdblattr(model, "ObjVal", cost);
663  if (flag) cost[0] = casadi::nan;
664  }
665 
666  // Get the optimal solution, if requested
667  if (x) {
668  flag = GRBgetdblattrarray(model, "X", 0, nx_, x);
669  if (flag) std::fill_n(x, nx_, casadi::nan);
670  }
671  if (lam_x) {
672  flag = GRBgetdblattrarray(model, "RC", 0, nx_, lam_x);
673  if (!flag) casadi_scal(nx_, -1.0, lam_x);
674  if (flag) std::fill_n(lam_x, nx_, casadi::nan);
675  }
676  if (lam_a) {
677  flag = GRBgetdblattrarray(model, "Pi", 0, npi, get_ptr(pi));
678  if (flag) {
679  std::fill_n(lam_a, na_, casadi::nan);
680  } else {
681  const double * p = get_ptr(pi);
682  for (casadi_int i=0;i<na_;++i) {
683  if (constraint_type[i]==0) {
684  lam_a[i] = 0;
685  } else if (constraint_type[i]==1) {
686  lam_a[i] = -(*p++);
687  }
688  }
689  }
690  }
691 
692  // Get solutions from solution pool
693  flag = GRBgetintattr(model, GRB_INT_ATTR_SOLCOUNT, &(m->pool_sol_nr));
694  if (!flag && m->pool_sol_nr > 0) {
695  m->pool_obj_vals = std::vector<double>(m->pool_sol_nr, casadi::nan);
696  for (int idx = 0; idx < m->pool_sol_nr; ++idx) {
697  std::vector<double> x_pool(nx_);
698 
699  flag = GRBsetintparam(GRBgetenv(model), GRB_INT_PAR_SOLUTIONNUMBER, idx);
700  if (!flag) flag = GRBgetdblattr(model, GRB_DBL_ATTR_POOLOBJVAL, &(m->pool_obj_vals[idx]));
701  if (!flag) flag = GRBgetdblattrarray(model, GRB_DBL_ATTR_XN, 0, nx_, get_ptr(x_pool));
702 
703  if (flag) {
704  m->pool_obj_vals[idx] = casadi::nan;
705  std::fill(x_pool.begin(), x_pool.end(), casadi::nan);
706  }
707 
708  m->pool_solutions.push_back(x_pool);
709  }
710  }
711 
713  // Clear callback to avoid dangling pointer
714  GRBsetcallbackfunc(model, nullptr, nullptr);
715  }
716 
717  // Free memory
718  GRBfreemodel(model);
719  m->fstats.at("postprocessing").toc();
720 
721  } catch (...) {
722  // Free memory
723  if (model) GRBfreemodel(model);
724  throw;
725  }
726 
727  return 0;
728  }
729 
730  Dict GurobiInterface::get_stats(void* mem) const {
731  Dict stats = Conic::get_stats(mem);
732  auto m = static_cast<GurobiMemory*>(mem);
733  stats["return_status"] = return_status_string(m->return_status);
734  stats["pool_sol_nr"] = m->pool_sol_nr;
735  stats["pool_obj_val"] = m->pool_obj_vals;
736  stats["pool_solutions"] = m->pool_solutions;
737  return stats;
738  }
739 
741  this->env = nullptr;
742  }
743 
745  if (this->env) GRBfreeenv(this->env);
746  }
747 
749  int version = s.version("GurobiInterface", 1, 2);
750  if (version>=2) {
751  s.unpack("GurobiInterface::lazy_constraints_callback", lazy_constraints_callback_);
752  }
753  s.unpack("GurobiInterface::vtype", vtype_);
754  s.unpack("GurobiInterface::opts", opts_);
755  s.unpack("GurobiInterface::sos_weights", sos_weights_);
756  s.unpack("GurobiInterface::sos_beg", sos_beg_);
757  s.unpack("GurobiInterface::sos_ind", sos_ind_);
758  s.unpack("GurobiInterface::sos_types", sos_types_);
760  }
761 
764  s.version("GurobiInterface", 2);
765  s.pack("GurobiInterface::lazy_constraints_callback", lazy_constraints_callback_);
766  s.pack("GurobiInterface::vtype", vtype_);
767  s.pack("GurobiInterface::opts", opts_);
768  s.pack("GurobiInterface::sos_weights", sos_weights_);
769  s.pack("GurobiInterface::sos_beg", sos_beg_);
770  s.pack("GurobiInterface::sos_ind", sos_ind_);
771  s.pack("GurobiInterface::sos_types", sos_types_);
773  }
774 
776  GRBmodel *model, void *cbdata, int where) const {
777  try {
778  // Defensive checks
780  casadi_warning("Lazy callback triggered but memory not initialized");
781  return;
782  }
783 
784  auto& cb = mem->lazy_cb_mem;
785  CallbackDataHelper helper(cbdata, where);
786 
787  // 1. Get solution (reuse buffer)
788  if (!helper.getSolution(cb.x_vals)) {
789  casadi_warning("Failed to get solution in MIPSOL callback");
790  return;
791  }
792 
793  // 2. Get scalar info (reuse buffer)
794  if (!helper.getDouble(GRB_CB_MIPSOL_OBJ, cb.obj_val) ||
795  !helper.getDouble(GRB_CB_MIPSOL_OBJBST, cb.obj_best) ||
796  !helper.getDouble(GRB_CB_MIPSOL_OBJBND, cb.obj_bound) ||
797  !helper.getDouble(GRB_CB_MIPSOL_SOLCNT, cb.sol_count)) {
798  casadi_warning("Failed to get callback information");
799  return;
800  }
801 
802  // 3. Fill input_data
803  std::copy(cb.x_vals.begin(), cb.x_vals.end(), cb.input_data.begin());
804  cb.input_data[cb.nx + 0] = cb.obj_val;
805  cb.input_data[cb.nx + 1] = cb.obj_best;
806  cb.input_data[cb.nx + 2] = cb.obj_bound;
807  cb.input_data[cb.nx + 3] = cb.sol_count;
808 
809  // 4. Reset outputs
810  cb.flag = 0.0;
811  std::fill(cb.a_vec.begin(), cb.a_vec.end(), 0.0);
812  cb.b_val = 0.0;
813 
814  // 5. Setup CasADi arguments (no allocation)
815  size_t offset = 0;
816  for (casadi_int i = 0; i < cb.sz_arg; ++i) {
817  cb.arg[i] = &cb.input_data[offset];
818  offset += (i == 0 ? cb.nx : 1);
819  }
820 
821  if (cb.sz_res >= 1) cb.res[0] = &cb.flag;
822  if (cb.sz_res >= 2) cb.res[1] = cb.a_vec.data();
823  if (cb.sz_res >= 3) cb.res[2] = &cb.b_val;
824 
825  // 6. Call CasADi callback
826  int ret = lazy_constraints_callback_(
827  cb.arg.data(), cb.res.data(),
828  cb.iw.data(), cb.w.data(), 0);
829 
830  if (ret != 0) {
831  casadi_warning("Lazy callback returned error " + std::to_string(ret));
832  return;
833  }
834 
835  // 7. Add lazy constraint if requested
836  if (cb.flag > 0.5) {
837  std::vector<int> cind;
838  std::vector<double> cval;
839 
840  for (casadi_int j = 0; j < nx_; ++j) {
841  if (std::abs(cb.a_vec[j]) > 1e-12) {
842  cind.push_back(static_cast<int>(j));
843  cval.push_back(cb.a_vec[j]);
844  }
845  }
846  if (!cind.empty()) {
847  char gurobi_sense = sense_to_gurobi("<=");
848  if (!helper.addLazyConstraint(cind, cval, gurobi_sense, cb.b_val)) {
849  casadi_warning("Failed to add lazy constraint");
850  }
851  }
852  }
853 
854  } catch (const std::exception& e) {
855  casadi_warning("Error in handle_lazy_constraints_callback: "
856  + std::string(e.what()));
857  }
858 }
859 
860 } // namespace casadi
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:173
int init_mem(void *mem) const override
Initalize memory block.
Definition: conic.cpp:466
casadi_int na_
The number of constraints (counting both equality and inequality) == A.size1()
Definition: conic_impl.hpp:176
virtual void check_inputs(const double *lbx, const double *ubx, const double *lba, const double *uba) const
Check if the numerical values of the supplied bounds make sense.
Definition: conic.cpp:503
Sparsity H_
Problem structure.
Definition: conic_impl.hpp:170
void init(const Dict &opts) override
Initialize.
Definition: conic.cpp:415
void deserialize(DeserializingStream &s, SDPToSOCPMem &m)
Definition: conic.cpp:744
std::vector< bool > discrete_
Options.
Definition: conic_impl.hpp:164
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: conic.cpp:753
Dict get_stats(void *mem) const override
Get all statistics.
Definition: conic.cpp:726
void sdp_to_socp_init(SDPToSOCPMem &mem) const
SDP to SOCP conversion initialization.
Definition: conic.cpp:595
void serialize(SerializingStream &s, const SDPToSOCPMem &m) const
Definition: conic.cpp:736
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
void alloc_iw(size_t sz_iw, bool persistent=false)
Ensure required length of iw field.
bool inputs_check_
Errors are thrown if numerical values of inputs look bad.
void alloc_w(size_t sz_w, bool persistent=false)
Ensure required length of w field.
Function object.
Definition: function.hpp:60
size_t sz_res() const
Get required length of res field.
Definition: function.cpp:1237
size_t sz_iw() const
Get required length of iw field.
Definition: function.cpp:1239
casadi_int n_out() const
Get the number of function outputs.
Definition: function.cpp:975
casadi_int n_in() const
Get the number of function inputs.
Definition: function.cpp:971
size_t sz_w() const
Get required length of w field.
Definition: function.cpp:1241
size_t sz_arg() const
Get required length of arg field.
Definition: function.cpp:1235
bool is_null() const
Is a null pointer?
std::vector< int > sos_ind_
Dict get_stats(void *mem) const override
Get all statistics.
GurobiInterface(const std::string &name, const std::map< std::string, Sparsity > &st)
Create a new Solver.
static Conic * creator(const std::string &name, const std::map< std::string, Sparsity > &st)
Create a new QP Solver.
std::vector< int > sos_types_
void handle_lazy_constraints_callback(GurobiMemory *mem, GRBmodel *model, void *cbdata, int where) const
Handle Lazy Constraints callback events (C API version)
~GurobiInterface() override
Destructor.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
std::vector< int > sos_beg_
std::vector< char > vtype_
Function lazy_constraints_callback_
User-provided callback function for MIPSOL events.
void init(const Dict &opts) override
Initialize.
Dict opts_
Gurobi options.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
SDPToSOCPMem sdp_to_socp_mem_
SDP to SOCP conversion memory.
int solve(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Solve the QP.
static const std::string meta_doc
A documentation string.
static const Options options_
Options.
int init_mem(void *mem) const override
Initalize memory block.
std::vector< double > sos_weights_
const Sparsity & sparsity() const
Const access the sparsity - reference to data member.
Scalar * ptr()
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.
General sparsity class.
Definition: sparsity.hpp:106
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
int CASADI_CONIC_GUROBI_EXPORT casadi_register_conic_gurobi(Conic::Plugin *plugin)
@ 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_Q
The matrix Q: sparse symmetric, (np^2 x n)
Definition: conic.hpp:193
@ 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
@ CONIC_P
The matrix P: sparse symmetric, (np x np)
Definition: conic.hpp:195
int to_int(casadi_int rhs)
Definition: casadi_misc.cpp:60
void flatten_nested_vector(const std::vector< std::vector< T > > &nested, std::vector< S > &flat)
Flatten a nested std::vector tot a single flattened vector.
void casadi_copy(const T1 *x, casadi_int n, T1 *y)
COPY: y <-x.
void casadi_fill(T1 *x, casadi_int n, T1 alpha)
FILL: x <- alpha.
const char * return_status_string(Bonmin::TMINLP::SolverReturn status)
static int gurobi_callback_function(GRBmodel *model, void *cbdata, int where, void *usrdata)
@ OT_STRINGVECTOR
@ 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.
const double nan
Not a number.
Definition: calculus.hpp:53
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.
void casadi_clear(T1 *x, casadi_int n)
CLEAR: x <- 0.
char sense_to_gurobi(const std::string &sense)
std::ostream & uout()
void CASADI_CONIC_GUROBI_EXPORT casadi_load_conic_gurobi()
@ SOLVER_RET_LIMITED
const double pi
Define pi.
Definition: calculus.hpp:46
@ 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
SDP to SOCP conversion memory.
Definition: conic_impl.hpp:182
std::vector< casadi_int > r
Definition: conic_impl.hpp:184
std::vector< casadi_int > A_mapping
Definition: conic_impl.hpp:188
std::vector< casadi_int > map_P
Definition: conic_impl.hpp:194
const GurobiInterface * interface
LazyCallbackMemory lazy_cb_mem
std::vector< double > input_data
std::vector< double > a_vec
std::vector< double > w
std::vector< const double * > arg
std::vector< casadi_int > iw
std::vector< double > x_vals
std::vector< double * > res
Options metadata for a class.
Definition: options.hpp:40