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/solvers/MIP/MIP_scip_wrap.hh>
17 #include <minizinc/utils.hh>
18
19 #include <array>
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 using namespace std;
30
31 /// Load SCIP DLL with the given path
ScipPlugin(const std::string & dll)32 ScipPlugin::ScipPlugin(const std::string& dll) : Plugin(dll) { load(); }
33
34 /// Load SCIP DLL with default search paths on Windows
ScipPlugin()35 ScipPlugin::ScipPlugin()
36 : Plugin(
37 #ifdef _WIN32
38 {
39 "libscip", "scip", "C:\\Program Files\\SCIPOptSuite 7.0.1\\bin\\libscip.dll",
40 "C:\\Program Files\\SCIPOptSuite 7.0.0\\bin\\libscip.dll",
41 "C:\\Program Files\\SCIPOptSuite 6.0.2\\bin\\scip.dll",
42 "C:\\Program Files\\SCIPOptSuite 6.0.1\\bin\\scip.dll",
43 "C:\\Program Files\\SCIPOptSuite 6.0.0\\bin\\scip.dll",
44 "C:\\Program Files\\SCIPOptSuite 5.0.1\\bin\\scip.dll",
45 "C:\\Program Files\\SCIPOptSuite 5.0.0\\bin\\scip.dll",
46 "C:\\Program Files\\SCIPOptSuite 4.0.1\\bin\\scip.dll",
47 "C:\\Program Files\\SCIPOptSuite 4.0.0\\bin\\scip.dll",
48 "C:\\Program Files (x86)\\SCIPOptSuite 7.0.1\\bin\\scip.dll",
49 "C:\\Program Files (x86)\\SCIPOptSuite 7.0.0\\bin\\scip.dll",
50 "C:\\Program Files (x86)\\SCIPOptSuite 6.0.2\\bin\\scip.dll",
51 "C:\\Program Files (x86)\\SCIPOptSuite 6.0.1\\bin\\scip.dll",
52 "C:\\Program Files (x86)\\SCIPOptSuite 6.0.0\\bin\\scip.dll",
53 "C:\\Program Files (x86)\\SCIPOptSuite 5.0.1\\bin\\scip.dll",
54 "C:\\Program Files (x86)\\SCIPOptSuite 5.0.0\\bin\\scip.dll",
55 "C:\\Program Files (x86)\\SCIPOptSuite 4.0.1\\bin\\scip.dll",
56 "C:\\Program Files (x86)\\SCIPOptSuite 4.0.0\\bin\\scip.dll",
57 }
58 #else
59 "libscip"
60 #endif
61 ) {
62 load();
63 }
64
load()65 void ScipPlugin::load() {
66 load_symbol(SCIPmajorVersion);
67 load_symbol(SCIPminorVersion);
68 load_symbol(SCIPtechVersion);
69 load_symbol(SCIPsubversion);
70 load_symbol(SCIPprintError);
71 load_symbol(SCIPcreate);
72 load_symbol(SCIPincludeDefaultPlugins);
73 load_symbol(SCIPcreateProbBasic);
74 load_symbol(SCIPfree);
75 load_symbol(SCIPcreateVarBasic);
76 load_symbol(SCIPaddVar);
77 load_symbol(SCIPreleaseVar);
78 #ifndef NDEBUG
79 load_symbol(SCIPinfinity);
80 #endif
81 load_symbol(SCIPcreateConsBasicLinear);
82 load_symbol(SCIPcreateConsBasicQuadratic);
83 load_symbol(SCIPaddCons);
84 load_symbol(SCIPreleaseCons);
85 load_symbol(SCIPchgVarLbGlobal);
86 load_symbol(SCIPchgVarUbGlobal);
87 load_symbol(SCIPgetNegatedVar);
88 load_symbol(SCIPcreateConsBasicIndicator);
89 load_symbol(SCIPcreateConsBasicBounddisjunction);
90 load_symbol(SCIPcreateConsBasicCumulative);
91 load_symbol(SCIPcreateConsBasicOrbisack);
92 load_symbol(SCIPcreateConsBasicOrbitope);
93 load_symbol(SCIPgetNSolsFound);
94 load_symbol(SCIPgetNSols);
95 load_symbol(SCIPsetIntParam);
96 load_symbol(SCIPsetRealParam);
97 load_symbol(SCIPwriteOrigProblem);
98 load_symbol(SCIPsetMessagehdlrQuiet);
99 load_symbol(SCIPmessagehdlrCreate);
100 load_symbol(SCIPsetMessagehdlr);
101 load_symbol(SCIPreadParams);
102 load_symbol(SCIPwriteParams);
103 load_symbol(SCIPsolve);
104 load_symbol(SCIPgetStatus);
105 load_symbol(SCIPgetPrimalbound);
106 load_symbol(SCIPgetDualbound);
107 load_symbol(SCIPgetSolVals);
108 load_symbol(SCIPgetBestSol);
109 load_symbol(SCIPgetNTotalNodes);
110 load_symbol(SCIPgetNNodes);
111 load_symbol(SCIPgetNNodesLeft);
112 load_symbol(SCIPfreeTransform);
113 load_symbol(SCIPsetObjsense);
114 load_symbol(SCIPeventhdlrGetName);
115 load_symbol(SCIPcatchEvent);
116 load_symbol(SCIPdropEvent);
117 load_symbol(SCIPeventGetType);
118 load_symbol(SCIPgetSolOrigObj);
119 load_symbol(SCIPincludeEventhdlrBasic);
120 load_symbol(SCIPsetEventhdlrInit);
121 load_symbol(SCIPsetEventhdlrExit);
122 load_symbol(SCIPmessagePrintErrorHeader);
123 load_symbol(SCIPmessagePrintError);
124 load_symbol(SCIPgetNVars);
125 load_symbol(SCIPgetNConss);
126 load_symbol(SCIPgetParams);
127 load_symbol(SCIPgetNParams);
128 load_symbol(SCIPparamGetName);
129 load_symbol(SCIPparamGetType);
130 load_symbol(SCIPparamGetDesc);
131 load_symbol(SCIPparamGetBoolDefault);
132 load_symbol(SCIPparamGetCharAllowedValues);
133 load_symbol(SCIPparamGetCharDefault);
134 load_symbol(SCIPparamGetIntDefault);
135 load_symbol(SCIPparamGetIntMin);
136 load_symbol(SCIPparamGetIntMax);
137 load_symbol(SCIPparamGetLongintDefault);
138 load_symbol(SCIPparamGetLongintMin);
139 load_symbol(SCIPparamGetLongintMax);
140 load_symbol(SCIPparamGetRealDefault);
141 load_symbol(SCIPparamGetRealMin);
142 load_symbol(SCIPparamGetRealMax);
143 load_symbol(SCIPparamGetStringDefault);
144 load_symbol(SCIPgetParam);
145 load_symbol(SCIPchgBoolParam);
146 load_symbol(SCIPchgIntParam);
147 load_symbol(SCIPchgLongintParam);
148 load_symbol(SCIPchgRealParam);
149 load_symbol(SCIPchgCharParam);
150 load_symbol(SCIPchgStringParam);
151 }
152
153 #define SCIP_PLUGIN_CALL_R(plugin, x) \
154 { \
155 SCIP_RETCODE _ret = (x); \
156 if (_ret != SCIP_OKAY) { \
157 (plugin)->SCIPmessagePrintErrorHeader(__FILE__, __LINE__); \
158 (plugin)->SCIPmessagePrintError("Error <%d> in function call\n", _ret); \
159 return _ret; \
160 } \
161 }
162
getDescription(FactoryOptions & factoryOpt,MiniZinc::SolverInstanceBase::Options * opt)163 string MIPScipWrapper::getDescription(FactoryOptions& factoryOpt,
164 MiniZinc::SolverInstanceBase::Options* opt) {
165 ostringstream oss;
166 oss << "MIP wrapper for SCIP " << getVersion(factoryOpt, opt)
167 << ". Compiled " __DATE__ " " __TIME__;
168 return oss.str();
169 }
getVersion(FactoryOptions & factoryOpt,MiniZinc::SolverInstanceBase::Options * opt)170 string MIPScipWrapper::getVersion(FactoryOptions& factoryOpt,
171 MiniZinc::SolverInstanceBase::Options* opt) {
172 try {
173 auto* p = factoryOpt.scipDll.empty() ? new ScipPlugin() : new ScipPlugin(factoryOpt.scipDll);
174 ostringstream oss;
175 oss << p->SCIPmajorVersion() << '.' << p->SCIPminorVersion() << '.' << p->SCIPtechVersion()
176 << '.' << p->SCIPsubversion();
177 delete p;
178 return oss.str();
179 } catch (MiniZinc::Plugin::PluginError&) {
180 return "<unknown version>";
181 }
182 }
getRequiredFlags(FactoryOptions & factoryOpt)183 vector<string> MIPScipWrapper::getRequiredFlags(FactoryOptions& factoryOpt) {
184 try {
185 ScipPlugin p;
186 return {};
187 } catch (MiniZinc::Plugin::PluginError&) {
188 return {"--scip-dll"};
189 }
190 }
191
getFactoryFlags()192 vector<string> MIPScipWrapper::getFactoryFlags() { return {"--scip-dll"}; };
193
getId()194 string MIPScipWrapper::getId() { return "scip"; }
195
getName()196 string MIPScipWrapper::getName() { return "SCIP"; }
197
getTags()198 vector<string> MIPScipWrapper::getTags() { return {"mip", "float", "api"}; }
199
getStdFlags()200 vector<string> MIPScipWrapper::getStdFlags() { return {"-i", "-p", "-s"}; }
201
printHelp(ostream & os)202 void MIPScipWrapper::Options::printHelp(ostream& os) {
203 os << "SCIP MIP wrapper options:"
204 << std::endl
205 // -s print statistics
206 // << " --readParam <file> read SCIP parameters from file
207 // << "--writeParam <file> write SCIP parameters to file
208 // << "--tuneParam instruct SCIP to tune parameters instead of solving
209 << "--writeModel <file> write model to <file> (.lp, .mps, ...?)" << std::endl
210 << "-i print intermediate solutions for optimization problems" << std::endl
211 << "-p <N>, --parallel <N>\n use N threads, default: 1"
212 << std::endl
213 // << "--nomippresolve disable MIP presolving NOT IMPL" << std::endl
214 << "--solver-time-limit <N> stop search after N milliseconds" << std::endl
215 << "--workmem <N> maximal amount of RAM used, MB" << std::endl
216 << "--readParam <file> read SCIP parameters from file" << std::endl
217 << "--writeParam <file> write SCIP parameters to file"
218 << std::endl
219 // << "--tuneParam instruct SCIP to tune parameters instead of solving NOT IMPL"
220
221 << "--absGap <n> absolute gap |primal-dual| to stop" << std::endl
222 << "--relGap <n> relative gap |primal-dual|/<solver-dep> to stop. Default 1e-8, set "
223 "<0 "
224 "to use backend's default"
225 << std::endl
226 << "--intTol <n> integrality tolerance for a variable. Default 1e-8"
227 << std::endl
228 // << "--objDiff <n> objective function discretization. Default 1.0" << std::endl
229 << "--scip-dll <file> load the SCIP library from the given file (absolute path or file "
230 "basename), default 'scip'"
231 << std::endl
232 << std::endl;
233 }
234
beginswith(const string & s,const string & t)235 static inline bool beginswith(const string& s, const string& t) {
236 return s.compare(0, t.length(), t) == 0;
237 }
238
processOption(int & i,std::vector<std::string> & argv,const std::string & workingDir)239 bool MIPScipWrapper::FactoryOptions::processOption(int& i, std::vector<std::string>& argv,
240 const std::string& workingDir) {
241 MiniZinc::CLOParser cop(i, argv);
242 return cop.get("--scip-dll", &scipDll);
243 }
244
processOption(int & i,vector<string> & argv,const std::string & workingDir)245 bool MIPScipWrapper::Options::processOption(int& i, vector<string>& argv,
246 const std::string& workingDir) {
247 MiniZinc::CLOParser cop(i, argv);
248 std::string buffer;
249 if (cop.get("-i")) {
250 flagIntermediate = true;
251 } else if (string(argv[i]) == "-f") { // NOLINT: Allow repeated empty if
252 // std::cerr << " Flag -f: ignoring fixed strategy anyway." << std::endl;
253 } else if (cop.get("--writeModel", &buffer)) {
254 sExportModel = MiniZinc::FileUtils::file_path(buffer);
255 } else if (cop.get("-p --parallel", &nThreads)) { // NOLINT: Allow repeated empty if
256 } else if (cop.get("--solver-time-limit", &nTimeout)) { // NOLINT: Allow repeated empty if
257 } else if (cop.get("--workmem", &nWorkMemLimit)) { // NOLINT: Allow repeated empty if
258 } else if (cop.get("--readParam", &buffer)) {
259 sReadParams = MiniZinc::FileUtils::file_path(buffer);
260 } else if (cop.get("--writeParam", &buffer)) {
261 sWriteParams = MiniZinc::FileUtils::file_path(buffer);
262 } else if (cop.get("--absGap", &absGap)) { // NOLINT: Allow repeated empty if
263 } else if (cop.get("--relGap", &relGap)) { // NOLINT: Allow repeated empty if
264 } else if (cop.get("--intTol", &intTol)) { // NOLINT: Allow repeated empty if
265 // } else if ( cop.get( "--objDiff", &objDiff ) ) {
266 } else {
267 return false;
268 }
269 return true;
270 error:
271 return false;
272 }
273
274 // NOLINTNEXTLINE(readability-identifier-naming)
SCIP_PLUGIN_CALL(SCIP_RETCODE retcode,const string & msg,bool fTerm)275 void MIPScipWrapper::SCIP_PLUGIN_CALL(SCIP_RETCODE retcode, const string& msg, bool fTerm) {
276 /* evaluate return code of the SCIP process */
277 if (retcode != SCIP_OKAY) {
278 /* write error back trace */
279 _plugin->SCIPprintError(retcode);
280 string msgAll = (" MIPScipWrapper runtime error, see output: " + msg);
281 cerr << msgAll << endl;
282 if (fTerm) {
283 cerr << "TERMINATING." << endl;
284 throw runtime_error(msgAll);
285 }
286 }
287 }
288
openSCIP()289 SCIP_RETCODE MIPScipWrapper::openSCIP() {
290 if (_factoryOptions.scipDll.empty()) {
291 _plugin = new ScipPlugin();
292 } else {
293 _plugin = new ScipPlugin(_factoryOptions.scipDll);
294 }
295
296 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPcreate(&_scip));
297 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPincludeDefaultPlugins(_scip));
298
299 /* create empty problem */
300 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPcreateProbBasic(_scip, "mzn_scip"));
301 return SCIP_OKAY;
302 }
303
closeSCIP()304 SCIP_RETCODE MIPScipWrapper::closeSCIP() {
305 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPfree(&_scip));
306
307 delete _plugin;
308 /// and at last:
309 // MIPWrapper::cleanup();
310 return SCIP_OKAY;
311 }
312
getExtraFlags(FactoryOptions & factoryOpt)313 std::vector<MiniZinc::SolverConfig::ExtraFlag> MIPScipWrapper::getExtraFlags(
314 FactoryOptions& factoryOpt) {
315 try {
316 MIPScipWrapper msw(factoryOpt, nullptr);
317 auto* params = msw._plugin->SCIPgetParams(msw._scip);
318 int num_params = msw._plugin->SCIPgetNParams(msw._scip);
319 std::vector<MiniZinc::SolverConfig::ExtraFlag> res;
320 res.reserve(num_params);
321 for (int i = 0; i < num_params; i++) {
322 auto* param = params[i];
323 std::string name = std::string(msw._plugin->SCIPparamGetName(param));
324 if (name == "lp/threads" || name == "limits/time" || name == "limits/memory" ||
325 name == "limits/absgap" || name == "limits/gap" || name == "numerics/feastol") {
326 // Handled by stdFlags
327 continue;
328 }
329 // Replace / in param name with _ (can't use - as some names have - in them already)
330 auto type = msw._plugin->SCIPparamGetType(param);
331 std::string desc(msw._plugin->SCIPparamGetDesc(param));
332 MiniZinc::SolverConfig::ExtraFlag::FlagType param_type;
333 std::vector<std::string> param_range;
334 std::string param_default;
335 switch (type) {
336 case SCIP_ParamType::SCIP_PARAMTYPE_BOOL:
337 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_BOOL;
338 param_range = {"true", "false"};
339 param_default = msw._plugin->SCIPparamGetBoolDefault(param) != 0 ? "true" : "false";
340 break;
341 case SCIP_ParamType::SCIP_PARAMTYPE_CHAR: {
342 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_STRING;
343 param_default = msw._plugin->SCIPparamGetCharDefault(param);
344 auto* allowed_values = msw._plugin->SCIPparamGetCharAllowedValues(param);
345 if (allowed_values != nullptr) {
346 for (int i = 0; i < strlen(allowed_values); i++) {
347 param_range.emplace_back(1, allowed_values[i]);
348 }
349 }
350 break;
351 }
352 case SCIP_ParamType::SCIP_PARAMTYPE_INT:
353 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_INT;
354 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetIntMin(param)));
355 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetIntMax(param)));
356 param_default = std::to_string(msw._plugin->SCIPparamGetIntDefault(param));
357 break;
358 case SCIP_ParamType::SCIP_PARAMTYPE_LONGINT:
359 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_INT;
360 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetLongintMin(param)));
361 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetLongintMax(param)));
362 param_default = std::to_string(msw._plugin->SCIPparamGetLongintDefault(param));
363 break;
364 case SCIP_ParamType::SCIP_PARAMTYPE_REAL:
365 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_FLOAT;
366 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetRealMin(param)));
367 param_range.push_back(std::to_string(msw._plugin->SCIPparamGetRealMax(param)));
368 param_default = std::to_string(msw._plugin->SCIPparamGetRealDefault(param));
369 break;
370 case SCIP_ParamType::SCIP_PARAMTYPE_STRING:
371 param_type = MiniZinc::SolverConfig::ExtraFlag::FlagType::T_STRING;
372 param_default = msw._plugin->SCIPparamGetStringDefault(param);
373 break;
374 default:
375 break;
376 }
377 res.emplace_back("--scip-" + name, desc, param_type, param_range, param_default);
378 }
379 return res;
380 } catch (MiniZinc::Plugin::PluginError&) {
381 return {};
382 }
383 return {};
384 }
385
doAddVarsSCIP(size_t n,double * obj,double * lb,double * ub,MIPWrapper::VarType * vt,string * names)386 SCIP_RETCODE MIPScipWrapper::doAddVarsSCIP(size_t n, double* obj, double* lb, double* ub,
387 MIPWrapper::VarType* vt, string* names) {
388 /// Convert var types:
389 // vector<char> ctype(n);
390 // vector<char*> pcNames(n);
391 for (size_t j = 0; j < n; ++j) {
392 // pcNames[i] = (char*)names[i].c_str();
393 SCIP_VARTYPE ctype;
394 switch (vt[j]) {
395 case REAL:
396 ctype = SCIP_VARTYPE_CONTINUOUS;
397 break;
398 case INT:
399 ctype = SCIP_VARTYPE_INTEGER;
400 break;
401 case BINARY:
402 ctype = SCIP_VARTYPE_BINARY;
403 break;
404 default:
405 throw runtime_error(" MIPWrapper: unknown variable type");
406 }
407 _scipVars.resize(_scipVars.size() + 1);
408 if (fPhase1Over) {
409 assert(_scipVars.size() == colObj.size());
410 }
411 SCIP_PLUGIN_CALL_R(
412 _plugin, _plugin->SCIPcreateVarBasic(_scip, &_scipVars.back(), names[j].c_str(), lb[j],
413 ub[j], obj[j], ctype));
414 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPaddVar(_scip, _scipVars.back()));
415 }
416 // retcode = SCIP_newcols (env, lp, n, obj, lb, ub, &ctype[0], &pcNames[0]);
417 // wrap_assert( !retcode, "Failed to declare variables." );
418 return SCIP_OKAY;
419 }
420
delSCIPVars()421 SCIP_RETCODE MIPScipWrapper::delSCIPVars() {
422 for (auto& v : _scipVars) {
423 _plugin->SCIPreleaseVar(_scip, &v);
424 }
425 return SCIP_OKAY;
426 }
427
addRowSCIP(int nnz,int * rmatind,double * rmatval,MIPWrapper::LinConType sense,double rhs,int mask,const string & rowName)428 SCIP_RETCODE MIPScipWrapper::addRowSCIP(int nnz, int* rmatind, double* rmatval,
429 MIPWrapper::LinConType sense, double rhs, int mask,
430 const string& rowName) {
431 /// Convert var types:
432 double lh = -SCIPinfinityPlugin(_plugin, _scip);
433 double rh = SCIPinfinityPlugin(_plugin, _scip);
434 switch (sense) {
435 case LQ:
436 rh = rhs;
437 break;
438 case EQ:
439 lh = rh = rhs;
440 break;
441 case GQ:
442 lh = rhs;
443 break;
444 default:
445 throw runtime_error(" MIPWrapper: unknown constraint type");
446 }
447 const int ccnt = 0;
448 const int rcnt = 1;
449 const int rmatbeg[] = {0};
450 char* pRName = (char*)rowName.c_str();
451 // ignoring mask for now. TODO
452 SCIP_CONS* cons;
453 vector<SCIP_VAR*> ab(nnz);
454
455 for (int j = 0; j < nnz; ++j) {
456 ab[j] = _scipVars[rmatind[j]];
457 }
458
459 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPcreateConsBasicLinear(_scip, &cons, rowName.c_str(), nnz,
460 &ab[0], rmatval, lh, rh));
461 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPaddCons(_scip, cons));
462 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPreleaseCons(_scip, &cons));
463 return SCIP_OKAY;
464 // retcode = SCIP_addrows (env, lp, ccnt, rcnt, nnz, &rhs,
465 // &ssense, rmatbeg, rmatind, rmatval,
466 // nullptr, &pRName);
467 // wrap_assert( !retcode, "Failed to add constraint." );
468 }
469
setVarBounds(int iVar,double lb,double ub)470 void MIPScipWrapper::setVarBounds(int iVar, double lb, double ub) {
471 SCIP_PLUGIN_CALL(lb <= ub ? SCIP_OKAY : SCIP_ERROR, "scip interface: setVarBounds: lb>ub");
472 setVarLB(iVar, lb);
473 setVarUB(iVar, ub);
474 }
475
setVarLB(int iVar,double lb)476 void MIPScipWrapper::setVarLB(int iVar, double lb) {
477 auto res = _plugin->SCIPchgVarLbGlobal(_scip, _scipVars[iVar], lb);
478 SCIP_PLUGIN_CALL(res, "scip interface: failed to set var lb.");
479 }
480
setVarUB(int iVar,double ub)481 void MIPScipWrapper::setVarUB(int iVar, double ub) {
482 auto res = _plugin->SCIPchgVarUbGlobal(_scip, _scipVars[iVar], ub);
483 SCIP_PLUGIN_CALL(res, "scip interface: failed to set var ub.");
484 }
485
addIndicatorConstraint(int iBVar,int bVal,int nnz,int * rmatind,double * rmatval,MIPWrapper::LinConType sense,double rhs,const string & rowName)486 void MIPScipWrapper::addIndicatorConstraint(int iBVar, int bVal, int nnz, int* rmatind,
487 double* rmatval, MIPWrapper::LinConType sense,
488 double rhs, const string& rowName) {
489 MZN_ASSERT_HARD_MSG(0 <= bVal && 1 >= bVal, "SCIP: addIndicatorConstraint: bVal not 0/1");
490 //// Make sure in order to notice the indices of lazy constr: also here? TODO
491 // ++ nRows;
492
493 SCIP_CONS* cons;
494 vector<SCIP_VAR*> ab(nnz);
495 SCIP_VAR*
496 indicator_var; // SCIP 6.0.1 requires that the implication is active for indicator_x == 1
497
498 for (int j = 0; j < nnz; ++j) {
499 ab[j] = _scipVars[rmatind[j]];
500 }
501
502 indicator_var = _scipVars[iBVar];
503 if (0 == bVal) {
504 SCIP_PLUGIN_CALL(_plugin->SCIPgetNegatedVar(_scip, indicator_var, &indicator_var));
505 }
506
507 if (LQ == sense || EQ == sense) {
508 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicIndicator(
509 _scip, &cons, rowName.c_str(), indicator_var, nnz, ab.data(), rmatval, rhs));
510 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
511 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
512 }
513 if (GQ == sense || EQ == sense) {
514 std::vector<double> rmatvalNEG(nnz);
515 for (int i = nnz; (i--) != 0;) {
516 rmatvalNEG[i] = -rmatval[i];
517 }
518 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicIndicator(
519 _scip, &cons, rowName.c_str(), indicator_var, nnz, ab.data(), rmatvalNEG.data(), -rhs));
520 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
521 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
522 }
523 }
524
addBoundsDisj(int n,double * fUB,double * bnd,int * vars,int nF,double * fUBF,double * bndF,int * varsF,const string & rowName)525 void MIPScipWrapper::addBoundsDisj(int n, double* fUB, double* bnd, int* vars, int nF, double* fUBF,
526 double* bndF, int* varsF, const string& rowName) {
527 SCIP_CONS* cons;
528 std::vector<SCIP_VAR*> v(n + nF);
529 std::vector<SCIP_BOUNDTYPE> bt(n + nF);
530 std::vector<SCIP_Real> bs(n + nF);
531
532 for (int j = 0; j < n; ++j) {
533 v[j] = _scipVars[vars[j]];
534 bt[j] = (fUB[j] != 0.0) ? SCIP_BOUNDTYPE_UPPER : SCIP_BOUNDTYPE_LOWER;
535 bs[j] = bnd[j];
536 }
537 for (int j = 0; j < nF; ++j) {
538 v[n + j] = _scipVars[varsF[j]];
539 bt[n + j] = (fUBF[j] != 0.0) ? SCIP_BOUNDTYPE_UPPER : SCIP_BOUNDTYPE_LOWER;
540 bs[n + j] = bndF[j];
541 }
542
543 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicBounddisjunction(
544 _scip, &cons, rowName.c_str(), v.size(), v.data(), bt.data(), bs.data()));
545 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
546 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
547 }
548
addCumulative(int nnz,int * rmatind,double * d,double * r,double b,const string & rowName)549 void MIPScipWrapper::addCumulative(int nnz, int* rmatind, double* d, double* r, double b,
550 const string& rowName) {
551 SCIP_CONS* cons;
552 vector<SCIP_VAR*> ab(nnz);
553 vector<int> nd(nnz);
554 vector<int> nr(nnz);
555
556 for (int j = 0; j < nnz; ++j) {
557 ab[j] = _scipVars[rmatind[j]];
558 nd[j] = (int)round(d[j]);
559 nr[j] = (int)round(r[j]);
560 }
561
562 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicCumulative(
563 _scip, &cons, rowName.c_str(), nnz, ab.data(), nd.data(), nr.data(), (int)round(b)));
564
565 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
566 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
567 }
568
569 /// Lex-lesseq binary, currently SCIP only
570 /// TODO check all variables are binary, SCIP 7.0.2 does not
addLexLesseq(int nnz,int * rmatind1,int * rmatind2,bool isModelCons,const std::string & rowName)571 void MIPScipWrapper::addLexLesseq(int nnz, int* rmatind1, int* rmatind2, bool isModelCons,
572 const std::string& rowName) {
573 SCIP_CONS* cons;
574 vector<SCIP_VAR*> vars1(nnz);
575 vector<SCIP_VAR*> vars2(nnz);
576
577 for (int j = 0; j < nnz; ++j) {
578 vars1[j] = _scipVars[rmatind1[j]];
579 vars2[j] = _scipVars[rmatind2[j]];
580 }
581
582 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicOrbisack(
583 _scip, &cons, rowName.c_str(), vars2.data(), vars1.data(), // it's actually lex_greatereq
584 nnz, FALSE, FALSE, (SCIP_Bool)isModelCons));
585 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
586 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
587 }
588
589 /// Lex-chain-lesseq binary, currently SCIP only
addLexChainLesseq(int m,int n,int * rmatind,int nOrbitopeType,bool resolveprop,bool isModelCons,const std::string & rowName)590 void MIPScipWrapper::addLexChainLesseq(int m, int n, int* rmatind, int nOrbitopeType,
591 bool resolveprop, bool isModelCons,
592 const std::string& rowName) {
593 SCIP_CONS* cons;
594 vector<vector<SCIP_VAR*> > vars(m, vector<SCIP_VAR*>(size_t(n)));
595 vector<SCIP_VAR**> vars_data(m);
596
597 for (int i = 0; i < m; ++i) {
598 for (int j = 0; j < n; ++j) {
599 vars[i][j] = _scipVars[rmatind[i * n + (n - j - 1)]]; // it's actually lex_chain_greatereq
600 }
601 vars_data[i] = vars[i].data();
602 }
603
604 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicOrbitope(
605 _scip, &cons, rowName.c_str(), vars_data.data(), (SCIP_ORBITOPETYPE)nOrbitopeType, m, n,
606 (SCIP_Bool)resolveprop, (SCIP_Bool)isModelCons));
607 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
608 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
609 }
610
addTimes(int x,int y,int z,const string & rowName)611 void MIPScipWrapper::addTimes(int x, int y, int z, const string& rowName) {
612 /// As x*y - z == 0
613 double zCoef = -1.0;
614 double xyCoef = 1.0;
615 SCIP_CONS* cons;
616 std::array<SCIP_VAR*, 3> zxy = {_scipVars[z], _scipVars[x], _scipVars[y]};
617
618 SCIP_PLUGIN_CALL(_plugin->SCIPcreateConsBasicQuadratic(
619 _scip, &cons, rowName.c_str(), 1, &zxy[0], &zCoef, 1, &zxy[1], &zxy[2], &xyCoef, 0.0, 0.0));
620 SCIP_PLUGIN_CALL(_plugin->SCIPaddCons(_scip, cons));
621 SCIP_PLUGIN_CALL(_plugin->SCIPreleaseCons(_scip, &cons));
622 }
623
624 /// SolutionCallback ------------------------------------------------------------------------
625
626 /// From event_bestsol.c:
627 #define EVENTHDLR_NAME "bestsol"
628 #define EVENTHDLR_DESC "event handler for best solutions found"
629
630 namespace {
631 // Dirty way of accessing SCIP functions inside C callbacks
632 ScipPlugin* _cb_plugin;
633
634 MIPWrapper::CBUserInfo* cbuiPtr = nullptr;
635 SCIP_VAR** _scipVarsPtr = nullptr;
636 } // namespace
637
638 /** initialization method of event handler (called after problem was transformed) */
SCIP_DECL_EVENTINIT(eventInitBestsol)639 static SCIP_DECL_EVENTINIT(eventInitBestsol) { /*lint --e{715}*/
640 assert(scip != nullptr);
641 assert(eventhdlr != nullptr);
642 assert(strcmp(_cb_plugin->SCIPeventhdlrGetName(eventhdlr), EVENTHDLR_NAME) == 0);
643
644 /* notify SCIP that your event handler wants to react on the event type best solution found */
645 SCIP_PLUGIN_CALL_R(_cb_plugin, _cb_plugin->SCIPcatchEvent(scip, SCIP_EVENTTYPE_BESTSOLFOUND,
646 eventhdlr, nullptr, nullptr));
647
648 return SCIP_OKAY;
649 }
650
651 /** deinitialization method of event handler (called before transformed problem is freed) */
SCIP_DECL_EVENTEXIT(eventExitBestsol)652 static SCIP_DECL_EVENTEXIT(eventExitBestsol) { /*lint --e{715}*/
653 assert(scip != nullptr);
654 assert(eventhdlr != nullptr);
655 assert(strcmp(_cb_plugin->SCIPeventhdlrGetName(eventhdlr), EVENTHDLR_NAME) == 0);
656
657 /* notify SCIP that your event handler wants to drop the event type best solution found */
658 SCIP_PLUGIN_CALL_R(_cb_plugin, _cb_plugin->SCIPdropEvent(scip, SCIP_EVENTTYPE_BESTSOLFOUND,
659 eventhdlr, nullptr, -1));
660
661 return SCIP_OKAY;
662 }
663
664 /** execution method of event handler */
SCIP_DECL_EVENTEXEC(eventExecBestsol)665 static SCIP_DECL_EVENTEXEC(eventExecBestsol) { /*lint --e{715}*/
666 SCIP_SOL* bestsol;
667 SCIP_Real objVal;
668 int newincumbent = 0;
669
670 assert(eventhdlr != nullptr);
671 assert(strcmp(_cb_plugin->SCIPeventhdlrGetName(eventhdlr), EVENTHDLR_NAME) == 0);
672 assert(event != nullptr);
673 assert(scip != nullptr);
674 assert(_cb_plugin->SCIPeventGetType(event) == SCIP_EVENTTYPE_BESTSOLFOUND);
675
676 SCIPdebugMessage("exec method of event handler for best solution found\n");
677
678 bestsol = _cb_plugin->SCIPgetBestSol(scip);
679 assert(bestsol != nullptr);
680 objVal = _cb_plugin->SCIPgetSolOrigObj(scip, bestsol);
681
682 if (cbuiPtr == nullptr) {
683 return SCIP_OKAY;
684 }
685
686 if (fabs(cbuiPtr->pOutput->objVal - objVal) > 1e-12 * (1.0 + fabs(objVal))) {
687 newincumbent = 1;
688 cbuiPtr->pOutput->objVal = objVal;
689 cbuiPtr->pOutput->status = MIPWrapper::SAT;
690 cbuiPtr->pOutput->statusName = "feasible from a callback";
691 }
692
693 if (newincumbent != 0 && _scipVarsPtr != nullptr) {
694 assert(cbuiPtr->pOutput->x);
695 SCIP_PLUGIN_CALL_R(
696 _cb_plugin, _cb_plugin->SCIPgetSolVals(scip, bestsol, cbuiPtr->pOutput->nCols, _scipVarsPtr,
697 (double*)cbuiPtr->pOutput->x));
698 // wrap_assert(!retcode, "Failed to get variable values.");
699 cbuiPtr->pOutput->nNodes = static_cast<int>(_cb_plugin->SCIPgetNNodes(scip));
700 cbuiPtr->pOutput->nOpenNodes = _cb_plugin->SCIPgetNNodesLeft(scip);
701 cbuiPtr->pOutput->bestBound = _cb_plugin->SCIPgetDualbound(scip);
702
703 cbuiPtr->pOutput->dWallTime = std::chrono::duration<double>(std::chrono::steady_clock::now() -
704 cbuiPtr->pOutput->dWallTime0)
705 .count();
706 cbuiPtr->pOutput->dCPUTime =
707 double(std::clock() - cbuiPtr->pOutput->cCPUTime0) / CLOCKS_PER_SEC;
708
709 /// Call the user function:
710 if (cbuiPtr->solcbfn != nullptr) {
711 (*cbuiPtr->solcbfn)(*cbuiPtr->pOutput, cbuiPtr->psi);
712 }
713 }
714
715 return SCIP_OKAY;
716 }
717
718 /** includes event handler for best solution found */
includeEventHdlrBestsol()719 SCIP_RETCODE MIPScipWrapper::includeEventHdlrBestsol() {
720 SCIP_EVENTHDLRDATA* eventhdlrdata;
721 SCIP_EVENTHDLR* eventhdlr;
722 eventhdlrdata = nullptr;
723
724 eventhdlr = nullptr;
725
726 _cb_plugin = _plugin; // So that callbacks can access plugin functions
727
728 /* create event handler for events on watched variables */
729 SCIP_PLUGIN_CALL_R(
730 _plugin, _plugin->SCIPincludeEventhdlrBasic(_scip, &eventhdlr, EVENTHDLR_NAME, EVENTHDLR_DESC,
731 eventExecBestsol, eventhdlrdata));
732 assert(eventhdlr != nullptr);
733
734 /// Not for sub-SCIPs
735 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetEventhdlrInit(_scip, eventhdlr, eventInitBestsol));
736 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetEventhdlrExit(_scip, eventhdlr, eventExitBestsol));
737
738 return SCIP_OKAY;
739 }
740
convertStatus(SCIP_STATUS scipStatus)741 MIPScipWrapper::Status MIPScipWrapper::convertStatus(SCIP_STATUS scipStatus) {
742 Status s = Status::UNKNOWN;
743 /* Converting the status. */
744 switch (scipStatus) {
745 case SCIP_STATUS_OPTIMAL:
746 s = Status::OPT;
747 output.statusName = "Optimal";
748 assert(_plugin->SCIPgetNSolsFound(_scip));
749 break;
750 case SCIP_STATUS_INFEASIBLE:
751 s = Status::UNSAT;
752 output.statusName = "Infeasible";
753 break;
754 // case SCIP_MIP_OPTIMAL_INFEAS:
755 case SCIP_STATUS_INFORUNBD:
756 s = Status::UNSATorUNBND;
757 output.statusName = "Infeasible or unbounded";
758 break;
759 // case SCIP_MIP_SOL_LIM:
760 // s = Status::SAT;
761 // wrap_assert(SCIP_getsolnpoolnumsolns(env, lp), "Feasibility reported but pool
762 // empty?", false); break;
763 case SCIP_STATUS_UNBOUNDED:
764 s = Status::UNBND;
765 output.statusName = "Unbounded";
766 break;
767 // case SCIP_STATUSMIP_ABORT_INFEAS:
768 // case SCIP_MIP_FAIL_INFEAS:
769 // s = Status::ERROR;
770 // break;
771 default:
772 // case SCIP_MIP_OPTIMAL_TOL:
773 // case SCIP_MIP_ABORT_RELAXATION_UNBOUNDED:
774 if (_plugin->SCIPgetNSols(_scip) != 0) {
775 s = Status::SAT;
776 output.statusName = "Feasible";
777 } else {
778 s = Status::UNKNOWN;
779 output.statusName = "Unknown";
780 }
781 }
782 return s;
783 }
784
SCIP_DECL_MESSAGEWARNING(printMsg)785 SCIP_DECL_MESSAGEWARNING(printMsg) { cerr << msg << flush; }
786
solveSCIP()787 SCIP_RETCODE MIPScipWrapper::solveSCIP() { // Move into ancestor?
788
789 /////////////// Last-minute solver options //////////////////
790 if (_options->nThreads > 0)
791 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetIntParam(_scip, "lp/threads", _options->nThreads));
792
793 if (_options->nTimeout > 0)
794 SCIP_PLUGIN_CALL_R(_plugin,
795 _plugin->SCIPsetRealParam(_scip, "limits/time",
796 static_cast<double>(_options->nTimeout) / 1000.0));
797
798 if (_options->nWorkMemLimit > 0)
799 SCIP_PLUGIN_CALL_R(_plugin,
800 _plugin->SCIPsetRealParam(_scip, "limits/memory", _options->nWorkMemLimit));
801
802 if (_options->absGap >= 0.0)
803 SCIP_PLUGIN_CALL_R(_plugin,
804 _plugin->SCIPsetRealParam(_scip, "limits/absgap", _options->absGap));
805 if (_options->relGap >= 0.0)
806 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetRealParam(_scip, "limits/gap", _options->relGap));
807 if (_options->intTol >= 0.0)
808 SCIP_PLUGIN_CALL_R(_plugin,
809 _plugin->SCIPsetRealParam(_scip, "numerics/feastol", _options->intTol));
810
811 // retcode = SCIP_setintparam (env, SCIP_PARAM_ClockType, 1); // CPU time
812 // wrap_assert(!retcode, " SCIP Warning: Failure to measure CPU time.", false);
813
814 if (!_options->sExportModel.empty()) {
815 // std::cerr <<" Exporting LP model to " << sExportModel << " ..." << std::endl;
816 SCIP_PLUGIN_CALL_R(
817 _plugin, _plugin->SCIPwriteOrigProblem(_scip, _options->sExportModel.c_str(), nullptr, 0));
818 }
819
820 /* Turn on output to the screen - after model export */
821 if (!fVerbose) {
822 // SCIP_PLUGIN_CALL(SCIPsetMessagehdlr(_scip, nullptr)); No LP export then
823 _plugin->SCIPsetMessagehdlrQuiet(_scip, TRUE);
824 } else {
825 SCIP_MESSAGEHDLR* pHndl = nullptr;
826 SCIP_PLUGIN_CALL_R(
827 _plugin, _plugin->SCIPmessagehdlrCreate(&pHndl, FALSE, nullptr, FALSE, printMsg, printMsg,
828 printMsg, nullptr, nullptr));
829 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetMessagehdlr(_scip, pHndl));
830 }
831
832 // assert(_scipVars.size() == colObj.size());
833 int cur_numcols = _scipVars.size(); // No, we create negated indicators: getNCols();
834 assert(cur_numcols == colObj.size());
835 assert(cur_numcols == _scipVars.size());
836
837 /// Solution callback
838 output.nCols = colObj.size();
839 _x.resize(output.nCols);
840 output.x = &_x[0];
841 if (_options->flagIntermediate && cbui.solcbfn != nullptr && cbuiPtr == nullptr) {
842 /* include event handler for best solution found */
843 SCIP_PLUGIN_CALL_R(_plugin, includeEventHdlrBestsol());
844 cbuiPtr = &cbui; // not thread-safe... TODO
845 _scipVarsPtr = &_scipVars[0];
846 // retcode = SCIP_setinfocallbackfunc (env, solcallback, &cbui);
847 // wrap_assert(!retcode, "Failed to set solution callback", false);
848 }
849
850 // Process extra flags options
851 for (auto& it : _options->extraParams) {
852 auto name = it.first.substr(7);
853 std::replace(name.begin(), name.end(), '_', '/');
854 auto* param = _plugin->SCIPgetParam(_scip, name.c_str());
855 if (param == nullptr) {
856 continue;
857 }
858 auto type = _plugin->SCIPparamGetType(param);
859 switch (type) {
860 case SCIP_ParamType::SCIP_PARAMTYPE_BOOL:
861 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPchgBoolParam(_scip, param, it.second == "true"));
862 break;
863 case SCIP_ParamType::SCIP_PARAMTYPE_CHAR:
864 if (!it.second.empty()) {
865 SCIP_PLUGIN_CALL_R(_plugin,
866 _plugin->SCIPchgCharParam(_scip, param, it.second.c_str()[0]));
867 }
868 break;
869 case SCIP_ParamType::SCIP_PARAMTYPE_INT:
870 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPchgIntParam(_scip, param, stoi(it.second)));
871 break;
872 case SCIP_ParamType::SCIP_PARAMTYPE_LONGINT:
873 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPchgLongintParam(_scip, param, stoll(it.second)));
874 break;
875 case SCIP_ParamType::SCIP_PARAMTYPE_REAL:
876 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPchgRealParam(_scip, param, stod(it.second)));
877 break;
878 case SCIP_ParamType::SCIP_PARAMTYPE_STRING:
879 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPchgStringParam(_scip, param, it.second.c_str()));
880 break;
881 default:
882 break;
883 }
884 }
885
886 if (!_options->sReadParams.empty()) {
887 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPreadParams(_scip, _options->sReadParams.c_str()));
888 }
889
890 if (!_options->sWriteParams.empty()) {
891 SCIP_PLUGIN_CALL_R(_plugin,
892 _plugin->SCIPwriteParams(_scip, _options->sReadParams.c_str(), TRUE, FALSE));
893 }
894
895 cbui.pOutput->dWallTime0 = output.dWallTime0 = std::chrono::steady_clock::now();
896 output.dCPUTime = clock();
897
898 /* Optimize the problem and obtain solution. */
899 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsolve(_scip));
900 // wrap_assert( !retcode, "Failed to optimize MIP." );
901
902 output.dWallTime =
903 std::chrono::duration<double>(std::chrono::steady_clock::now() - output.dWallTime0).count();
904 output.dCPUTime = (clock() - output.dCPUTime) / CLOCKS_PER_SEC;
905
906 cbuiPtr = nullptr; /// cleanup
907 _scipVarsPtr = nullptr;
908
909 SCIP_STATUS solstat = _plugin->SCIPgetStatus(_scip);
910 output.status = convertStatus(solstat);
911 // output.statusName = SCIP_getstatstring (env, solstat, scip_status_buffer);
912
913 /// Continuing to fill the output object:
914 output.objVal = _plugin->SCIPgetPrimalbound(_scip);
915 output.bestBound = _plugin->SCIPgetDualbound(_scip);
916 // wrap_assert(!retcode, "Failed to get the best bound.", false);
917 if (Status::OPT == output.status || Status::SAT == output.status) {
918 // wrap_assert( !retcode, "No MIP objective value available." );
919
920 _x.resize(cur_numcols);
921 output.x = &_x[0];
922 SCIP_PLUGIN_CALL_R(_plugin,
923 _plugin->SCIPgetSolVals(_scip, _plugin->SCIPgetBestSol(_scip), cur_numcols,
924 &_scipVars[0], (double*)output.x));
925 if (cbui.solcbfn != nullptr && (!_options->flagIntermediate || !cbui.printed)) {
926 cbui.solcbfn(output, cbui.psi);
927 }
928 }
929 output.nNodes = static_cast<int>(_plugin->SCIPgetNTotalNodes(_scip));
930 output.nOpenNodes = _plugin->SCIPgetNNodesLeft(_scip); // SCIP_getnodeleftcnt (env, lp);
931
932 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPfreeTransform(_scip));
933
934 return SCIP_OKAY;
935 }
936
setObjSenseSCIP(int s)937 SCIP_RETCODE MIPScipWrapper::setObjSenseSCIP(int s) {
938 SCIP_PLUGIN_CALL_R(_plugin, _plugin->SCIPsetObjsense(
939 _scip, s > 0 ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
940 return SCIP_OKAY;
941 }
942