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/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}