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