this repo has no description
1// * -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Gleb Belov <gleb.belov@monash.edu>
6 */
7
8/* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12#ifdef _MSC_VER
13#define _CRT_SECURE_NO_WARNINGS
14#endif
15
16#include <minizinc/config.hh>
17#include <minizinc/exception.hh>
18#include <minizinc/file_utils.hh>
19#include <minizinc/utils_savestream.hh>
20
21#include <cmath>
22#include <cstring>
23#include <ctime>
24#include <fstream>
25#include <iomanip>
26#include <iostream>
27#include <sstream>
28#include <stdexcept>
29#include <string>
30
31#ifdef GUROBI_PLUGIN
32#ifdef HAS_DLFCN_H
33#include <dlfcn.h>
34#elif defined HAS_WINDOWS_H
35#include <Windows.h>
36#endif
37#endif
38
39using namespace std;
40
41#include <minizinc/solvers/MIP/MIP_gurobi_wrap.hh>
42#include <minizinc/utils.hh>
43
44string MIP_gurobi_wrapper::getDescription(MiniZinc::SolverInstanceBase::Options* opt) {
45 ostringstream oss;
46 oss << "MIP wrapper for Gurobi library " << getVersion();
47 oss << ". Compiled " __DATE__ " " __TIME__;
48 return oss.str();
49}
50
51string MIP_gurobi_wrapper::getVersion(MiniZinc::SolverInstanceBase::Options* opt) {
52 ostringstream oss;
53 MIP_gurobi_wrapper mgw(nullptr); // to avoid opening the env
54 try {
55 mgw.checkDLL();
56 int major, minor, technical;
57 mgw.dll_GRBversion(&major, &minor, &technical);
58 oss << major << '.' << minor << '.' << technical;
59 return oss.str();
60 } catch (MiniZinc::InternalError&) {
61 return "<unknown version>";
62 }
63}
64
65string MIP_gurobi_wrapper::needDllFlag() {
66 MIP_gurobi_wrapper mgw(NULL);
67 try {
68 mgw.checkDLL();
69 return "";
70 } catch (MiniZinc::InternalError&) {
71 return "--gurobi-dll";
72 }
73}
74
75string MIP_gurobi_wrapper::getId() { return "gurobi"; }
76
77string MIP_gurobi_wrapper::getName() { return "Gurobi"; }
78
79vector<string> MIP_gurobi_wrapper::getTags() { return {"mip", "float", "api"}; }
80
81vector<string> MIP_gurobi_wrapper::getStdFlags() { return {"-a", "-n", "-p", "-s"}; }
82
83const vector<string>& gurobiDLLs(void) {
84 static const vector<string> sGurobiDLLs = {"gurobi90", "gurobi85", "gurobi81", "gurobi80",
85 "gurobi75", "gurobi70", "gurobi65"};
86 return sGurobiDLLs;
87}
88
89void MIP_gurobi_wrapper::Options::printHelp(ostream& os) {
90 os << "GUROBI MIP wrapper options:"
91 << std::endl
92 // -s print statistics
93 // << " --readParam <file> read GUROBI parameters from file
94 // << "--writeParam <file> write GUROBI parameters to file
95 // << "--tuneParam instruct GUROBI to tune parameters instead of solving
96 << " -f\n free search (default)" << std::endl
97 << " --fixed-search\n fixed search (approximation of the model's one by branching "
98 "priorities)"
99 << std::endl
100 << " --uniform-search\n 'more fixed' search (all variables in the search anns get "
101 "priority 1)"
102 << std::endl
103 << " --mipfocus <n>\n 1: feasibility, 2: optimality, 3: move bound (default is 0, "
104 "balanced)"
105 << std::endl
106 << " -a\n print intermediate solutions (use for optimization problems only TODO)"
107 << std::endl
108 << " -p <N>\n use N threads, default: 1."
109 << std::endl
110 // << " --nomippresolve disable MIP presolving NOT IMPL" << std::endl
111 << " --solver-time-limit <N>, --solver-time\n"
112 " stop search after N milliseconds wall time"
113 << std::endl
114 << " --solver-time-limit-feas <N>, --solver-tlf\n"
115 " stop search after N milliseconds wall time after the first feasible solution"
116 << std::endl
117 << " -n <N>, --num-solutions <N>\n"
118 " stop search after N solutions"
119 << std::endl
120 << " --workmem <N>, --nodefilestart <N>\n"
121 " maximal RAM for node tree used before writing to node file, GB, default: 3"
122 << std::endl
123 << " --writeModel <file>\n write model to <file> (.lp, .mps, .sav, ...)" << std::endl
124 << " --readParam <file>\n read GUROBI parameters from file" << std::endl
125 << " --writeParam <file>\n write GUROBI parameters to file"
126 << std::endl
127 // << " --tuneParam instruct GUROBI to tune parameters instead of solving NOT
128 // IMPL"
129
130 << "\n --absGap <n>\n absolute gap |primal-dual| to stop" << std::endl
131 << " --relGap <n>\n relative gap |primal-dual|/<solver-dep> to stop. Default 1e-8, set <0 "
132 "to use backend's default"
133 << std::endl
134 << " --feasTol <n>\n primal feasibility tolerance. Default 1e-8" << std::endl
135 << " --intTol <n>\n integrality tolerance for a variable. Gurobi recommends at least "
136 "feasTol. Default 1e-8"
137 << std::endl
138 // << " --objDiff <n> objective function discretization. Default 1.0" << std::endl
139
140 << "\n --gurobi-dll <file> or <basename>\n Gurobi DLL, or base name, such as gurobi75, "
141 "when using plugin. Default range tried: "
142 << gurobiDLLs().front() << " .. " << gurobiDLLs().back() << std::endl
143 << std::endl;
144}
145
146bool MIP_gurobi_wrapper::Options::processOption(int& i, std::vector<std::string>& argv) {
147 MiniZinc::CLOParser cop(i, argv);
148 if (string(argv[i]) == "-a" || string(argv[i]) == "--all" ||
149 string(argv[i]) == "--all-solutions") {
150 flag_all_solutions = true;
151 } else if (string(argv[i]) == "-f") {
152 } else if (string(argv[i]) == "--fixed-search") {
153 nFreeSearch = 0;
154 } else if (string(argv[i]) == "--uniform-search") {
155 nFreeSearch = 2;
156 } else if (cop.get("--mipfocus --mipFocus --MIPFocus --MIPfocus", &nMIPFocus)) {
157 } else if (cop.get("--writeModel", &sExportModel)) {
158 } else if (cop.get("-p", &nThreads)) {
159 } else if (cop.get("--solver-time-limit --solver-time", &nTimeout1000)) {
160 } else if (cop.get("--solver-time-limit-feas --solver-tlf", &nTimeoutFeas1000)) {
161 } else if (cop.get("-n --num-solutions", &nSolLimit)) {
162 } else if (cop.get("--workmem --nodefilestart", &nWorkMemLimit)) {
163 } else if (cop.get("--readParam", &sReadParams)) {
164 } else if (cop.get("--writeParam", &sWriteParams)) {
165 } else if (cop.get("--absGap", &absGap)) {
166 } else if (cop.get("--relGap", &relGap)) {
167 } else if (cop.get("--feasTol", &feasTol)) {
168 } else if (cop.get("--intTol", &intTol)) {
169 } else if (cop.get("--gurobi-dll", &sGurobiDLL)) {
170 // } else if ( cop.get( "--objDiff", &objDiff ) ) {
171 } else
172 return false;
173 return true;
174}
175
176void MIP_gurobi_wrapper::wrap_assert(bool cond, string msg, bool fTerm) {
177 if (!cond) {
178 gurobi_buffer = "[NO ERROR STRING GIVEN]";
179 if (error) {
180 gurobi_buffer = dll_GRBgeterrormsg(env);
181 }
182 string msgAll =
183 (" MIP_gurobi_wrapper runtime error: " + gurobi_buffer + "\nMessage from caller: " + msg);
184 cerr << msgAll << "\nGurobi error code: " << error << endl;
185 if (fTerm) {
186 cerr << "TERMINATING." << endl;
187 throw runtime_error(msgAll);
188 }
189 }
190}
191
192#ifdef GUROBI_PLUGIN
193
194namespace {
195void* dll_open(const char* file) {
196#ifdef HAS_DLFCN_H
197 if (MiniZinc::FileUtils::is_absolute(file)) {
198 return dlopen(file, RTLD_NOW);
199 } else {
200 return dlopen((std::string("lib") + file + ".so").c_str(), RTLD_NOW);
201 }
202#else
203 if (MiniZinc::FileUtils::is_absolute(file)) {
204 return LoadLibrary(file);
205 } else {
206 return LoadLibrary((std::string(file) + ".dll").c_str());
207 }
208#endif
209}
210void* dll_sym(void* dll, const char* sym) {
211#ifdef HAS_DLFCN_H
212 void* ret = dlsym(dll, sym);
213#else
214 void* ret = GetProcAddress((HMODULE)dll, sym);
215#endif
216 if (ret == NULL)
217 throw MiniZinc::InternalError("cannot load symbol " + string(sym) + " from gurobi dll");
218 return ret;
219}
220void dll_close(void* dll) {
221#ifdef HAS_DLFCN_H
222 dlclose(dll);
223#else
224 FreeLibrary((HMODULE)dll);
225#endif
226}
227} // namespace
228
229#endif
230
231void MIP_gurobi_wrapper::checkDLL() {
232#ifdef GUROBI_PLUGIN
233 gurobi_dll = NULL;
234 if (options && options->sGurobiDLL.size()) {
235 gurobi_dll = dll_open(options->sGurobiDLL.c_str());
236 } else {
237 for (const auto& s : gurobiDLLs()) {
238 gurobi_dll = dll_open(s.c_str());
239 if (NULL != gurobi_dll) {
240 break;
241 }
242 }
243 }
244
245 if (gurobi_dll == NULL) {
246 if (options == NULL || options->sGurobiDLL.empty()) {
247 throw MiniZinc::InternalError("cannot load gurobi dll, specify --gurobi-dll");
248 } else {
249 throw MiniZinc::InternalError("cannot load gurobi dll `" + options->sGurobiDLL + "'");
250 }
251 }
252
253 *(void**)(&dll_GRBversion) = dll_sym(gurobi_dll, "GRBversion");
254 *(void**)(&dll_GRBaddconstr) = dll_sym(gurobi_dll, "GRBaddconstr");
255 *(void**)(&dll_GRBaddgenconstrIndicator) = dll_sym(gurobi_dll, "GRBaddgenconstrIndicator");
256 *(void**)(&dll_GRBaddvars) = dll_sym(gurobi_dll, "GRBaddvars");
257 *(void**)(&dll_GRBcbcut) = dll_sym(gurobi_dll, "GRBcbcut");
258 *(void**)(&dll_GRBcbget) = dll_sym(gurobi_dll, "GRBcbget");
259 *(void**)(&dll_GRBcblazy) = dll_sym(gurobi_dll, "GRBcblazy");
260 *(void**)(&dll_GRBfreeenv) = dll_sym(gurobi_dll, "GRBfreeenv");
261 *(void**)(&dll_GRBfreemodel) = dll_sym(gurobi_dll, "GRBfreemodel");
262 *(void**)(&dll_GRBgetdblattr) = dll_sym(gurobi_dll, "GRBgetdblattr");
263 *(void**)(&dll_GRBgetdblattrarray) = dll_sym(gurobi_dll, "GRBgetdblattrarray");
264 *(void**)(&dll_GRBgetenv) = dll_sym(gurobi_dll, "GRBgetenv");
265 *(void**)(&dll_GRBgeterrormsg) = dll_sym(gurobi_dll, "GRBgeterrormsg");
266 *(void**)(&dll_GRBgetintattr) = dll_sym(gurobi_dll, "GRBgetintattr");
267 *(void**)(&dll_GRBloadenv) = dll_sym(gurobi_dll, "GRBloadenv");
268 *(void**)(&dll_GRBnewmodel) = dll_sym(gurobi_dll, "GRBnewmodel");
269 *(void**)(&dll_GRBoptimize) = dll_sym(gurobi_dll, "GRBoptimize");
270 *(void**)(&dll_GRBreadparams) = dll_sym(gurobi_dll, "GRBreadparams");
271 *(void**)(&dll_GRBsetcallbackfunc) = dll_sym(gurobi_dll, "GRBsetcallbackfunc");
272 *(void**)(&dll_GRBsetdblparam) = dll_sym(gurobi_dll, "GRBsetdblparam");
273 *(void**)(&dll_GRBsetintattr) = dll_sym(gurobi_dll, "GRBsetintattr");
274 *(void**)(&dll_GRBsetintattrlist) = dll_sym(gurobi_dll, "GRBsetintattrlist");
275 *(void**)(&dll_GRBsetdblattrelement) = dll_sym(gurobi_dll, "GRBsetdblattrelement");
276 *(void**)(&dll_GRBsetdblattrlist) = dll_sym(gurobi_dll, "GRBsetdblattrlist");
277 *(void**)(&dll_GRBsetintparam) = dll_sym(gurobi_dll, "GRBsetintparam");
278 *(void**)(&dll_GRBsetstrparam) = dll_sym(gurobi_dll, "GRBsetstrparam");
279 *(void**)(&dll_GRBterminate) = dll_sym(gurobi_dll, "GRBterminate");
280 *(void**)(&dll_GRBupdatemodel) = dll_sym(gurobi_dll, "GRBupdatemodel");
281 *(void**)(&dll_GRBwrite) = dll_sym(gurobi_dll, "GRBwrite");
282 *(void**)(&dll_GRBwriteparams) = dll_sym(gurobi_dll, "GRBwriteparams");
283
284#else
285
286 dll_GRBversion = GRBversion;
287 dll_GRBaddconstr = GRBaddconstr;
288 dll_GRBaddgenconstrIndicator = GRBaddgenconstrIndicator;
289 dll_GRBaddvars = GRBaddvars;
290 dll_GRBcbcut = GRBcbcut;
291 dll_GRBcbget = GRBcbget;
292 dll_GRBcblazy = GRBcblazy;
293 dll_GRBfreeenv = GRBfreeenv;
294 dll_GRBfreemodel = GRBfreemodel;
295 dll_GRBgetdblattr = GRBgetdblattr;
296 dll_GRBgetdblattrarray = GRBgetdblattrarray;
297 dll_GRBgetenv = GRBgetenv;
298 dll_GRBgeterrormsg = GRBgeterrormsg;
299 dll_GRBgetintattr = GRBgetintattr;
300 dll_GRBloadenv = GRBloadenv;
301 dll_GRBnewmodel = GRBnewmodel;
302 dll_GRBoptimize = GRBoptimize;
303 dll_GRBreadparams = GRBreadparams;
304 dll_GRBsetcallbackfunc = GRBsetcallbackfunc;
305 dll_GRBsetdblparam = GRBsetdblparam;
306 dll_GRBsetintattr = GRBsetintattr;
307 dll_GRBsetintattrlist = GRBsetintattrlist;
308 dll_GRBsetdblattrelement = GRBsetdblattrelement;
309 dll_GRBsetdblattrlist = GRBsetdblattrlist;
310 dll_GRBsetintparam = GRBsetintparam;
311 dll_GRBsetstrparam = GRBsetstrparam;
312 dll_GRBterminate = GRBterminate;
313 dll_GRBupdatemodel = GRBupdatemodel;
314 dll_GRBwrite = GRBwrite;
315 dll_GRBwriteparams = GRBwriteparams;
316
317#endif
318}
319
320void MIP_gurobi_wrapper::openGUROBI() {
321 checkDLL();
322
323 /* Initialize the GUROBI environment */
324 {
325 // cout << "% " << flush; // Gurobi 7.5.2 prints "Academic License..."
326 MiniZinc::StreamRedir redirStdout(stdout, stderr);
327 error = dll_GRBloadenv(&env, "mzn-gurobi.log");
328 }
329 wrap_assert(!error, "Could not open GUROBI environment.");
330 error = dll_GRBsetintparam(env, "OutputFlag", 0); // Switch off output
331 // error = dll_GRBsetintparam(env, "LogToConsole",
332 // fVerbose ? 1 : 0); // also when flag_all_solutions? TODO
333 /* Create the problem. */
334 error = dll_GRBnewmodel(env, &model, "mzn_gurobi", 0, NULL, NULL, NULL, NULL, NULL);
335 wrap_assert(model != NULL, "Failed to create LP.");
336}
337
338void MIP_gurobi_wrapper::closeGUROBI() {
339 /* Free model */
340
341 // If not allocated, skip
342 if (nullptr != model) {
343 /* Free up the problem as allocated by GRB_createprob, if necessary */
344 dll_GRBfreemodel(model);
345 model = 0;
346 }
347
348 /* Free environment */
349
350 if (nullptr != env) dll_GRBfreeenv(env);
351 /// and at last:
352// MIP_wrapper::cleanup();
353#ifdef GUROBI_PLUGIN
354 // dll_close(gurobi_dll); // Is called too many times, disabling. 2019-05-06
355#endif
356}
357
358void MIP_gurobi_wrapper::doAddVars(size_t n, double* obj, double* lb, double* ub,
359 MIP_wrapper::VarType* vt, string* names) {
360 /// Convert var types:
361 vector<char> ctype(n);
362 vector<char*> pcNames(n);
363 for (size_t i = 0; i < n; ++i) {
364 pcNames[i] = (char*)names[i].c_str();
365 switch (vt[i]) {
366 case REAL:
367 ctype[i] = GRB_CONTINUOUS;
368 break;
369 case INT:
370 ctype[i] = GRB_INTEGER;
371 break;
372 case BINARY:
373 ctype[i] = GRB_BINARY;
374 break;
375 default:
376 throw runtime_error(" MIP_wrapper: unknown variable type");
377 }
378 }
379 error = dll_GRBaddvars(model, static_cast<int>(n), 0, NULL, NULL, NULL, obj, lb, ub, &ctype[0],
380 &pcNames[0]);
381 wrap_assert(!error, "Failed to declare variables.");
382 error = dll_GRBupdatemodel(model);
383 wrap_assert(!error, "Failed to update model.");
384}
385
386static char getGRBSense(MIP_wrapper::LinConType s) {
387 switch (s) {
388 case MIP_wrapper::LQ:
389 return GRB_LESS_EQUAL;
390 case MIP_wrapper::EQ:
391 return GRB_EQUAL;
392 case MIP_wrapper::GQ:
393 return GRB_GREATER_EQUAL;
394 default:
395 throw runtime_error(" MIP_gurobi_wrapper: unknown constraint sense");
396 }
397}
398
399void MIP_gurobi_wrapper::addRow(int nnz, int* rmatind, double* rmatval,
400 MIP_wrapper::LinConType sense, double rhs, int mask,
401 string rowName) {
402 //// Make sure in order to notice the indices of lazy constr:
403 ++nRows;
404 /// Convert var types:
405 char ssense = getGRBSense(sense);
406 const char* pRName = rowName.c_str();
407 error = dll_GRBaddconstr(model, nnz, rmatind, rmatval, ssense, rhs, pRName);
408 wrap_assert(!error, "Failed to add constraint.");
409 int nLazyAttr = 0;
410 const bool fUser = (MaskConsType_Usercut & mask) != 0;
411 const bool fLazy = (MaskConsType_Lazy & mask) != 0;
412 /// Gurobi 6.5.2 has lazyness 1-3.
413 if (fUser) {
414 if (fLazy)
415 nLazyAttr = 2; // just active lazy
416 else
417 nLazyAttr = 3; // even LP-active
418 } else if (fLazy)
419 nLazyAttr = 1; // very lazy
420 if (nLazyAttr) {
421 nLazyIdx.push_back(nRows - 1);
422 nLazyValue.push_back(nLazyAttr);
423 }
424}
425
426void MIP_gurobi_wrapper::addIndicatorConstraint(int iBVar, int bVal, int nnz, int* rmatind,
427 double* rmatval, MIP_wrapper::LinConType sense,
428 double rhs, string rowName) {
429 wrap_assert(0 <= bVal && 1 >= bVal, "Gurobi: addIndicatorConstraint: bVal not 0/1");
430 //// Make sure in order to notice the indices of lazy constr: also here? TODO
431 ++nRows;
432 char ssense = getGRBSense(sense);
433 error = dll_GRBaddgenconstrIndicator(model, rowName.c_str(), iBVar, bVal, nnz, rmatind, rmatval,
434 ssense, rhs);
435 wrap_assert(!error, "Failed to add indicator constraint.");
436}
437
438bool MIP_gurobi_wrapper::addSearch(const std::vector<VarId>& vars, const std::vector<int> pri) {
439 assert(vars.size() == pri.size());
440 static_assert(sizeof(VarId) == sizeof(int), "VarId should be (u)int currently");
441 error = dll_GRBsetintattrlist(model, "BranchPriority", static_cast<int>(vars.size()),
442 (int*)vars.data(), (int*)pri.data());
443 wrap_assert(!error, "Failed to add branching priorities");
444 return true;
445}
446
447int MIP_gurobi_wrapper::getFreeSearch() { return options->nFreeSearch; }
448
449bool MIP_gurobi_wrapper::addWarmStart(const std::vector<VarId>& vars,
450 const std::vector<double> vals) {
451 assert(vars.size() == vals.size());
452 static_assert(sizeof(VarId) == sizeof(int), "VarId should be (u)int currently");
453 // error = GRBsetdblattrelement(model, "Start", 0, 1.0);
454 error = dll_GRBsetdblattrlist(model, "Start", static_cast<int>(vars.size()), (int*)vars.data(),
455 (double*)vals.data());
456 wrap_assert(!error, "Failed to add warm start");
457 return true;
458}
459
460void MIP_gurobi_wrapper::setVarBounds(int iVar, double lb, double ub) {
461 wrap_assert(lb <= ub, "mzn-gurobi: setVarBounds: lb>ub");
462 error = dll_GRBsetdblattrelement(model, GRB_DBL_ATTR_LB, iVar, lb);
463 wrap_assert(!error, "mzn-gurobi: failed to set var lb.");
464 error = dll_GRBsetdblattrelement(model, GRB_DBL_ATTR_UB, iVar, ub);
465 wrap_assert(!error, "mzn-gurobi: failed to set var ub.");
466}
467
468void MIP_gurobi_wrapper::setVarLB(int iVar, double lb) {
469 error = dll_GRBsetdblattrelement(model, GRB_DBL_ATTR_LB, iVar, lb);
470 wrap_assert(!error, "mzn-gurobi: failed to set var lb.");
471}
472
473void MIP_gurobi_wrapper::setVarUB(int iVar, double ub) {
474 error = dll_GRBsetdblattrelement(model, GRB_DBL_ATTR_UB, iVar, ub);
475 wrap_assert(!error, "mzn-gurobi: failed to set var ub.");
476}
477
478/// SolutionCallback ------------------------------------------------------------------------
479/// Gurobi ensures thread-safety
480static int __stdcall solcallback(GRBmodel* model, void* cbdata, int where, void* usrdata) {
481 MIP_wrapper::CBUserInfo* info = (MIP_wrapper::CBUserInfo*)usrdata;
482 MIP_gurobi_wrapper* gw = static_cast<MIP_gurobi_wrapper*>(info->wrapper);
483
484 double nodecnt = 0.0, actnodes = 0.0, objVal = 0.0;
485 int solcnt = 0;
486 int newincumbent = 0;
487
488 if (GRB_CB_MIP == where) {
489 /* General MIP callback */
490 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIP_OBJBND, &info->pOutput->bestBound);
491 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIP_NODLFT, &actnodes);
492 info->pOutput->nOpenNodes = static_cast<int>(actnodes);
493 /// Check time after the 1st feas
494 if (-1e100 != info->nTime1Feas) {
495 double tNow;
496 gw->dll_GRBcbget(cbdata, where, GRB_CB_RUNTIME, (void*)&tNow);
497 if (tNow - info->nTime1Feas >= info->nTimeoutFeas) gw->dll_GRBterminate(model);
498 }
499 } else if (GRB_CB_MESSAGE == where) {
500 /* Message callback */
501 if (info->fVerb) {
502 char* msg;
503 gw->dll_GRBcbget(cbdata, where, GRB_CB_MSG_STRING, &msg);
504 cerr << msg << flush;
505 }
506 } else if (GRB_CB_MIPSOL == where) {
507 /* MIP solution callback */
508 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPSOL_NODCNT, &nodecnt);
509 info->pOutput->nNodes = static_cast<int>(nodecnt);
510 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPSOL_OBJ, &objVal);
511 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPSOL_SOLCNT, &solcnt);
512
513 if (fabs(info->pOutput->objVal - objVal) > 1e-12 * (1.0 + fabs(objVal))) {
514 newincumbent = 1;
515 // Not confirmed yet, see lazy cuts
516 // info->pOutput->objVal = objVal;
517 // info->pOutput->status = MIP_wrapper::SAT;
518 // info->pOutput->statusName = "feasible from a callback";
519 }
520 if (newincumbent) {
521 assert(info->pOutput->x);
522 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPSOL_SOL, (void*)info->pOutput->x);
523
524 info->pOutput->dWallTime = std::chrono::duration<double>(std::chrono::steady_clock::now() -
525 info->pOutput->dWallTime0)
526 .count();
527 info->pOutput->dCPUTime = double(std::clock() - info->pOutput->cCPUTime0) / CLOCKS_PER_SEC;
528 }
529
530 /// Callback for lazy cuts
531 /// Before printing
532 if (info->cutcbfn && info->cutMask & MIP_wrapper::MaskConsType_Lazy) {
533 MIP_wrapper::CutInput cutInput;
534 cerr << " GRB: GRB_CB_MIPSOL (" << objVal << ") -> cut callback " << endl;
535 info->cutcbfn(*info->pOutput, cutInput, info->psi, true);
536 for (auto& cd : cutInput) {
537 // assert( cd.mask & MIP_wrapper::MaskConsType_Lazy );
538 if (cd.mask & MIP_wrapper::MaskConsType_Lazy) { // take only lazy constr generators
539 int error =
540 gw->dll_GRBcblazy(cbdata, static_cast<int>(cd.rmatind.size()), cd.rmatind.data(),
541 cd.rmatval.data(), getGRBSense(cd.sense), cd.rhs);
542 if (error)
543 cerr << " GRB_wrapper: failed to add lazy cut. " << endl;
544 else
545 newincumbent = -1;
546 // info->pOutput->objVal = 1e100; // to mark that we can get a new incumbent
547 // which should be printed
548 }
549 }
550 }
551 if (solcnt >= 0 /*This is solution number for Gurobi*/ && newincumbent >= 0) {
552 if (fabs(info->pOutput->objVal - objVal) > 1e-12 * (1.0 + fabs(objVal))) {
553 newincumbent = 1;
554 info->pOutput->objVal = objVal;
555 info->pOutput->status = MIP_wrapper::SAT;
556 info->pOutput->statusName = "feasible from a callback";
557 }
558 }
559 if (newincumbent > 0) {
560 info->pOutput->dCPUTime = double(std::clock() - info->pOutput->cCPUTime0) / CLOCKS_PER_SEC;
561
562 /// Set time for the 1st feas
563 if (0 <= info->nTimeoutFeas && -1e100 == info->nTime1Feas)
564 gw->dll_GRBcbget(cbdata, where, GRB_CB_RUNTIME, (void*)&info->nTime1Feas);
565
566 /// Call the user function:
567 if (info->solcbfn) (*info->solcbfn)(*info->pOutput, info->psi);
568
569 if (0 == info->nTimeoutFeas) gw->dll_GRBterminate(model); // Straight after feas
570 }
571 } else if (GRB_CB_MIPNODE == where) {
572 int status;
573 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPNODE_STATUS, &status);
574 if (status == GRB_OPTIMAL && info->cutcbfn) { // if cut handler given
575 MIP_wrapper::Output outpRlx;
576 outpRlx.x = info->pOutput->x; // using the sol output storage TODO?
577 outpRlx.nCols = info->pOutput->nCols;
578 assert(outpRlx.x && outpRlx.nCols);
579 // dll_GRBcbget(cbdata, where, GRB_CB_MIPNODE_RELOBJ, outpRlx.objVal);
580 gw->dll_GRBcbget(cbdata, where, GRB_CB_MIPNODE_REL, (void*)outpRlx.x);
581 // cerr << " GRB: GRB_CB_MIPNODE -> cut callback " << endl;
582 MIP_wrapper::CutInput cutInput;
583 info->cutcbfn(outpRlx, cutInput, info->psi, false);
584 // static int nCuts=0;
585 // nCuts += cutInput.size();
586 // if ( cutInput.size() )
587 // cerr << "\n N CUTS: " << nCuts << endl;
588 for (auto& cd : cutInput) {
589 if (!(cd.mask & (MIP_wrapper::MaskConsType_Usercut | MIP_wrapper::MaskConsType_Lazy)))
590 throw runtime_error("Cut callback: should be user/lazy");
591 if (cd.mask & MIP_wrapper::MaskConsType_Usercut) {
592 int error =
593 gw->dll_GRBcbcut(cbdata, static_cast<int>(cd.rmatind.size()), cd.rmatind.data(),
594 cd.rmatval.data(), getGRBSense(cd.sense), cd.rhs);
595 if (error) cerr << " GRB_wrapper: failed to add user cut. " << endl;
596 }
597 if (cd.mask & MIP_wrapper::MaskConsType_Lazy) {
598 int error =
599 gw->dll_GRBcblazy(cbdata, static_cast<int>(cd.rmatind.size()), cd.rmatind.data(),
600 cd.rmatval.data(), getGRBSense(cd.sense), cd.rhs);
601 if (error) cerr << " GRB_wrapper: failed to add lazy cut. " << endl;
602 }
603 }
604 }
605 }
606 return 0;
607} /* END logcallback */
608// end SolutionCallback ---------------------------------------------------------------------
609
610MIP_gurobi_wrapper::Status MIP_gurobi_wrapper::convertStatus(int gurobiStatus) {
611 Status s = Status::UNKNOWN;
612 ostringstream oss;
613 /* Converting the status. */
614 if (gurobiStatus == GRB_OPTIMAL) {
615 s = Status::OPT;
616 oss << "Optimal";
617 } else if (gurobiStatus == GRB_INF_OR_UNBD) {
618 s = Status::UNSATorUNBND;
619 oss << "Infeasible or unbounded";
620 } else if (gurobiStatus == GRB_INFEASIBLE) {
621 s = Status::UNSAT;
622 oss << "Infeasible";
623 } else if (gurobiStatus == GRB_UNBOUNDED) {
624 oss << "Unbounded";
625 s = Status::UNBND;
626 } else {
627 int solcount = 0;
628 error = dll_GRBgetintattr(model, "SolCount", &solcount);
629 wrap_assert(!error, " Failure to access solution count.", false);
630 if (solcount) s = Status::SAT;
631 oss << "Gurobi stopped with status " << gurobiStatus;
632 }
633 output.statusName = gurobi_status_buffer = oss.str();
634 return s;
635}
636
637void MIP_gurobi_wrapper::solve() { // Move into ancestor?
638 if (options->flag_all_solutions && 0 == nProbType)
639 cerr << "WARNING. --all-solutions for SAT problems not implemented." << endl;
640
641 error = dll_GRBupdatemodel(model); // for model export
642 wrap_assert(!error, "Failed to update model.");
643
644 /// ADDING LAZY CONSTRAINTS IF ANY
645 if (nLazyIdx.size()) {
646 assert(nLazyIdx.size() == nLazyValue.size());
647 if (fVerbose)
648 cerr << " MIP_gurobi_wrapper: marking " << nLazyIdx.size() << " lazy cuts." << endl;
649 error = dll_GRBsetintattrlist(model, "Lazy", static_cast<int>(nLazyIdx.size()), nLazyIdx.data(),
650 nLazyValue.data());
651 wrap_assert(!error, "Failed to set constraint attribute.");
652 nLazyIdx.clear();
653 nLazyValue.clear();
654 error = dll_GRBupdatemodel(model); // for model export
655 wrap_assert(!error, "Failed to update model after modifying some constraint attr.");
656 }
657
658 /////////////// Last-minute solver options //////////////////
659 /* Turn on output to file */
660 error = dll_GRBsetstrparam(dll_GRBgetenv(model), "LogFile",
661 ""); // FAILS to switch off in Ubuntu 15.04
662 /* Turn on output to the screen */
663 error = dll_GRBsetintparam(dll_GRBgetenv(model), "OutputFlag",
664 /*fVerbose ? 1 :*/ 0); // switch off, redirect in callback
665 // error = dll_GRBsetintparam(dll_GRBgetenv(model), "LogToConsole",
666 // fVerbose ? 1 : 0); // also when flag_all_solutions? TODO
667 wrap_assert(!error, " GUROBI Warning: Failure to switch screen indicator.", false);
668 // error = dll_GRB_setintparam (env, GRB_PARAM_ClockType, 1); // CPU time
669 // error = dll_GRB_setintparam (env, GRB_PARAM_MIP_Strategy_CallbackReducedLP, GRB__OFF); //
670 // Access original model
671 if (options->sExportModel.size()) {
672 error = dll_GRBwrite(model, options->sExportModel.c_str());
673 wrap_assert(!error, "Failed to write LP to disk.", false);
674 }
675
676 /// TODO
677 // if(all_solutions && obj.getImpl()) {
678 // IloNum lastObjVal = (obj.getSense() == IloObjective::Minimize ) ?
679 // _ilogurobi->use(SolutionCallback(_iloenv, lastObjVal, *this));
680 // Turn off GUROBI logging
681
682 if (options->nThreads > 0) {
683 error = dll_GRBsetintparam(dll_GRBgetenv(model), GRB_INT_PAR_THREADS, options->nThreads);
684 // int nn; // THE SETTING FAILS TO WORK IN 6.0.5.
685 // error = dll_getintparam(env, GRB_INT_PAR_THREADS, &nn);
686 // cerr << "Set " << nThreads << " threads, reported " << nn << endl;
687 wrap_assert(!error, "Failed to set GRB_INT_PAR_THREADS.", false);
688 }
689
690 if (options->nTimeout1000 > 0) {
691 error = dll_GRBsetdblparam(dll_GRBgetenv(model), GRB_DBL_PAR_TIMELIMIT,
692 static_cast<double>(options->nTimeout1000) / 1000.0);
693 wrap_assert(!error, "Failed to set GRB_PARAM_TimeLimit.", false);
694 }
695
696 if (options->nSolLimit > 0) {
697 error = dll_GRBsetintparam(dll_GRBgetenv(model), GRB_INT_PAR_SOLUTIONLIMIT, options->nSolLimit);
698 wrap_assert(!error, "Failed to set GRB_INT_PAR_SOLLIMIT.", false);
699 }
700
701 if (options->nWorkMemLimit > 0 && options->nWorkMemLimit < 1e200) {
702 error = dll_GRBsetdblparam(dll_GRBgetenv(model), "NodefileStart", options->nWorkMemLimit);
703 wrap_assert(!error, "Failed to set NodefileStart.", false);
704 }
705
706 if (options->absGap >= 0.0) {
707 error = dll_GRBsetdblparam(dll_GRBgetenv(model), "MIPGapAbs", options->absGap);
708 wrap_assert(!error, "Failed to set MIPGapAbs.", false);
709 }
710 if (options->nMIPFocus > 0) {
711 error = dll_GRBsetintparam(dll_GRBgetenv(model), GRB_INT_PAR_MIPFOCUS, options->nMIPFocus);
712 wrap_assert(!error, "Failed to set GRB_INT_PAR_MIPFOCUS.", false);
713 }
714
715 if (options->relGap >= 0.0) {
716 error = dll_GRBsetdblparam(dll_GRBgetenv(model), "MIPGap", options->relGap);
717 wrap_assert(!error, "Failed to set MIPGap.", false);
718 }
719 if (options->intTol >= 0.0) {
720 error = dll_GRBsetdblparam(dll_GRBgetenv(model), "IntFeasTol", options->intTol);
721 wrap_assert(!error, "Failed to set IntFeasTol.", false);
722 }
723 if (options->feasTol >= 0.0) {
724 error = dll_GRBsetdblparam(dll_GRBgetenv(model), "FeasibilityTol", options->feasTol);
725 wrap_assert(!error, "Failed to set FeasTol.", false);
726 }
727
728 /// Solution callback
729 output.nCols = static_cast<int>(colObj.size());
730 x.resize(output.nCols);
731 output.x = &x[0];
732 SolCallbackFn solcbfn = cbui.solcbfn;
733 if (true) { // Need for logging
734 cbui.fVerb = fVerbose;
735 cbui.nTimeoutFeas = options->nTimeoutFeas1000 / 1000.0;
736 if (!options->flag_all_solutions) cbui.solcbfn = 0;
737 if (cbui.cutcbfn) {
738 assert(cbui.cutMask & (MaskConsType_Usercut | MaskConsType_Lazy));
739 if (cbui.cutMask & MaskConsType_Usercut) {
740 // For user cuts, needs to keep some info after presolve
741 if (fVerbose)
742 cerr << " MIP_gurobi_wrapper: user cut callback enabled, setting PreCrush=1" << endl;
743 error = dll_GRBsetintparam(dll_GRBgetenv(model), GRB_INT_PAR_PRECRUSH, 1);
744 wrap_assert(!error, "Failed to set GRB_INT_PAR_PRECRUSH.", false);
745 }
746 if (cbui.cutMask & MaskConsType_Lazy) {
747 // For lazy cuts, Gurobi disables some presolves
748 if (fVerbose)
749 cerr << " MIP_gurobi_wrapper: lazy cut callback enabled, setting LazyConstraints=1"
750 << endl;
751 error = dll_GRBsetintparam(dll_GRBgetenv(model), GRB_INT_PAR_LAZYCONSTRAINTS, 1);
752 wrap_assert(!error, "Failed to set GRB_INT_PAR_LAZYCONSTRAINTS.", false);
753 }
754 }
755 error = dll_GRBsetcallbackfunc(model, solcallback, (void*)&cbui);
756 wrap_assert(!error, "Failed to set callback", false);
757 }
758
759 /// after all modifs
760 if (options->sReadParams.size()) {
761 error = dll_GRBreadparams(dll_GRBgetenv(model), options->sReadParams.c_str());
762 wrap_assert(!error, "Failed to read GUROBI parameters.", false);
763 }
764
765 if (options->sWriteParams.size()) {
766 error = dll_GRBwriteparams(dll_GRBgetenv(model), options->sWriteParams.c_str());
767 wrap_assert(!error, "Failed to write GUROBI parameters.", false);
768 }
769
770 cbui.pOutput->dWallTime0 = output.dWallTime0 = std::chrono::steady_clock::now();
771 output.dCPUTime = cbui.pOutput->cCPUTime0 = std::clock();
772
773 /* Optimize the problem and obtain solution. */
774 error = dll_GRBoptimize(model);
775 wrap_assert(!error, "Failed to optimize MIP.");
776
777 output.dWallTime =
778 std::chrono::duration<double>(std::chrono::steady_clock::now() - output.dWallTime0).count();
779 output.dCPUTime = (std::clock() - output.dCPUTime) / CLOCKS_PER_SEC;
780
781 int solstat;
782 error = dll_GRBgetintattr(model, GRB_INT_ATTR_STATUS, &solstat);
783 wrap_assert(!error, "Failed to get MIP status.", false);
784 output.status = convertStatus(solstat);
785
786 /// Continuing to fill the output object:
787 if (Status::OPT == output.status || Status::SAT == output.status) {
788 error = dll_GRBgetdblattr(model, GRB_DBL_ATTR_OBJVAL, &output.objVal);
789 wrap_assert(!error, "No MIP objective value available.");
790
791 // int cur_numrows = dll_GRB_getnumrows (env, lp);
792 int cur_numcols = getNCols();
793 assert(cur_numcols == colObj.size());
794
795 x.resize(cur_numcols);
796 output.x = &x[0];
797 error = dll_GRBgetdblattrarray(model, GRB_DBL_ATTR_X, 0, cur_numcols, (double*)output.x);
798 wrap_assert(!error, "Failed to get variable values.");
799 if (!options->flag_all_solutions && solcbfn) {
800 solcbfn(output, cbui.psi);
801 }
802 }
803 output.bestBound = 1e308;
804 error = dll_GRBgetdblattr(model, GRB_DBL_ATTR_OBJBOUNDC, &output.bestBound);
805 wrap_assert(!error, "Failed to get the best bound.", false);
806 double nNodes = -1;
807 error = dll_GRBgetdblattr(model, GRB_DBL_ATTR_NODECOUNT, &nNodes);
808 output.nNodes = static_cast<int>(nNodes);
809 output.nOpenNodes = 0;
810}
811
812void MIP_gurobi_wrapper::setObjSense(int s) {
813 error = dll_GRBsetintattr(model, GRB_INT_ATTR_MODELSENSE, s > 0 ? GRB_MAXIMIZE : GRB_MINIMIZE);
814 wrap_assert(!error, "Failed to set obj sense.");
815}