this repo has no description
at develop 33 kB view raw
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}