this repo has no description
at develop 40 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/file_utils.hh> 18#include <minizinc/utils.hh> 19 20#include <cmath> 21#include <cstring> 22#include <fstream> 23#include <iomanip> 24#include <iostream> 25#include <sstream> 26#include <stdexcept> 27#include <string> 28 29#ifdef CPLEX_PLUGIN 30#ifdef HAS_DLFCN_H 31#include <dlfcn.h> 32#elif defined HAS_WINDOWS_H 33#include <Windows.h> 34#endif 35#endif 36 37using namespace std; 38 39#include <minizinc/solvers/MIP/MIP_cplex_wrap.hh> 40 41#ifdef CPLEX_PLUGIN 42 43namespace { 44void* dll_open(const std::string& file) { 45#ifdef HAS_DLFCN_H 46 if (MiniZinc::FileUtils::is_absolute(file)) { 47 return dlopen(file.c_str(), RTLD_NOW); 48 } 49 if (void* so = dlopen(("lib" + file + ".so").c_str(), RTLD_NOW)) { 50 return so; 51 } 52 return dlopen(("lib" + file + ".jnilib").c_str(), RTLD_NOW); 53#else 54 if (MiniZinc::FileUtils::is_absolute(file)) { 55 return LoadLibrary(file.c_str()); 56 } else { 57 return LoadLibrary((file + ".dll").c_str()); 58 } 59#endif 60} 61void* dll_sym(void* dll, const char* sym) { 62#ifdef HAS_DLFCN_H 63 void* ret = dlsym(dll, sym); 64#else 65 void* ret = GetProcAddress((HMODULE)dll, sym); 66#endif 67 if (ret == NULL) 68 throw MiniZinc::InternalError("cannot load symbol " + string(sym) + " from CPLEX dll"); 69 return ret; 70} 71void dll_close(void* dll) { 72#ifdef HAS_DLFCN_H 73 dlclose(dll); 74#else 75 FreeLibrary((HMODULE)dll); 76#endif 77} 78} // namespace 79 80#endif 81 82const vector<string>& CPLEXDLLs(void) { 83 static const vector<string> sCPLEXDLLs = {"cplex1290", "cplex1280", "cplex1270"}; 84 return sCPLEXDLLs; 85} 86 87void MIP_cplex_wrapper::checkDLL() { 88#ifdef CPLEX_PLUGIN 89 _cplex_dll = NULL; 90 if (options->sCPLEXDLL.size()) { 91 _cplex_dll = dll_open(options->sCPLEXDLL.c_str()); 92 } else { 93 for (const auto& s : CPLEXDLLs()) { 94 _cplex_dll = dll_open(s.c_str()); 95 if (NULL != _cplex_dll) { 96 break; 97 } 98 } 99 } 100 101 if (_cplex_dll == NULL) { 102 if (options->sCPLEXDLL.empty()) { 103 throw MiniZinc::InternalError("cannot load cplex dll, specify --cplex-dll"); 104 } else { 105 throw MiniZinc::InternalError("cannot load cplex dll `" + options->sCPLEXDLL + "'"); 106 } 107 } 108 109 *(void**)(&dll_CPXaddfuncdest) = dll_sym(_cplex_dll, "CPXaddfuncdest"); 110 *(void**)(&dll_CPXaddindconstr) = dll_sym(_cplex_dll, "CPXaddindconstr"); 111 *(void**)(&dll_CPXaddlazyconstraints) = dll_sym(_cplex_dll, "CPXaddlazyconstraints"); 112 *(void**)(&dll_CPXaddmipstarts) = dll_sym(_cplex_dll, "CPXaddmipstarts"); 113 *(void**)(&dll_CPXaddrows) = dll_sym(_cplex_dll, "CPXaddrows"); 114 *(void**)(&dll_CPXaddusercuts) = dll_sym(_cplex_dll, "CPXaddusercuts"); 115 *(void**)(&dll_CPXchgbds) = dll_sym(_cplex_dll, "CPXchgbds"); 116 *(void**)(&dll_CPXchgmipstarts) = dll_sym(_cplex_dll, "CPXchgmipstarts"); 117 *(void**)(&dll_CPXchgobjsen) = dll_sym(_cplex_dll, "CPXchgobjsen"); 118 *(void**)(&dll_CPXcloseCPLEX) = dll_sym(_cplex_dll, "CPXcloseCPLEX"); 119 *(void**)(&dll_CPXcreateprob) = dll_sym(_cplex_dll, "CPXcreateprob"); 120 *(void**)(&dll_CPXcutcallbackadd) = dll_sym(_cplex_dll, "CPXcutcallbackadd"); 121 *(void**)(&dll_CPXfreeprob) = dll_sym(_cplex_dll, "CPXfreeprob"); 122 *(void**)(&dll_CPXgetbestobjval) = dll_sym(_cplex_dll, "CPXgetbestobjval"); 123 *(void**)(&dll_CPXgetcallbackincumbent) = dll_sym(_cplex_dll, "CPXgetcallbackincumbent"); 124 *(void**)(&dll_CPXgetcallbackinfo) = dll_sym(_cplex_dll, "CPXgetcallbackinfo"); 125 *(void**)(&dll_CPXgetcallbacknodeinfo) = dll_sym(_cplex_dll, "CPXgetcallbacknodeinfo"); 126 *(void**)(&dll_CPXgetcallbacknodex) = dll_sym(_cplex_dll, "CPXgetcallbacknodex"); 127 *(void**)(&dll_CPXgetchannels) = dll_sym(_cplex_dll, "CPXgetchannels"); 128 *(void**)(&dll_CPXgetdettime) = dll_sym(_cplex_dll, "CPXgetdettime"); 129 *(void**)(&dll_CPXgeterrorstring) = dll_sym(_cplex_dll, "CPXgeterrorstring"); 130 *(void**)(&dll_CPXgetmipstartindex) = dll_sym(_cplex_dll, "CPXgetmipstartindex"); 131 *(void**)(&dll_CPXgetnodecnt) = dll_sym(_cplex_dll, "CPXgetnodecnt"); 132 *(void**)(&dll_CPXgetnodeleftcnt) = dll_sym(_cplex_dll, "CPXgetnodeleftcnt"); 133 *(void**)(&dll_CPXgetnumcols) = dll_sym(_cplex_dll, "CPXgetnumcols"); 134 *(void**)(&dll_CPXgetnumrows) = dll_sym(_cplex_dll, "CPXgetnumrows"); 135 *(void**)(&dll_CPXgetobjsen) = dll_sym(_cplex_dll, "CPXgetobjsen"); 136 *(void**)(&dll_CPXgetobjval) = dll_sym(_cplex_dll, "CPXgetobjval"); 137 *(void**)(&dll_CPXgetsolnpoolnumsolns) = dll_sym(_cplex_dll, "CPXgetsolnpoolnumsolns"); 138 *(void**)(&dll_CPXgetstat) = dll_sym(_cplex_dll, "CPXgetstat"); 139 *(void**)(&dll_CPXgetstatstring) = dll_sym(_cplex_dll, "CPXgetstatstring"); 140 *(void**)(&dll_CPXgettime) = dll_sym(_cplex_dll, "CPXgettime"); 141 *(void**)(&dll_CPXgetx) = dll_sym(_cplex_dll, "CPXgetx"); 142 *(void**)(&dll_CPXmipopt) = dll_sym(_cplex_dll, "CPXmipopt"); 143 *(void**)(&dll_CPXnewcols) = dll_sym(_cplex_dll, "CPXnewcols"); 144 *(void**)(&dll_CPXopenCPLEX) = dll_sym(_cplex_dll, "CPXopenCPLEX"); 145 *(void**)(&dll_CPXreadcopyparam) = dll_sym(_cplex_dll, "CPXreadcopyparam"); 146 *(void**)(&dll_CPXsetdblparam) = dll_sym(_cplex_dll, "CPXsetdblparam"); 147 *(void**)(&dll_CPXsetinfocallbackfunc) = dll_sym(_cplex_dll, "CPXsetinfocallbackfunc"); 148 *(void**)(&dll_CPXsetintparam) = dll_sym(_cplex_dll, "CPXsetintparam"); 149 *(void**)(&dll_CPXsetlazyconstraintcallbackfunc) = 150 dll_sym(_cplex_dll, "CPXsetlazyconstraintcallbackfunc"); 151 *(void**)(&dll_CPXsetusercutcallbackfunc) = dll_sym(_cplex_dll, "CPXsetusercutcallbackfunc"); 152 *(void**)(&dll_CPXversion) = dll_sym(_cplex_dll, "CPXversion"); 153 *(void**)(&dll_CPXwriteparam) = dll_sym(_cplex_dll, "CPXwriteparam"); 154 *(void**)(&dll_CPXwriteprob) = dll_sym(_cplex_dll, "CPXwriteprob"); 155 156#else 157 158 dll_CPXaddfuncdest = CPXaddfuncdest; 159 dll_CPXaddindconstr = CPXaddindconstr; 160 dll_CPXaddlazyconstraints = CPXaddlazyconstraints; 161 dll_CPXaddmipstarts = CPXaddmipstarts; 162 dll_CPXaddrows = CPXaddrows; 163 dll_CPXaddusercuts = CPXaddusercuts; 164 dll_CPXchgbds = CPXchgbds; 165 dll_CPXchgmipstarts = CPXchgmipstarts; 166 dll_CPXchgobjsen = CPXchgobjsen; 167 dll_CPXcloseCPLEX = CPXcloseCPLEX; 168 dll_CPXcreateprob = CPXcreateprob; 169 dll_CPXcutcallbackadd = CPXcutcallbackadd; 170 dll_CPXfreeprob = CPXfreeprob; 171 dll_CPXgetbestobjval = CPXgetbestobjval; 172 dll_CPXgetcallbackincumbent = CPXgetcallbackincumbent; 173 dll_CPXgetcallbackinfo = CPXgetcallbackinfo; 174 dll_CPXgetcallbacknodeinfo = CPXgetcallbacknodeinfo; 175 dll_CPXgetcallbacknodex = CPXgetcallbacknodex; 176 dll_CPXgetchannels = CPXgetchannels; 177 dll_CPXgetdettime = CPXgetdettime; 178 dll_CPXgeterrorstring = CPXgeterrorstring; 179 dll_CPXgetmipstartindex = CPXgetmipstartindex; 180 dll_CPXgetnodecnt = CPXgetnodecnt; 181 dll_CPXgetnodeleftcnt = CPXgetnodeleftcnt; 182 dll_CPXgetnumcols = CPXgetnumcols; 183 dll_CPXgetnumrows = CPXgetnumrows; 184 dll_CPXgetobjsen = CPXgetobjsen; 185 dll_CPXgetobjval = CPXgetobjval; 186 dll_CPXgetsolnpoolnumsolns = CPXgetsolnpoolnumsolns; 187 dll_CPXgetstat = CPXgetstat; 188 dll_CPXgetstatstring = CPXgetstatstring; 189 dll_CPXgettime = CPXgettime; 190 dll_CPXgetx = CPXgetx; 191 dll_CPXmipopt = CPXmipopt; 192 dll_CPXnewcols = CPXnewcols; 193 dll_CPXopenCPLEX = CPXopenCPLEX; 194 dll_CPXreadcopyparam = CPXreadcopyparam; 195 dll_CPXsetdblparam = CPXsetdblparam; 196 dll_CPXsetinfocallbackfunc = CPXsetinfocallbackfunc; 197 dll_CPXsetintparam = CPXsetintparam; 198 dll_CPXsetlazyconstraintcallbackfunc = CPXsetlazyconstraintcallbackfunc; 199 dll_CPXsetusercutcallbackfunc = CPXsetusercutcallbackfunc; 200 dll_CPXversion = CPXversion; 201 dll_CPXwriteparam = CPXwriteparam; 202 dll_CPXwriteprob = CPXwriteprob; 203 204#endif 205} 206 207string MIP_cplex_wrapper::getDescription(MiniZinc::SolverInstanceBase::Options* opt) { 208 string v = "MIP wrapper for IBM ILOG CPLEX "; 209 int status; 210 Options def_options; 211 Options* options = opt ? static_cast<Options*>(opt) : &def_options; 212 try { 213 MIP_cplex_wrapper mcw(options); 214 CPXENVptr env = mcw.dll_CPXopenCPLEX(&status); 215 if (env) { 216 v += mcw.dll_CPXversion(env); 217 status = mcw.dll_CPXcloseCPLEX(&env); 218 } else 219 v += "[?? ...cannot open CPLEX env to query version]"; 220 } catch (MiniZinc::InternalError&) { 221 v += "[?? ...cannot open CPLEX env to query version]"; 222 } 223 v += " Compiled " __DATE__ " " __TIME__; 224 return v; 225} 226 227string MIP_cplex_wrapper::getVersion(MiniZinc::SolverInstanceBase::Options* opt) { 228 string v; 229 int status; 230 Options def_options; 231 Options* options = opt ? static_cast<Options*>(opt) : &def_options; 232 try { 233 MIP_cplex_wrapper mcw(options); 234 CPXENVptr env = mcw.dll_CPXopenCPLEX(&status); 235 if (env) { 236 v += mcw.dll_CPXversion(env); 237 status = mcw.dll_CPXcloseCPLEX(&env); 238 } else { 239 v += "<unknown version>"; 240 } 241 } catch (MiniZinc::InternalError&) { 242 v += "<unknown version>"; 243 } 244 return v; 245} 246 247std::string MIP_cplex_wrapper::needDllFlag(void) { 248 int status; 249 Options options; 250 try { 251 MIP_cplex_wrapper mcw(&options); 252 CPXENVptr env = mcw.dll_CPXopenCPLEX(&status); 253 if (env) { 254 return ""; 255 } 256 } catch (MiniZinc::InternalError&) { 257 } 258 return "--cplex-dll"; 259} 260 261string MIP_cplex_wrapper::getId() { return "cplex"; } 262 263string MIP_cplex_wrapper::getName() { return "CPLEX"; } 264 265vector<string> MIP_cplex_wrapper::getTags() { return {"mip", "float", "api"}; } 266 267vector<string> MIP_cplex_wrapper::getStdFlags() { return {"-a", "-n", "-p", "-s"}; } 268 269void MIP_cplex_wrapper::Options::printHelp(ostream& os) { 270 os << "IBM ILOG CPLEX MIP wrapper options:" 271 << std::endl 272 // -s print statistics 273 // << " --readParam <file> read CPLEX parameters from file 274 // << "--writeParam <file> write CPLEX parameters to file 275 // << "--tuneParam instruct CPLEX to tune parameters instead of solving 276 << " --mipfocus <n>\n 1: feasibility, 2: optimality, 3: move bound (default is 0, " 277 "balanced)" 278 << std::endl 279 << " -a\n print intermediate solutions (use for optimization problems only TODO)" 280 << std::endl 281 << " -p <N>\n use N threads, default: 1" 282 << std::endl 283 // << " --nomippresolve disable MIP presolving NOT IMPL" << std::endl 284 << " --solver-time-limit <N>\n stop search after N milliseconds wall time" << std::endl 285 << " -n <N>, --num-solutions <N>\n" 286 " stop search after N solutions" 287 << std::endl 288 << " --workmem <N>, --nodefilestart <N>\n" 289 " maximal RAM for working memory used before writing to node file, GB, default: 3" 290 << std::endl 291 << " --writeModel <file>\n write model to <file> (.lp, .mps, .sav, ...)" << std::endl 292 << " --readParam <file>\n read CPLEX parameters from file" << std::endl 293 << " --writeParam <file>\n write CPLEX parameters to file" 294 << std::endl 295 // << " --tuneParam instruct CPLEX to tune parameters instead of solving NOT IMPL" 296 297 << " --absGap <n>\n absolute gap |primal-dual| to stop" << std::endl 298 << " --relGap <n>\n relative gap |primal-dual|/<solver-dep> to stop. Default 1e-8, set <0 " 299 "to use backend's default" 300 << std::endl 301 << " --intTol <n>\n integrality tolerance for a variable. Default 1e-8" << std::endl 302 << "\n --cplex-dll <file> or <basename>\n CPLEX DLL, or base name, such as cplex1280, " 303 "when using plugin. Default range tried: " 304 << CPLEXDLLs().front() << " .. " << CPLEXDLLs().back() 305 << std::endl 306 // << " --objDiff <n> objective function discretization. Default 1.0" << std::endl 307 308 << std::endl; 309} 310 311bool MIP_cplex_wrapper::Options::processOption(int& i, std::vector<std::string>& argv) { 312 MiniZinc::CLOParser cop(i, argv); 313 if (string(argv[i]) == "-a" || string(argv[i]) == "--all" || 314 string(argv[i]) == "--all-solutions") { 315 flag_all_solutions = true; 316 } else if (string(argv[i]) == "-f") { 317 // std::cerr << " Flag -f: ignoring fixed strategy anyway." << std::endl; 318 } else if (cop.get("--mipfocus --mipFocus --MIPFocus --MIPfocus", &nMIPFocus)) { 319 } else if (cop.get("--writeModel", &sExportModel)) { 320 } else if (cop.get("-p", &nThreads)) { 321 } else if (cop.get("--solver-time-limit", &nTimeout)) { 322 } else if (cop.get("-n --num-solutions", &nSolLimit)) { 323 } else if (cop.get("--workmem --nodefilestart", &nWorkMemLimit)) { 324 } else if (cop.get("--readParam", &sReadParams)) { 325 } else if (cop.get("--writeParam", &sWriteParams)) { 326 } else if (cop.get("--absGap", &absGap)) { 327 } else if (cop.get("--relGap", &relGap)) { 328 } else if (cop.get("--intTol", &intTol)) { 329 } else if (cop.get("--cplex-dll", &sCPLEXDLL)) { 330 // } else if ( cop.get( "--objDiff", &objDiff ) ) { 331 } else 332 return false; 333 return true; 334} 335 336void MIP_cplex_wrapper::wrap_assert(bool cond, string msg, bool fTerm) { 337 if (!cond) { 338 strcpy(cplex_buffer, "[NO ERROR STRING GIVEN]"); 339 dll_CPXgeterrorstring(env, status, cplex_buffer); 340 string msgAll = (" MIP_cplex_wrapper runtime error: " + msg + " " + cplex_buffer); 341 cerr << msgAll << endl; 342 if (fTerm) { 343 cerr << "TERMINATING." << endl; 344 throw runtime_error(msgAll); 345 } 346 } 347} 348 349void MIP_cplex_wrapper::openCPLEX() { 350 checkDLL(); 351 cbui.wrapper = this; 352 /// Cleanup first. 353 // cleanup(); 354 /* Initialize the CPLEX environment */ 355 env = dll_CPXopenCPLEX(&status); 356 /* If an error occurs, the status value indicates the reason for 357 failure. A call to CPXgeterrorstring will produce the text of 358 the error message. Note that CPXopenCPLEX produces no output, 359 so the only way to see the cause of the error is to use 360 CPXgeterrorstring. For other CPLEX routines, the errors will 361 be seen if the CPXPARAM_ScreenOutput indicator is set to CPX_ON. */ 362 wrap_assert(env, "Could not open CPLEX environment."); 363 /* Create the problem. */ 364 lp = dll_CPXcreateprob(env, &status, "MIP_cplex_wrapper"); 365 /* A returned pointer of NULL may mean that not enough memory 366 was available or there was some other problem. In the case of 367 failure, an error message will have been written to the error 368 channel from inside CPLEX. In this example, the setting of 369 the parameter CPXPARAM_ScreenOutput causes the error message to 370 appear on stdout. */ 371 wrap_assert(lp, "Failed to create LP."); 372} 373 374void MIP_cplex_wrapper::closeCPLEX() { 375 /// Freeing the problem can be slow both in C and C++, see IBM forums. Skipping. 376 /* Free up the problem as allocated by CPXcreateprob, if necessary */ 377 // if ( lp != NULL ) { 378 // status = CPXfreeprob (env, &lp); 379 // cplex_wrap_assert ( !status, "CPXfreeprob failed." ); 380 // } 381 lp = 0; 382 /* Free up the CPLEX environment, if necessary */ 383 if (env != NULL) { 384 status = dll_CPXcloseCPLEX(&env); 385 wrap_assert(!status, "Could not close CPLEX environment."); 386 } 387 /// and at last: 388// MIP_wrapper::cleanup(); 389#ifdef CPLEX_PLUGIN 390// dll_close(cplex_dll); 391#endif 392} 393 394void MIP_cplex_wrapper::doAddVars(size_t n, double* obj, double* lb, double* ub, 395 MIP_wrapper::VarType* vt, string* names) { 396 /// Convert var types: 397 vector<char> ctype(n); 398 vector<char*> pcNames(n); 399 for (size_t i = 0; i < n; ++i) { 400 pcNames[i] = (char*)names[i].c_str(); 401 switch (vt[i]) { 402 case REAL: 403 ctype[i] = CPX_CONTINUOUS; 404 break; 405 case INT: 406 ctype[i] = CPX_INTEGER; 407 break; 408 case BINARY: 409 ctype[i] = CPX_BINARY; 410 break; 411 default: 412 throw runtime_error(" MIP_wrapper: unknown variable type"); 413 } 414 } 415 status = dll_CPXnewcols(env, lp, n, obj, lb, ub, &ctype[0], &pcNames[0]); 416 wrap_assert(!status, "Failed to declare variables."); 417} 418 419static char getCPLEXConstrSense(MIP_wrapper::LinConType sense) { 420 switch (sense) { 421 case MIP_wrapper::LQ: 422 return 'L'; 423 case MIP_wrapper::EQ: 424 return 'E'; 425 case MIP_wrapper::GQ: 426 return 'G'; 427 default: 428 throw runtime_error(" MIP_cplex_wrapper: unknown constraint type"); 429 } 430} 431 432void MIP_cplex_wrapper::addRow(int nnz, int* rmatind, double* rmatval, 433 MIP_wrapper::LinConType sense, double rhs, int mask, 434 string rowName) { 435 /// Convert var types: 436 char ssense = getCPLEXConstrSense(sense); 437 const int ccnt = 0; 438 const int rcnt = 1; 439 const int rmatbeg[] = {0}; 440 char* pRName = (char*)rowName.c_str(); 441 if (MaskConsType_Normal & mask) { 442 status = dll_CPXaddrows(env, lp, ccnt, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval, 443 NULL, &pRName); 444 wrap_assert(!status, "Failed to add constraint."); 445 } 446 if (MaskConsType_Usercut & mask) { 447 status = 448 dll_CPXaddusercuts(env, lp, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval, &pRName); 449 wrap_assert(!status, "Failed to add usercut."); 450 } 451 if (MaskConsType_Lazy & mask) { 452 status = dll_CPXaddlazyconstraints(env, lp, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval, 453 &pRName); 454 wrap_assert(!status, "Failed to add lazy constraint."); 455 } 456} 457 458void MIP_cplex_wrapper::addIndicatorConstraint(int iBVar, int bVal, int nnz, int* rmatind, 459 double* rmatval, MIP_wrapper::LinConType sense, 460 double rhs, string rowName) { 461 wrap_assert(0 <= bVal && 1 >= bVal, "mzn-cplex: addIndicatorConstraint: bVal not 0/1"); 462 char ssense = getCPLEXConstrSense(sense); 463 status = dll_CPXaddindconstr(env, lp, iBVar, 1 - bVal, nnz, rhs, ssense, rmatind, rmatval, 464 rowName.c_str()); 465 wrap_assert(!status, "Failed to add indicator constraint."); 466} 467 468bool MIP_cplex_wrapper::addWarmStart(const std::vector<VarId>& vars, 469 const std::vector<double> vals) { 470 assert(vars.size() == vals.size()); 471 const char* sMSName = "MZNMS"; 472 int msindex = -1; 473 const int beg = 0; 474 /// Check if we already added a start 475 status = dll_CPXgetmipstartindex(env, lp, sMSName, &msindex); 476 if (status) { // not existent 477 // status = dll_CPXaddmipstarts (env, lp, mcnt, nzcnt, beg, varindices, 478 // values, effortlevel, mipstartname); 479 status = dll_CPXaddmipstarts(env, lp, 1, vars.size(), &beg, vars.data(), vals.data(), nullptr, 480 (char**)&sMSName); 481 wrap_assert(!status, "Failed to add warm start."); 482 } else { 483 // status = dll_CPXchgmipstarts (env, lp, mcnt, mipstartindices, nzcnt, beg, varindices, values, 484 // effortlevel); 485 status = dll_CPXchgmipstarts(env, lp, 1, &msindex, vars.size(), &beg, vars.data(), vals.data(), 486 nullptr); 487 wrap_assert(!status, "Failed to extend warm start."); 488 } 489 return true; 490} 491 492void MIP_cplex_wrapper::setVarBounds(int iVar, double lb, double ub) { 493 wrap_assert(lb <= ub, "mzn-cplex: setVarBounds: lb>ub"); 494 char cl = 'L', cu = 'U'; 495 status = dll_CPXchgbds(env, lp, 1, &iVar, &cl, &lb); 496 wrap_assert(!status, "Failed to set lower bound."); 497 status = dll_CPXchgbds(env, lp, 1, &iVar, &cu, &ub); 498 wrap_assert(!status, "Failed to set upper bound."); 499} 500 501void MIP_cplex_wrapper::setVarLB(int iVar, double lb) { 502 char cl = 'L'; 503 status = dll_CPXchgbds(env, lp, 1, &iVar, &cl, &lb); 504 wrap_assert(!status, "Failed to set lower bound."); 505} 506 507void MIP_cplex_wrapper::setVarUB(int iVar, double ub) { 508 char cu = 'U'; 509 status = dll_CPXchgbds(env, lp, 1, &iVar, &cu, &ub); 510 wrap_assert(!status, "Failed to set upper bound."); 511} 512 513/// SolutionCallback ------------------------------------------------------------------------ 514/// CPLEX ensures thread-safety 515static int CPXPUBLIC solcallback(CPXCENVptr env, void* cbdata, int wherefrom, void* cbhandle) { 516 int status = 0; 517 518 MIP_wrapper::CBUserInfo* info = (MIP_wrapper::CBUserInfo*)cbhandle; 519 MIP_cplex_wrapper* cw = static_cast<MIP_cplex_wrapper*>(info->wrapper); 520 int hasincumbent = 0; 521 int newincumbent = 0; 522 double objVal; 523 524 status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_NODE_COUNT, 525 &info->pOutput->nNodes); 526 if (status) goto TERMINATE; 527 528 status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_NODES_LEFT, 529 &info->pOutput->nOpenNodes); 530 if (status) goto TERMINATE; 531 532 status = 533 cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_MIP_FEAS, &hasincumbent); 534 if (status) goto TERMINATE; 535 536 if (hasincumbent) { 537 status = 538 cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_BEST_INTEGER, &objVal); 539 if (status) goto TERMINATE; 540 541 if (fabs(info->pOutput->objVal - objVal) > 1e-12 * (1.0 + fabs(objVal))) { 542 newincumbent = 1; 543 info->pOutput->objVal = objVal; 544 info->pOutput->status = MIP_wrapper::SAT; 545 info->pOutput->statusName = "feasible from a callback"; 546 } 547 } 548 549 // if ( nodecnt >= info->lastlog + 100 || newincumbent ) { 550 // double walltime; 551 // double dettime; 552 553 status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_BEST_REMAINING, 554 &info->pOutput->bestBound); 555 // if ( status ) goto TERMINATE; 556 557 // status = dll_CPXgettime (env, &walltime); 558 // if ( status ) goto TERMINATE; 559 // 560 // status = dll_CPXgetdettime (env, &dettime); 561 // if ( status ) goto TERMINATE; 562 // 563 // } 564 565 if (newincumbent) { 566 assert(info->pOutput->x); 567 status = cw->dll_CPXgetcallbackincumbent(env, cbdata, wherefrom, (double*)info->pOutput->x, 0, 568 info->pOutput->nCols - 1); 569 if (status) goto TERMINATE; 570 571 info->pOutput->dWallTime = 572 std::chrono::duration<double>(std::chrono::steady_clock::now() - info->pOutput->dWallTime0) 573 .count(); 574 info->pOutput->dCPUTime = double(std::clock() - info->pOutput->cCPUTime0) / CLOCKS_PER_SEC; 575 576 /// Call the user function: 577 if (info->solcbfn) (*info->solcbfn)(*info->pOutput, info->psi); 578 info->printed = true; 579 } 580 581TERMINATE: 582 return (status); 583 584} /* END logcallback */ 585// end SolutionCallback --------------------------------------------------------------------- 586 587/// Cut callbacks, mostly copied from admipex5.c, CPLEX 12.6.3 588/* The following macro defines the smallest improvement 589 on the value of the objective function that is required 590 when adding user cuts from within a callback. 591 If the improvement on the value of the ojective function 592 is not large enough, the callback will abort the cut loop. */ 593 594#define EPSOBJ 0.1 595 596/* The following structure will hold the information we need to 597 pass to the cut callback function */ 598 599struct cutinfo { 600 CPXLPptr lp; 601 int numcols; 602 int num; 603 double* x; 604 int* beg; 605 int* ind; 606 double* val; 607 double* rhs; 608 int nodeid; 609 double nodeobjval; 610 int objsen; 611 MIP_wrapper::CBUserInfo* info = 0; 612}; 613typedef struct cutinfo CUTINFO, *CUTINFOptr; 614 615/* Init information on the node objval for the user cut callback */ 616 617static void initnodeobjvalinfo(MIP_cplex_wrapper* cw, CPXENVptr env, CPXLPptr lp, 618 CUTINFOptr cutinfo) { 619 cutinfo->nodeid = -1; 620 cutinfo->nodeobjval = 0.0; 621 cutinfo->objsen = cw->dll_CPXgetobjsen(env, lp); 622 if (cutinfo->objsen == CPX_MIN) 623 cutinfo->objsen = 1; 624 else 625 cutinfo->objsen = -1; 626} /* END initnodeobjvalinfo */ 627 628static int CPXPUBLIC myusercutcallback(CPXCENVptr env, void* cbdata, int wherefrom, void* cbhandle, 629 int* useraction_p) { 630 int status = 0; 631 632 CUTINFOptr cutinfo = (CUTINFOptr)cbhandle; 633 634 // int numcols = cutinfo->numcols; 635 // int numcuts = cutinfo->num; 636 // double *x = cutinfo->x; 637 // int *beg = cutinfo->beg; 638 // int *ind = cutinfo->ind; 639 // double *val = cutinfo->val; 640 // double *rhs = cutinfo->rhs; 641 // int *cutind = NULL; 642 // double *cutval = NULL; 643 // double cutvio; 644 int addedcuts = 0; 645 // int i, j, k; //, cutnz; 646 MIP_wrapper::CBUserInfo* info = cutinfo->info; 647 MIP_cplex_wrapper* cw = static_cast<MIP_cplex_wrapper*>(info->wrapper); 648 // double *x = info->pCutOutput->x; 649 650 *useraction_p = CPX_CALLBACK_DEFAULT; 651 652 /* If we are called as a user cut callback, decide 653 first if we want to add cuts or abort the cut loop. 654 When adding user cuts with purgeable flag set to 655 CPX_USECUT_PURGE or CPX_USECUT_FILTER, it is important 656 to avoid the possibility of an infinite cut loop, where 657 the same cuts are added to the LP and then immediately 658 purged at every cut pass. Such a situation can be avoided, 659 for instance, by applying a tailing off criterion and aborting 660 the cut loop where no progress in the objval is observed. 661 Note, however, that the same approach must not be applied 662 with lazy constraints. In this case, if lazy constraints are 663 added with purgeable flag set to CPX_USECUT_PURGE, adding 664 the same lazy constraint more than once could be required 665 to ensure the correctness of the final result. */ 666 667 bool fMIPSol = true; 668 if (wherefrom == CPX_CALLBACK_MIP_CUT_LOOP || wherefrom == CPX_CALLBACK_MIP_CUT_LAST) { 669 int oldnodeid = cutinfo->nodeid; 670 double oldnodeobjval = cutinfo->nodeobjval; 671 672 fMIPSol = false; 673 674 /* Retrieve nodeid and node objval of the current node */ 675 676 status = cw->dll_CPXgetcallbacknodeinfo(env, cbdata, wherefrom, 0, 677 CPX_CALLBACK_INFO_NODE_SEQNUM, &cutinfo->nodeid); 678 if (status) { 679 fprintf(stderr, "Failed to get node id.\n"); 680 goto TERMINATE; 681 } 682 683 status = cw->dll_CPXgetcallbacknodeinfo(env, cbdata, wherefrom, 0, 684 CPX_CALLBACK_INFO_NODE_OBJVAL, &cutinfo->nodeobjval); 685 if (status) { 686 fprintf(stderr, "Failed to get node objval.\n"); 687 goto TERMINATE; 688 } 689 690 /* Abort the cut loop if we are stuck at the same node 691 as before and there is no progress in the node objval */ 692 693 if (oldnodeid == cutinfo->nodeid) { 694 double objchg = (cutinfo->nodeobjval - oldnodeobjval); 695 /* Multiply objchg by objsen to normalize 696 the change in the objective function to 697 the case of a minimization problem */ 698 objchg *= cutinfo->objsen; 699 if (objchg <= EPSOBJ) { 700 *useraction_p = CPX_CALLBACK_ABORT_CUT_LOOP; 701 goto TERMINATE; 702 } 703 } 704 } 705 706 /* If we reached this point, we are 707 .. in a lazyconstraint callback, or 708 .. in a user cut callback, and cuts seem to help 709 improving the node objval. 710 In both cases, we retrieve the x solution and 711 look for violated cuts. */ 712 713 if (info->cutcbfn) { // if cut handler given 714 MIP_wrapper::Output outpRlx; 715 outpRlx.x = info->pOutput->x; // using the sol output storage TODO? 716 outpRlx.nCols = info->pOutput->nCols; 717 assert(outpRlx.x && outpRlx.nCols); 718 status = cw->dll_CPXgetcallbacknodex(env, cbdata, wherefrom, (double*)outpRlx.x, 0, 719 outpRlx.nCols - 1); 720 if (status) { 721 fprintf(stderr, "Cut callback: failed to get node solution.\n"); 722 goto TERMINATE; 723 } 724 MIP_wrapper::CutInput cutInput; 725 info->cutcbfn(outpRlx, cutInput, info->psi, fMIPSol); 726 static int nCuts = 0; 727 nCuts += cutInput.size(); 728 // if ( cutInput.size() ) 729 // cerr << "\n N CUTS: " << nCuts << endl; 730 for (auto& cd : cutInput) { 731 if (!(cd.mask & (MIP_wrapper::MaskConsType_Usercut | MIP_wrapper::MaskConsType_Lazy))) 732 throw runtime_error("Cut callback: should be user/lazy"); 733 /* Use a cut violation tolerance of 0.01 */ 734 if (true) { // cutvio > 0.01 ) { 735 status = cw->dll_CPXcutcallbackadd(env, cbdata, wherefrom, cd.rmatind.size(), cd.rhs, 736 getCPLEXConstrSense(cd.sense), cd.rmatind.data(), 737 cd.rmatval.data(), 738 CPX_USECUT_FORCE); // PURGE? 739 if (status) { 740 fprintf(stderr, "CPLEX callback: failed to add cut.\n"); 741 goto TERMINATE; 742 } 743 addedcuts++; 744 } 745 } 746 } 747 748 /* Tell CPLEX that cuts have been created */ 749 if (addedcuts > 0) { 750 *useraction_p = CPX_CALLBACK_SET; 751 } 752 753TERMINATE: 754 755 return (status); 756 757} /* END myusercutcallback */ 758 759// ----------------- END Cut callbacks ------------------ 760 761MIP_cplex_wrapper::Status MIP_cplex_wrapper::convertStatus(int cplexStatus) { 762 Status s = Status::UNKNOWN; 763 /* Converting the status. */ 764 switch (cplexStatus) { 765 case CPXMIP_OPTIMAL: 766 s = Status::OPT; 767 wrap_assert(dll_CPXgetsolnpoolnumsolns(env, lp), "Optimality reported but pool empty?", 768 false); 769 break; 770 case CPXMIP_INFEASIBLE: 771 s = Status::UNSAT; 772 break; 773 // case CPXMIP_OPTIMAL_INFEAS: 774 case CPXMIP_INForUNBD: 775 s = Status::UNSATorUNBND; 776 break; 777 case CPXMIP_SOL_LIM: 778 case CPXMIP_NODE_LIM_FEAS: 779 case CPXMIP_TIME_LIM_FEAS: 780 case CPXMIP_FAIL_FEAS: 781 case CPXMIP_MEM_LIM_FEAS: 782 case CPXMIP_ABORT_FEAS: 783 case CPXMIP_FAIL_FEAS_NO_TREE: 784 s = Status::SAT; 785 wrap_assert(dll_CPXgetsolnpoolnumsolns(env, lp), "Feasibility reported but pool empty?", 786 false); 787 break; 788 case CPXMIP_UNBOUNDED: 789 s = Status::UNBND; 790 break; 791 // case CPXMIP_ABORT_INFEAS: 792 case CPXMIP_FAIL_INFEAS: 793 s = Status::__ERROR; 794 break; 795 default: 796 // case CPXMIP_OPTIMAL_TOL: 797 // case CPXMIP_ABORT_RELAXATION_UNBOUNDED: 798 if (dll_CPXgetsolnpoolnumsolns(env, lp)) 799 s = Status::SAT; 800 else 801 s = Status::UNKNOWN; 802 } 803 return s; 804} 805 806void msgfunction(void* handle, const char* msg_string) { cerr << msg_string << flush; } 807 808void MIP_cplex_wrapper::solve() { // Move into ancestor? 809 810 /////////////// Last-minute solver options ////////////////// 811 if (options->flag_all_solutions && 0 == nProbType) 812 cerr << "WARNING. --all-solutions for SAT problems not implemented." << endl; 813 // Before all manual params ??? 814 if (options->sReadParams.size()) { 815 status = dll_CPXreadcopyparam(env, options->sReadParams.c_str()); 816 wrap_assert(!status, "Failed to read CPLEX parameters.", false); 817 } 818 819 /* Turn on output to the screen */ 820 if (fVerbose) { 821 CPXCHANNELptr chnl[4]; 822 dll_CPXgetchannels(env, &chnl[0], &chnl[1], &chnl[2], &chnl[3]); 823 for (int i = 0; i < 3; ++i) { 824 status = dll_CPXaddfuncdest(env, chnl[i], nullptr, msgfunction); 825 } 826 // status = dll_CPXsetintparam(env, CPXPARAM_ScreenOutput, 827 // fVerbose ? CPX_ON : CPX_OFF); // also when flag_all_solutions? TODO 828 // wrap_assert(!status, " CPLEX Warning: Failure to switch screen indicator.", false); 829 } 830 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Display, 831 fVerbose ? 2 : 0); // also when flag_all_solutions? TODO 832 wrap_assert(!status, " CPLEX Warning: Failure to switch logging.", false); 833 /// Make it wall time by default, 12.8 834 // status = dll_CPXsetintparam (env, CPXPARAM_ClockType, 1); // CPU time 835 // wrap_assert(!status, " CPLEX Warning: Failure to measure CPU time.", false); 836 status = dll_CPXsetintparam(env, CPX_PARAM_MIPCBREDLP, CPX_OFF); // Access original model 837 wrap_assert(!status, " CPLEX Warning: Failure to set access original model in callbacks.", 838 false); 839 if (options->sExportModel.size()) { 840 status = dll_CPXwriteprob(env, lp, options->sExportModel.c_str(), NULL); 841 wrap_assert(!status, "Failed to write LP to disk.", false); 842 } 843 844 /// TODO 845 // if(all_solutions && obj.getImpl()) { 846 // IloNum lastObjVal = (obj.getSense() == IloObjective::Minimize ) ? 847 // _ilocplex->use(SolutionCallback(_iloenv, lastObjVal, *this)); 848 // Turn off CPLEX logging 849 850 if (options->nThreads > 0) { 851 status = dll_CPXsetintparam(env, CPXPARAM_Threads, options->nThreads); 852 wrap_assert(!status, "Failed to set CPXPARAM_Threads.", false); 853 } 854 855 if (options->nTimeout > 0) { 856 status = dll_CPXsetdblparam(env, CPXPARAM_TimeLimit, 857 static_cast<double>(options->nTimeout) / 1000.0); 858 wrap_assert(!status, "Failed to set CPXPARAM_TimeLimit.", false); 859 } 860 if (options->nSolLimit > 0) { 861 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Limits_Solutions, options->nSolLimit); 862 wrap_assert(!status, "Failed to set CPXPARAM_MIP_Limits_Solutions.", false); 863 } 864 if (options->nMIPFocus > 0) { 865 status = dll_CPXsetintparam(env, CPXPARAM_Emphasis_MIP, options->nMIPFocus); 866 wrap_assert(!status, "Failed to set CPXPARAM_Emphasis_MIP.", false); 867 } 868 869 if (options->nWorkMemLimit > 0) { 870 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_File, 3); 871 wrap_assert(!status, "Failed to set CPXPARAM_MIP_Strategy_File.", false); 872 status = 873 dll_CPXsetdblparam(env, CPXPARAM_WorkMem, 1024.0 * options->nWorkMemLimit); // MB in CPLEX 874 wrap_assert(!status, "Failed to set CPXPARAM_WorkMem.", false); 875 } 876 877 if (options->absGap >= 0.0) { 878 status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_AbsMIPGap, options->absGap); 879 wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_AbsMIPGap.", false); 880 } 881 if (options->relGap >= 0.0) { 882 status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_MIPGap, options->relGap); 883 wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_MIPGap.", false); 884 } 885 if (options->intTol >= 0.0) { 886 status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_Integrality, options->intTol); 887 wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_Integrality.", false); 888 } 889 890 // status = dll_CPXsetdblparam (env, CPXPARAM_MIP_Tolerances_ObjDifference, objDiff); 891 // wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_ObjDifference.", false); 892 893 /// Solution callback 894 output.nCols = colObj.size(); 895 x.resize(output.nCols); 896 output.x = &x[0]; 897 if (options->flag_all_solutions && cbui.solcbfn) { 898 status = dll_CPXsetinfocallbackfunc(env, solcallback, &cbui); 899 wrap_assert(!status, "Failed to set solution callback", false); 900 } 901 if (cbui.cutcbfn) { 902 assert(cbui.cutMask & (MaskConsType_Usercut | MaskConsType_Lazy)); 903 if (cbui.cutMask & MaskConsType_Usercut) { 904 // For user cuts, needs to keep some info after presolve 905 if (fVerbose) 906 cerr << " MIP_cplex_wrapper: user cut callback enabled, setting params" << endl; 907 CUTINFO usercutinfo; // THREADS? TODO 908 usercutinfo.info = &cbui; 909 /* Init information on the node objval for the user cut callback */ 910 initnodeobjvalinfo(this, env, lp, &usercutinfo); 911 /* Assure linear mappings between the presolved and original 912 models */ 913 status = dll_CPXsetintparam(env, CPXPARAM_Preprocessing_Linear, 0); 914 wrap_assert(!status, "CPLEX: setting prepro_linear"); 915 /* Turn on traditional search for use with control callbacks */ 916 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_Search, CPX_MIPSEARCH_TRADITIONAL); 917 wrap_assert(!status, "CPLEX: setting traditional search"); 918 /* Let MIP callbacks work on the original model */ 919 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_CallbackReducedLP, CPX_OFF); 920 wrap_assert(!status, "CPLEX: setting callbacks to work on orig model"); 921 /// And 922 /* Set up to use MIP usercut callback */ 923 924 status = dll_CPXsetusercutcallbackfunc(env, myusercutcallback, &usercutinfo); 925 wrap_assert(!status, "CPLEX: setting user cut callback"); 926 } 927 if (cbui.cutMask & MaskConsType_Lazy) { 928 if (fVerbose) 929 cerr << " MIP_cplex_wrapper: lazy cut callback enabled, setting params" << endl; 930 CUTINFO lazyconinfo; 931 lazyconinfo.info = &cbui; 932 /* Init information on the node objval for the user cut callback. 933 No need to initialize the information on the node objval, 934 for the lazy constraint callback, because those information are 935 used only in the user cut callback. */ 936 initnodeobjvalinfo(this, env, lp, &lazyconinfo); 937 /* Assure linear mappings between the presolved and original 938 models */ 939 status = dll_CPXsetintparam(env, CPXPARAM_Preprocessing_Linear, 0); 940 wrap_assert(!status, "CPLEX: setting prepro_linear"); 941 /* Turn on traditional search for use with control callbacks */ 942 // status = dll_CPXsetintparam (env, CPXPARAM_MIP_Strategy_Search, 943 // CPX_MIPSEARCH_TRADITIONAL); 944 // wrap_assert ( !status, "CPLEX: setting traditional search" ); 945 /* Let MIP callbacks work on the original model */ 946 status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_CallbackReducedLP, CPX_OFF); 947 wrap_assert(!status, "CPLEX: setting callbacks to work on orig model"); 948 /* Set up to use MIP lazyconstraint callback. The callback funtion 949 * registered is the same, but the data will be different. */ 950 951 status = dll_CPXsetlazyconstraintcallbackfunc(env, myusercutcallback, &lazyconinfo); 952 wrap_assert(!status, "CPLEX: setting lazy cut callback"); 953 } 954 } 955 956 /// after all modifs 957 if (options->sWriteParams.size()) { 958 status = dll_CPXwriteparam(env, options->sWriteParams.c_str()); 959 wrap_assert(!status, "Failed to write CPLEX parameters.", false); 960 } 961 962 // status = dll_CPXgettime (env, &output.dCPUTime); 963 // wrap_assert(!status, "Failed to get time stamp.", false); 964 cbui.pOutput->dWallTime0 = output.dWallTime0 = std::chrono::steady_clock::now(); 965 cbui.pOutput->cCPUTime0 = std::clock(); 966 967 /* Optimize the problem and obtain solution. */ 968 status = dll_CPXmipopt(env, lp); 969 wrap_assert(!status, "Failed to optimize MIP."); 970 971 output.dWallTime = 972 std::chrono::duration<double>(std::chrono::steady_clock::now() - output.dWallTime0).count(); 973 double tmNow = std::clock(); 974 // status = dll_CPXgettime (env, &tmNow); Buggy in 12.7.1.0 975 wrap_assert(!status, "Failed to get time stamp.", false); 976 output.dCPUTime = (tmNow - cbui.pOutput->cCPUTime0) / CLOCKS_PER_SEC; 977 978 int solstat = dll_CPXgetstat(env, lp); 979 output.status = convertStatus(solstat); 980 output.statusName = dll_CPXgetstatstring(env, solstat, cplex_status_buffer); 981 982 /// Continuing to fill the output object: 983 if (Status::OPT == output.status || Status::SAT == output.status) { 984 status = dll_CPXgetobjval(env, lp, &output.objVal); 985 wrap_assert(!status, "No MIP objective value available."); 986 987 /* The size of the problem should be obtained by asking CPLEX what 988 the actual size is, rather than using what was passed to CPXcopylp. 989 cur_numrows and cur_numcols store the current number of rows and 990 columns, respectively. */ // ?????????????? TODO 991 992 // int cur_numrows = dll_CPXgetnumrows (env, lp); 993 int cur_numcols = dll_CPXgetnumcols(env, lp); 994 assert(cur_numcols == colObj.size()); 995 996 x.resize(cur_numcols); 997 output.x = &x[0]; 998 status = dll_CPXgetx(env, lp, &x[0], 0, cur_numcols - 1); 999 wrap_assert(!status, "Failed to get variable values."); 1000 if (cbui.solcbfn /*&& (!options->flag_all_solutions || !cbui.printed)*/) { 1001 cbui.solcbfn(output, cbui.psi); 1002 } 1003 } 1004 output.bestBound = 1e308; 1005 status = dll_CPXgetbestobjval(env, lp, &output.bestBound); 1006 wrap_assert(!status, "Failed to get the best bound.", false); 1007 output.nNodes = dll_CPXgetnodecnt(env, lp); 1008 output.nOpenNodes = dll_CPXgetnodeleftcnt(env, lp); 1009} 1010 1011void MIP_cplex_wrapper::setObjSense(int s) { 1012 status = dll_CPXchgobjsen(env, lp, -s); // +1 for min in CPLEX 1013 wrap_assert(!status, "Failed to set obj sense."); 1014}