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