1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
on-restart-benchmarks/solvers/MIP/MIP_cplex_wrap.cpp
Jip J. Dekker f2a1c4e389 Squashed 'software/mza/' content from commit f970a59b17
git-subtree-dir: software/mza
git-subtree-split: f970a59b177c13ca3dd8aaef8cc6681d83b7e813
2021-07-11 16:34:30 +10:00

1015 lines
39 KiB
C++

// * -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main authors:
* Gleb Belov <gleb.belov@monash.edu>
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <minizinc/config.hh>
#include <minizinc/file_utils.hh>
#include <minizinc/utils.hh>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#ifdef CPLEX_PLUGIN
#ifdef HAS_DLFCN_H
#include <dlfcn.h>
#elif defined HAS_WINDOWS_H
#include <Windows.h>
#endif
#endif
using namespace std;
#include <minizinc/solvers/MIP/MIP_cplex_wrap.hh>
#ifdef CPLEX_PLUGIN
namespace {
void* dll_open(const std::string& file) {
#ifdef HAS_DLFCN_H
if (MiniZinc::FileUtils::is_absolute(file)) {
return dlopen(file.c_str(), RTLD_NOW);
}
if (void* so = dlopen(("lib" + file + ".so").c_str(), RTLD_NOW)) {
return so;
}
return dlopen(("lib" + file + ".jnilib").c_str(), RTLD_NOW);
#else
if (MiniZinc::FileUtils::is_absolute(file)) {
return LoadLibrary(file.c_str());
} else {
return LoadLibrary((file + ".dll").c_str());
}
#endif
}
void* dll_sym(void* dll, const char* sym) {
#ifdef HAS_DLFCN_H
void* ret = dlsym(dll, sym);
#else
void* ret = GetProcAddress((HMODULE)dll, sym);
#endif
if (ret == NULL)
throw MiniZinc::InternalError("cannot load symbol " + string(sym) + " from CPLEX dll");
return ret;
}
void dll_close(void* dll) {
#ifdef HAS_DLFCN_H
dlclose(dll);
#else
FreeLibrary((HMODULE)dll);
#endif
}
} // namespace
#endif
const vector<string>& CPLEXDLLs(void) {
static const vector<string> sCPLEXDLLs = {"cplex1290", "cplex1280", "cplex1270"};
return sCPLEXDLLs;
}
void MIP_cplex_wrapper::checkDLL() {
#ifdef CPLEX_PLUGIN
_cplex_dll = NULL;
if (options->sCPLEXDLL.size()) {
_cplex_dll = dll_open(options->sCPLEXDLL.c_str());
} else {
for (const auto& s : CPLEXDLLs()) {
_cplex_dll = dll_open(s.c_str());
if (NULL != _cplex_dll) {
break;
}
}
}
if (_cplex_dll == NULL) {
if (options->sCPLEXDLL.empty()) {
throw MiniZinc::InternalError("cannot load cplex dll, specify --cplex-dll");
} else {
throw MiniZinc::InternalError("cannot load cplex dll `" + options->sCPLEXDLL + "'");
}
}
*(void**)(&dll_CPXaddfuncdest) = dll_sym(_cplex_dll, "CPXaddfuncdest");
*(void**)(&dll_CPXaddindconstr) = dll_sym(_cplex_dll, "CPXaddindconstr");
*(void**)(&dll_CPXaddlazyconstraints) = dll_sym(_cplex_dll, "CPXaddlazyconstraints");
*(void**)(&dll_CPXaddmipstarts) = dll_sym(_cplex_dll, "CPXaddmipstarts");
*(void**)(&dll_CPXaddrows) = dll_sym(_cplex_dll, "CPXaddrows");
*(void**)(&dll_CPXaddusercuts) = dll_sym(_cplex_dll, "CPXaddusercuts");
*(void**)(&dll_CPXchgbds) = dll_sym(_cplex_dll, "CPXchgbds");
*(void**)(&dll_CPXchgmipstarts) = dll_sym(_cplex_dll, "CPXchgmipstarts");
*(void**)(&dll_CPXchgobjsen) = dll_sym(_cplex_dll, "CPXchgobjsen");
*(void**)(&dll_CPXcloseCPLEX) = dll_sym(_cplex_dll, "CPXcloseCPLEX");
*(void**)(&dll_CPXcreateprob) = dll_sym(_cplex_dll, "CPXcreateprob");
*(void**)(&dll_CPXcutcallbackadd) = dll_sym(_cplex_dll, "CPXcutcallbackadd");
*(void**)(&dll_CPXfreeprob) = dll_sym(_cplex_dll, "CPXfreeprob");
*(void**)(&dll_CPXgetbestobjval) = dll_sym(_cplex_dll, "CPXgetbestobjval");
*(void**)(&dll_CPXgetcallbackincumbent) = dll_sym(_cplex_dll, "CPXgetcallbackincumbent");
*(void**)(&dll_CPXgetcallbackinfo) = dll_sym(_cplex_dll, "CPXgetcallbackinfo");
*(void**)(&dll_CPXgetcallbacknodeinfo) = dll_sym(_cplex_dll, "CPXgetcallbacknodeinfo");
*(void**)(&dll_CPXgetcallbacknodex) = dll_sym(_cplex_dll, "CPXgetcallbacknodex");
*(void**)(&dll_CPXgetchannels) = dll_sym(_cplex_dll, "CPXgetchannels");
*(void**)(&dll_CPXgetdettime) = dll_sym(_cplex_dll, "CPXgetdettime");
*(void**)(&dll_CPXgeterrorstring) = dll_sym(_cplex_dll, "CPXgeterrorstring");
*(void**)(&dll_CPXgetmipstartindex) = dll_sym(_cplex_dll, "CPXgetmipstartindex");
*(void**)(&dll_CPXgetnodecnt) = dll_sym(_cplex_dll, "CPXgetnodecnt");
*(void**)(&dll_CPXgetnodeleftcnt) = dll_sym(_cplex_dll, "CPXgetnodeleftcnt");
*(void**)(&dll_CPXgetnumcols) = dll_sym(_cplex_dll, "CPXgetnumcols");
*(void**)(&dll_CPXgetnumrows) = dll_sym(_cplex_dll, "CPXgetnumrows");
*(void**)(&dll_CPXgetobjsen) = dll_sym(_cplex_dll, "CPXgetobjsen");
*(void**)(&dll_CPXgetobjval) = dll_sym(_cplex_dll, "CPXgetobjval");
*(void**)(&dll_CPXgetsolnpoolnumsolns) = dll_sym(_cplex_dll, "CPXgetsolnpoolnumsolns");
*(void**)(&dll_CPXgetstat) = dll_sym(_cplex_dll, "CPXgetstat");
*(void**)(&dll_CPXgetstatstring) = dll_sym(_cplex_dll, "CPXgetstatstring");
*(void**)(&dll_CPXgettime) = dll_sym(_cplex_dll, "CPXgettime");
*(void**)(&dll_CPXgetx) = dll_sym(_cplex_dll, "CPXgetx");
*(void**)(&dll_CPXmipopt) = dll_sym(_cplex_dll, "CPXmipopt");
*(void**)(&dll_CPXnewcols) = dll_sym(_cplex_dll, "CPXnewcols");
*(void**)(&dll_CPXopenCPLEX) = dll_sym(_cplex_dll, "CPXopenCPLEX");
*(void**)(&dll_CPXreadcopyparam) = dll_sym(_cplex_dll, "CPXreadcopyparam");
*(void**)(&dll_CPXsetdblparam) = dll_sym(_cplex_dll, "CPXsetdblparam");
*(void**)(&dll_CPXsetinfocallbackfunc) = dll_sym(_cplex_dll, "CPXsetinfocallbackfunc");
*(void**)(&dll_CPXsetintparam) = dll_sym(_cplex_dll, "CPXsetintparam");
*(void**)(&dll_CPXsetlazyconstraintcallbackfunc) =
dll_sym(_cplex_dll, "CPXsetlazyconstraintcallbackfunc");
*(void**)(&dll_CPXsetusercutcallbackfunc) = dll_sym(_cplex_dll, "CPXsetusercutcallbackfunc");
*(void**)(&dll_CPXversion) = dll_sym(_cplex_dll, "CPXversion");
*(void**)(&dll_CPXwriteparam) = dll_sym(_cplex_dll, "CPXwriteparam");
*(void**)(&dll_CPXwriteprob) = dll_sym(_cplex_dll, "CPXwriteprob");
#else
dll_CPXaddfuncdest = CPXaddfuncdest;
dll_CPXaddindconstr = CPXaddindconstr;
dll_CPXaddlazyconstraints = CPXaddlazyconstraints;
dll_CPXaddmipstarts = CPXaddmipstarts;
dll_CPXaddrows = CPXaddrows;
dll_CPXaddusercuts = CPXaddusercuts;
dll_CPXchgbds = CPXchgbds;
dll_CPXchgmipstarts = CPXchgmipstarts;
dll_CPXchgobjsen = CPXchgobjsen;
dll_CPXcloseCPLEX = CPXcloseCPLEX;
dll_CPXcreateprob = CPXcreateprob;
dll_CPXcutcallbackadd = CPXcutcallbackadd;
dll_CPXfreeprob = CPXfreeprob;
dll_CPXgetbestobjval = CPXgetbestobjval;
dll_CPXgetcallbackincumbent = CPXgetcallbackincumbent;
dll_CPXgetcallbackinfo = CPXgetcallbackinfo;
dll_CPXgetcallbacknodeinfo = CPXgetcallbacknodeinfo;
dll_CPXgetcallbacknodex = CPXgetcallbacknodex;
dll_CPXgetchannels = CPXgetchannels;
dll_CPXgetdettime = CPXgetdettime;
dll_CPXgeterrorstring = CPXgeterrorstring;
dll_CPXgetmipstartindex = CPXgetmipstartindex;
dll_CPXgetnodecnt = CPXgetnodecnt;
dll_CPXgetnodeleftcnt = CPXgetnodeleftcnt;
dll_CPXgetnumcols = CPXgetnumcols;
dll_CPXgetnumrows = CPXgetnumrows;
dll_CPXgetobjsen = CPXgetobjsen;
dll_CPXgetobjval = CPXgetobjval;
dll_CPXgetsolnpoolnumsolns = CPXgetsolnpoolnumsolns;
dll_CPXgetstat = CPXgetstat;
dll_CPXgetstatstring = CPXgetstatstring;
dll_CPXgettime = CPXgettime;
dll_CPXgetx = CPXgetx;
dll_CPXmipopt = CPXmipopt;
dll_CPXnewcols = CPXnewcols;
dll_CPXopenCPLEX = CPXopenCPLEX;
dll_CPXreadcopyparam = CPXreadcopyparam;
dll_CPXsetdblparam = CPXsetdblparam;
dll_CPXsetinfocallbackfunc = CPXsetinfocallbackfunc;
dll_CPXsetintparam = CPXsetintparam;
dll_CPXsetlazyconstraintcallbackfunc = CPXsetlazyconstraintcallbackfunc;
dll_CPXsetusercutcallbackfunc = CPXsetusercutcallbackfunc;
dll_CPXversion = CPXversion;
dll_CPXwriteparam = CPXwriteparam;
dll_CPXwriteprob = CPXwriteprob;
#endif
}
string MIP_cplex_wrapper::getDescription(MiniZinc::SolverInstanceBase::Options* opt) {
string v = "MIP wrapper for IBM ILOG CPLEX ";
int status;
Options def_options;
Options* options = opt ? static_cast<Options*>(opt) : &def_options;
try {
MIP_cplex_wrapper mcw(options);
CPXENVptr env = mcw.dll_CPXopenCPLEX(&status);
if (env) {
v += mcw.dll_CPXversion(env);
status = mcw.dll_CPXcloseCPLEX(&env);
} else
v += "[?? ...cannot open CPLEX env to query version]";
} catch (MiniZinc::InternalError&) {
v += "[?? ...cannot open CPLEX env to query version]";
}
v += " Compiled " __DATE__ " " __TIME__;
return v;
}
string MIP_cplex_wrapper::getVersion(MiniZinc::SolverInstanceBase::Options* opt) {
string v;
int status;
Options def_options;
Options* options = opt ? static_cast<Options*>(opt) : &def_options;
try {
MIP_cplex_wrapper mcw(options);
CPXENVptr env = mcw.dll_CPXopenCPLEX(&status);
if (env) {
v += mcw.dll_CPXversion(env);
status = mcw.dll_CPXcloseCPLEX(&env);
} else {
v += "<unknown version>";
}
} catch (MiniZinc::InternalError&) {
v += "<unknown version>";
}
return v;
}
std::string MIP_cplex_wrapper::needDllFlag(void) {
int status;
Options options;
try {
MIP_cplex_wrapper mcw(&options);
CPXENVptr env = mcw.dll_CPXopenCPLEX(&status);
if (env) {
return "";
}
} catch (MiniZinc::InternalError&) {
}
return "--cplex-dll";
}
string MIP_cplex_wrapper::getId() { return "cplex"; }
string MIP_cplex_wrapper::getName() { return "CPLEX"; }
vector<string> MIP_cplex_wrapper::getTags() { return {"mip", "float", "api"}; }
vector<string> MIP_cplex_wrapper::getStdFlags() { return {"-a", "-n", "-p", "-s"}; }
void MIP_cplex_wrapper::Options::printHelp(ostream& os) {
os << "IBM ILOG CPLEX MIP wrapper options:"
<< std::endl
// -s print statistics
// << " --readParam <file> read CPLEX parameters from file
// << "--writeParam <file> write CPLEX parameters to file
// << "--tuneParam instruct CPLEX to tune parameters instead of solving
<< " --mipfocus <n>\n 1: feasibility, 2: optimality, 3: move bound (default is 0, "
"balanced)"
<< std::endl
<< " -a\n print intermediate solutions (use for optimization problems only TODO)"
<< std::endl
<< " -p <N>\n use N threads, default: 1"
<< std::endl
// << " --nomippresolve disable MIP presolving NOT IMPL" << std::endl
<< " --solver-time-limit <N>\n stop search after N milliseconds wall time" << std::endl
<< " -n <N>, --num-solutions <N>\n"
" stop search after N solutions"
<< std::endl
<< " --workmem <N>, --nodefilestart <N>\n"
" maximal RAM for working memory used before writing to node file, GB, default: 3"
<< std::endl
<< " --writeModel <file>\n write model to <file> (.lp, .mps, .sav, ...)" << std::endl
<< " --readParam <file>\n read CPLEX parameters from file" << std::endl
<< " --writeParam <file>\n write CPLEX parameters to file"
<< std::endl
// << " --tuneParam instruct CPLEX to tune parameters instead of solving NOT IMPL"
<< " --absGap <n>\n absolute gap |primal-dual| to stop" << std::endl
<< " --relGap <n>\n relative gap |primal-dual|/<solver-dep> to stop. Default 1e-8, set <0 "
"to use backend's default"
<< std::endl
<< " --intTol <n>\n integrality tolerance for a variable. Default 1e-8" << std::endl
<< "\n --cplex-dll <file> or <basename>\n CPLEX DLL, or base name, such as cplex1280, "
"when using plugin. Default range tried: "
<< CPLEXDLLs().front() << " .. " << CPLEXDLLs().back()
<< std::endl
// << " --objDiff <n> objective function discretization. Default 1.0" << std::endl
<< std::endl;
}
bool MIP_cplex_wrapper::Options::processOption(int& i, std::vector<std::string>& argv) {
MiniZinc::CLOParser cop(i, argv);
if (string(argv[i]) == "-a" || string(argv[i]) == "--all" ||
string(argv[i]) == "--all-solutions") {
flag_all_solutions = true;
} else if (string(argv[i]) == "-f") {
// std::cerr << " Flag -f: ignoring fixed strategy anyway." << std::endl;
} else if (cop.get("--mipfocus --mipFocus --MIPFocus --MIPfocus", &nMIPFocus)) {
} else if (cop.get("--writeModel", &sExportModel)) {
} else if (cop.get("-p", &nThreads)) {
} else if (cop.get("--solver-time-limit", &nTimeout)) {
} else if (cop.get("-n --num-solutions", &nSolLimit)) {
} else if (cop.get("--workmem --nodefilestart", &nWorkMemLimit)) {
} else if (cop.get("--readParam", &sReadParams)) {
} else if (cop.get("--writeParam", &sWriteParams)) {
} else if (cop.get("--absGap", &absGap)) {
} else if (cop.get("--relGap", &relGap)) {
} else if (cop.get("--intTol", &intTol)) {
} else if (cop.get("--cplex-dll", &sCPLEXDLL)) {
// } else if ( cop.get( "--objDiff", &objDiff ) ) {
} else
return false;
return true;
}
void MIP_cplex_wrapper::wrap_assert(bool cond, string msg, bool fTerm) {
if (!cond) {
strcpy(cplex_buffer, "[NO ERROR STRING GIVEN]");
dll_CPXgeterrorstring(env, status, cplex_buffer);
string msgAll = (" MIP_cplex_wrapper runtime error: " + msg + " " + cplex_buffer);
cerr << msgAll << endl;
if (fTerm) {
cerr << "TERMINATING." << endl;
throw runtime_error(msgAll);
}
}
}
void MIP_cplex_wrapper::openCPLEX() {
checkDLL();
cbui.wrapper = this;
/// Cleanup first.
// cleanup();
/* Initialize the CPLEX environment */
env = dll_CPXopenCPLEX(&status);
/* If an error occurs, the status value indicates the reason for
failure. A call to CPXgeterrorstring will produce the text of
the error message. Note that CPXopenCPLEX produces no output,
so the only way to see the cause of the error is to use
CPXgeterrorstring. For other CPLEX routines, the errors will
be seen if the CPXPARAM_ScreenOutput indicator is set to CPX_ON. */
wrap_assert(env, "Could not open CPLEX environment.");
/* Create the problem. */
lp = dll_CPXcreateprob(env, &status, "MIP_cplex_wrapper");
/* A returned pointer of NULL may mean that not enough memory
was available or there was some other problem. In the case of
failure, an error message will have been written to the error
channel from inside CPLEX. In this example, the setting of
the parameter CPXPARAM_ScreenOutput causes the error message to
appear on stdout. */
wrap_assert(lp, "Failed to create LP.");
}
void MIP_cplex_wrapper::closeCPLEX() {
/// Freeing the problem can be slow both in C and C++, see IBM forums. Skipping.
/* Free up the problem as allocated by CPXcreateprob, if necessary */
// if ( lp != NULL ) {
// status = CPXfreeprob (env, &lp);
// cplex_wrap_assert ( !status, "CPXfreeprob failed." );
// }
lp = 0;
/* Free up the CPLEX environment, if necessary */
if (env != NULL) {
status = dll_CPXcloseCPLEX(&env);
wrap_assert(!status, "Could not close CPLEX environment.");
}
/// and at last:
// MIP_wrapper::cleanup();
#ifdef CPLEX_PLUGIN
// dll_close(cplex_dll);
#endif
}
void MIP_cplex_wrapper::doAddVars(size_t n, double* obj, double* lb, double* ub,
MIP_wrapper::VarType* vt, string* names) {
/// Convert var types:
vector<char> ctype(n);
vector<char*> pcNames(n);
for (size_t i = 0; i < n; ++i) {
pcNames[i] = (char*)names[i].c_str();
switch (vt[i]) {
case REAL:
ctype[i] = CPX_CONTINUOUS;
break;
case INT:
ctype[i] = CPX_INTEGER;
break;
case BINARY:
ctype[i] = CPX_BINARY;
break;
default:
throw runtime_error(" MIP_wrapper: unknown variable type");
}
}
status = dll_CPXnewcols(env, lp, n, obj, lb, ub, &ctype[0], &pcNames[0]);
wrap_assert(!status, "Failed to declare variables.");
}
static char getCPLEXConstrSense(MIP_wrapper::LinConType sense) {
switch (sense) {
case MIP_wrapper::LQ:
return 'L';
case MIP_wrapper::EQ:
return 'E';
case MIP_wrapper::GQ:
return 'G';
default:
throw runtime_error(" MIP_cplex_wrapper: unknown constraint type");
}
}
void MIP_cplex_wrapper::addRow(int nnz, int* rmatind, double* rmatval,
MIP_wrapper::LinConType sense, double rhs, int mask,
string rowName) {
/// Convert var types:
char ssense = getCPLEXConstrSense(sense);
const int ccnt = 0;
const int rcnt = 1;
const int rmatbeg[] = {0};
char* pRName = (char*)rowName.c_str();
if (MaskConsType_Normal & mask) {
status = dll_CPXaddrows(env, lp, ccnt, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval,
NULL, &pRName);
wrap_assert(!status, "Failed to add constraint.");
}
if (MaskConsType_Usercut & mask) {
status =
dll_CPXaddusercuts(env, lp, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval, &pRName);
wrap_assert(!status, "Failed to add usercut.");
}
if (MaskConsType_Lazy & mask) {
status = dll_CPXaddlazyconstraints(env, lp, rcnt, nnz, &rhs, &ssense, rmatbeg, rmatind, rmatval,
&pRName);
wrap_assert(!status, "Failed to add lazy constraint.");
}
}
void MIP_cplex_wrapper::addIndicatorConstraint(int iBVar, int bVal, int nnz, int* rmatind,
double* rmatval, MIP_wrapper::LinConType sense,
double rhs, string rowName) {
wrap_assert(0 <= bVal && 1 >= bVal, "mzn-cplex: addIndicatorConstraint: bVal not 0/1");
char ssense = getCPLEXConstrSense(sense);
status = dll_CPXaddindconstr(env, lp, iBVar, 1 - bVal, nnz, rhs, ssense, rmatind, rmatval,
rowName.c_str());
wrap_assert(!status, "Failed to add indicator constraint.");
}
bool MIP_cplex_wrapper::addWarmStart(const std::vector<VarId>& vars,
const std::vector<double> vals) {
assert(vars.size() == vals.size());
const char* sMSName = "MZNMS";
int msindex = -1;
const int beg = 0;
/// Check if we already added a start
status = dll_CPXgetmipstartindex(env, lp, sMSName, &msindex);
if (status) { // not existent
// status = dll_CPXaddmipstarts (env, lp, mcnt, nzcnt, beg, varindices,
// values, effortlevel, mipstartname);
status = dll_CPXaddmipstarts(env, lp, 1, vars.size(), &beg, vars.data(), vals.data(), nullptr,
(char**)&sMSName);
wrap_assert(!status, "Failed to add warm start.");
} else {
// status = dll_CPXchgmipstarts (env, lp, mcnt, mipstartindices, nzcnt, beg, varindices, values,
// effortlevel);
status = dll_CPXchgmipstarts(env, lp, 1, &msindex, vars.size(), &beg, vars.data(), vals.data(),
nullptr);
wrap_assert(!status, "Failed to extend warm start.");
}
return true;
}
void MIP_cplex_wrapper::setVarBounds(int iVar, double lb, double ub) {
wrap_assert(lb <= ub, "mzn-cplex: setVarBounds: lb>ub");
char cl = 'L', cu = 'U';
status = dll_CPXchgbds(env, lp, 1, &iVar, &cl, &lb);
wrap_assert(!status, "Failed to set lower bound.");
status = dll_CPXchgbds(env, lp, 1, &iVar, &cu, &ub);
wrap_assert(!status, "Failed to set upper bound.");
}
void MIP_cplex_wrapper::setVarLB(int iVar, double lb) {
char cl = 'L';
status = dll_CPXchgbds(env, lp, 1, &iVar, &cl, &lb);
wrap_assert(!status, "Failed to set lower bound.");
}
void MIP_cplex_wrapper::setVarUB(int iVar, double ub) {
char cu = 'U';
status = dll_CPXchgbds(env, lp, 1, &iVar, &cu, &ub);
wrap_assert(!status, "Failed to set upper bound.");
}
/// SolutionCallback ------------------------------------------------------------------------
/// CPLEX ensures thread-safety
static int CPXPUBLIC solcallback(CPXCENVptr env, void* cbdata, int wherefrom, void* cbhandle) {
int status = 0;
MIP_wrapper::CBUserInfo* info = (MIP_wrapper::CBUserInfo*)cbhandle;
MIP_cplex_wrapper* cw = static_cast<MIP_cplex_wrapper*>(info->wrapper);
int hasincumbent = 0;
int newincumbent = 0;
double objVal;
status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_NODE_COUNT,
&info->pOutput->nNodes);
if (status) goto TERMINATE;
status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_NODES_LEFT,
&info->pOutput->nOpenNodes);
if (status) goto TERMINATE;
status =
cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_MIP_FEAS, &hasincumbent);
if (status) goto TERMINATE;
if (hasincumbent) {
status =
cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_BEST_INTEGER, &objVal);
if (status) goto TERMINATE;
if (fabs(info->pOutput->objVal - objVal) > 1e-12 * (1.0 + fabs(objVal))) {
newincumbent = 1;
info->pOutput->objVal = objVal;
info->pOutput->status = MIP_wrapper::SAT;
info->pOutput->statusName = "feasible from a callback";
}
}
// if ( nodecnt >= info->lastlog + 100 || newincumbent ) {
// double walltime;
// double dettime;
status = cw->dll_CPXgetcallbackinfo(env, cbdata, wherefrom, CPX_CALLBACK_INFO_BEST_REMAINING,
&info->pOutput->bestBound);
// if ( status ) goto TERMINATE;
// status = dll_CPXgettime (env, &walltime);
// if ( status ) goto TERMINATE;
//
// status = dll_CPXgetdettime (env, &dettime);
// if ( status ) goto TERMINATE;
//
// }
if (newincumbent) {
assert(info->pOutput->x);
status = cw->dll_CPXgetcallbackincumbent(env, cbdata, wherefrom, (double*)info->pOutput->x, 0,
info->pOutput->nCols - 1);
if (status) goto TERMINATE;
info->pOutput->dWallTime =
std::chrono::duration<double>(std::chrono::steady_clock::now() - info->pOutput->dWallTime0)
.count();
info->pOutput->dCPUTime = double(std::clock() - info->pOutput->cCPUTime0) / CLOCKS_PER_SEC;
/// Call the user function:
if (info->solcbfn) (*info->solcbfn)(*info->pOutput, info->psi);
info->printed = true;
}
TERMINATE:
return (status);
} /* END logcallback */
// end SolutionCallback ---------------------------------------------------------------------
/// Cut callbacks, mostly copied from admipex5.c, CPLEX 12.6.3
/* The following macro defines the smallest improvement
on the value of the objective function that is required
when adding user cuts from within a callback.
If the improvement on the value of the ojective function
is not large enough, the callback will abort the cut loop. */
#define EPSOBJ 0.1
/* The following structure will hold the information we need to
pass to the cut callback function */
struct cutinfo {
CPXLPptr lp;
int numcols;
int num;
double* x;
int* beg;
int* ind;
double* val;
double* rhs;
int nodeid;
double nodeobjval;
int objsen;
MIP_wrapper::CBUserInfo* info = 0;
};
typedef struct cutinfo CUTINFO, *CUTINFOptr;
/* Init information on the node objval for the user cut callback */
static void initnodeobjvalinfo(MIP_cplex_wrapper* cw, CPXENVptr env, CPXLPptr lp,
CUTINFOptr cutinfo) {
cutinfo->nodeid = -1;
cutinfo->nodeobjval = 0.0;
cutinfo->objsen = cw->dll_CPXgetobjsen(env, lp);
if (cutinfo->objsen == CPX_MIN)
cutinfo->objsen = 1;
else
cutinfo->objsen = -1;
} /* END initnodeobjvalinfo */
static int CPXPUBLIC myusercutcallback(CPXCENVptr env, void* cbdata, int wherefrom, void* cbhandle,
int* useraction_p) {
int status = 0;
CUTINFOptr cutinfo = (CUTINFOptr)cbhandle;
// int numcols = cutinfo->numcols;
// int numcuts = cutinfo->num;
// double *x = cutinfo->x;
// int *beg = cutinfo->beg;
// int *ind = cutinfo->ind;
// double *val = cutinfo->val;
// double *rhs = cutinfo->rhs;
// int *cutind = NULL;
// double *cutval = NULL;
// double cutvio;
int addedcuts = 0;
// int i, j, k; //, cutnz;
MIP_wrapper::CBUserInfo* info = cutinfo->info;
MIP_cplex_wrapper* cw = static_cast<MIP_cplex_wrapper*>(info->wrapper);
// double *x = info->pCutOutput->x;
*useraction_p = CPX_CALLBACK_DEFAULT;
/* If we are called as a user cut callback, decide
first if we want to add cuts or abort the cut loop.
When adding user cuts with purgeable flag set to
CPX_USECUT_PURGE or CPX_USECUT_FILTER, it is important
to avoid the possibility of an infinite cut loop, where
the same cuts are added to the LP and then immediately
purged at every cut pass. Such a situation can be avoided,
for instance, by applying a tailing off criterion and aborting
the cut loop where no progress in the objval is observed.
Note, however, that the same approach must not be applied
with lazy constraints. In this case, if lazy constraints are
added with purgeable flag set to CPX_USECUT_PURGE, adding
the same lazy constraint more than once could be required
to ensure the correctness of the final result. */
bool fMIPSol = true;
if (wherefrom == CPX_CALLBACK_MIP_CUT_LOOP || wherefrom == CPX_CALLBACK_MIP_CUT_LAST) {
int oldnodeid = cutinfo->nodeid;
double oldnodeobjval = cutinfo->nodeobjval;
fMIPSol = false;
/* Retrieve nodeid and node objval of the current node */
status = cw->dll_CPXgetcallbacknodeinfo(env, cbdata, wherefrom, 0,
CPX_CALLBACK_INFO_NODE_SEQNUM, &cutinfo->nodeid);
if (status) {
fprintf(stderr, "Failed to get node id.\n");
goto TERMINATE;
}
status = cw->dll_CPXgetcallbacknodeinfo(env, cbdata, wherefrom, 0,
CPX_CALLBACK_INFO_NODE_OBJVAL, &cutinfo->nodeobjval);
if (status) {
fprintf(stderr, "Failed to get node objval.\n");
goto TERMINATE;
}
/* Abort the cut loop if we are stuck at the same node
as before and there is no progress in the node objval */
if (oldnodeid == cutinfo->nodeid) {
double objchg = (cutinfo->nodeobjval - oldnodeobjval);
/* Multiply objchg by objsen to normalize
the change in the objective function to
the case of a minimization problem */
objchg *= cutinfo->objsen;
if (objchg <= EPSOBJ) {
*useraction_p = CPX_CALLBACK_ABORT_CUT_LOOP;
goto TERMINATE;
}
}
}
/* If we reached this point, we are
.. in a lazyconstraint callback, or
.. in a user cut callback, and cuts seem to help
improving the node objval.
In both cases, we retrieve the x solution and
look for violated cuts. */
if (info->cutcbfn) { // if cut handler given
MIP_wrapper::Output outpRlx;
outpRlx.x = info->pOutput->x; // using the sol output storage TODO?
outpRlx.nCols = info->pOutput->nCols;
assert(outpRlx.x && outpRlx.nCols);
status = cw->dll_CPXgetcallbacknodex(env, cbdata, wherefrom, (double*)outpRlx.x, 0,
outpRlx.nCols - 1);
if (status) {
fprintf(stderr, "Cut callback: failed to get node solution.\n");
goto TERMINATE;
}
MIP_wrapper::CutInput cutInput;
info->cutcbfn(outpRlx, cutInput, info->psi, fMIPSol);
static int nCuts = 0;
nCuts += cutInput.size();
// if ( cutInput.size() )
// cerr << "\n N CUTS: " << nCuts << endl;
for (auto& cd : cutInput) {
if (!(cd.mask & (MIP_wrapper::MaskConsType_Usercut | MIP_wrapper::MaskConsType_Lazy)))
throw runtime_error("Cut callback: should be user/lazy");
/* Use a cut violation tolerance of 0.01 */
if (true) { // cutvio > 0.01 ) {
status = cw->dll_CPXcutcallbackadd(env, cbdata, wherefrom, cd.rmatind.size(), cd.rhs,
getCPLEXConstrSense(cd.sense), cd.rmatind.data(),
cd.rmatval.data(),
CPX_USECUT_FORCE); // PURGE?
if (status) {
fprintf(stderr, "CPLEX callback: failed to add cut.\n");
goto TERMINATE;
}
addedcuts++;
}
}
}
/* Tell CPLEX that cuts have been created */
if (addedcuts > 0) {
*useraction_p = CPX_CALLBACK_SET;
}
TERMINATE:
return (status);
} /* END myusercutcallback */
// ----------------- END Cut callbacks ------------------
MIP_cplex_wrapper::Status MIP_cplex_wrapper::convertStatus(int cplexStatus) {
Status s = Status::UNKNOWN;
/* Converting the status. */
switch (cplexStatus) {
case CPXMIP_OPTIMAL:
s = Status::OPT;
wrap_assert(dll_CPXgetsolnpoolnumsolns(env, lp), "Optimality reported but pool empty?",
false);
break;
case CPXMIP_INFEASIBLE:
s = Status::UNSAT;
break;
// case CPXMIP_OPTIMAL_INFEAS:
case CPXMIP_INForUNBD:
s = Status::UNSATorUNBND;
break;
case CPXMIP_SOL_LIM:
case CPXMIP_NODE_LIM_FEAS:
case CPXMIP_TIME_LIM_FEAS:
case CPXMIP_FAIL_FEAS:
case CPXMIP_MEM_LIM_FEAS:
case CPXMIP_ABORT_FEAS:
case CPXMIP_FAIL_FEAS_NO_TREE:
s = Status::SAT;
wrap_assert(dll_CPXgetsolnpoolnumsolns(env, lp), "Feasibility reported but pool empty?",
false);
break;
case CPXMIP_UNBOUNDED:
s = Status::UNBND;
break;
// case CPXMIP_ABORT_INFEAS:
case CPXMIP_FAIL_INFEAS:
s = Status::__ERROR;
break;
default:
// case CPXMIP_OPTIMAL_TOL:
// case CPXMIP_ABORT_RELAXATION_UNBOUNDED:
if (dll_CPXgetsolnpoolnumsolns(env, lp))
s = Status::SAT;
else
s = Status::UNKNOWN;
}
return s;
}
void msgfunction(void* handle, const char* msg_string) { cerr << msg_string << flush; }
void MIP_cplex_wrapper::solve() { // Move into ancestor?
/////////////// Last-minute solver options //////////////////
if (options->flag_all_solutions && 0 == nProbType)
cerr << "WARNING. --all-solutions for SAT problems not implemented." << endl;
// Before all manual params ???
if (options->sReadParams.size()) {
status = dll_CPXreadcopyparam(env, options->sReadParams.c_str());
wrap_assert(!status, "Failed to read CPLEX parameters.", false);
}
/* Turn on output to the screen */
if (fVerbose) {
CPXCHANNELptr chnl[4];
dll_CPXgetchannels(env, &chnl[0], &chnl[1], &chnl[2], &chnl[3]);
for (int i = 0; i < 3; ++i) {
status = dll_CPXaddfuncdest(env, chnl[i], nullptr, msgfunction);
}
// status = dll_CPXsetintparam(env, CPXPARAM_ScreenOutput,
// fVerbose ? CPX_ON : CPX_OFF); // also when flag_all_solutions? TODO
// wrap_assert(!status, " CPLEX Warning: Failure to switch screen indicator.", false);
}
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Display,
fVerbose ? 2 : 0); // also when flag_all_solutions? TODO
wrap_assert(!status, " CPLEX Warning: Failure to switch logging.", false);
/// Make it wall time by default, 12.8
// status = dll_CPXsetintparam (env, CPXPARAM_ClockType, 1); // CPU time
// wrap_assert(!status, " CPLEX Warning: Failure to measure CPU time.", false);
status = dll_CPXsetintparam(env, CPX_PARAM_MIPCBREDLP, CPX_OFF); // Access original model
wrap_assert(!status, " CPLEX Warning: Failure to set access original model in callbacks.",
false);
if (options->sExportModel.size()) {
status = dll_CPXwriteprob(env, lp, options->sExportModel.c_str(), NULL);
wrap_assert(!status, "Failed to write LP to disk.", false);
}
/// TODO
// if(all_solutions && obj.getImpl()) {
// IloNum lastObjVal = (obj.getSense() == IloObjective::Minimize ) ?
// _ilocplex->use(SolutionCallback(_iloenv, lastObjVal, *this));
// Turn off CPLEX logging
if (options->nThreads > 0) {
status = dll_CPXsetintparam(env, CPXPARAM_Threads, options->nThreads);
wrap_assert(!status, "Failed to set CPXPARAM_Threads.", false);
}
if (options->nTimeout > 0) {
status = dll_CPXsetdblparam(env, CPXPARAM_TimeLimit,
static_cast<double>(options->nTimeout) / 1000.0);
wrap_assert(!status, "Failed to set CPXPARAM_TimeLimit.", false);
}
if (options->nSolLimit > 0) {
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Limits_Solutions, options->nSolLimit);
wrap_assert(!status, "Failed to set CPXPARAM_MIP_Limits_Solutions.", false);
}
if (options->nMIPFocus > 0) {
status = dll_CPXsetintparam(env, CPXPARAM_Emphasis_MIP, options->nMIPFocus);
wrap_assert(!status, "Failed to set CPXPARAM_Emphasis_MIP.", false);
}
if (options->nWorkMemLimit > 0) {
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_File, 3);
wrap_assert(!status, "Failed to set CPXPARAM_MIP_Strategy_File.", false);
status =
dll_CPXsetdblparam(env, CPXPARAM_WorkMem, 1024.0 * options->nWorkMemLimit); // MB in CPLEX
wrap_assert(!status, "Failed to set CPXPARAM_WorkMem.", false);
}
if (options->absGap >= 0.0) {
status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_AbsMIPGap, options->absGap);
wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_AbsMIPGap.", false);
}
if (options->relGap >= 0.0) {
status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_MIPGap, options->relGap);
wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_MIPGap.", false);
}
if (options->intTol >= 0.0) {
status = dll_CPXsetdblparam(env, CPXPARAM_MIP_Tolerances_Integrality, options->intTol);
wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_Integrality.", false);
}
// status = dll_CPXsetdblparam (env, CPXPARAM_MIP_Tolerances_ObjDifference, objDiff);
// wrap_assert(!status, "Failed to set CPXPARAM_MIP_Tolerances_ObjDifference.", false);
/// Solution callback
output.nCols = colObj.size();
x.resize(output.nCols);
output.x = &x[0];
if (options->flag_all_solutions && cbui.solcbfn) {
status = dll_CPXsetinfocallbackfunc(env, solcallback, &cbui);
wrap_assert(!status, "Failed to set solution callback", false);
}
if (cbui.cutcbfn) {
assert(cbui.cutMask & (MaskConsType_Usercut | MaskConsType_Lazy));
if (cbui.cutMask & MaskConsType_Usercut) {
// For user cuts, needs to keep some info after presolve
if (fVerbose)
cerr << " MIP_cplex_wrapper: user cut callback enabled, setting params" << endl;
CUTINFO usercutinfo; // THREADS? TODO
usercutinfo.info = &cbui;
/* Init information on the node objval for the user cut callback */
initnodeobjvalinfo(this, env, lp, &usercutinfo);
/* Assure linear mappings between the presolved and original
models */
status = dll_CPXsetintparam(env, CPXPARAM_Preprocessing_Linear, 0);
wrap_assert(!status, "CPLEX: setting prepro_linear");
/* Turn on traditional search for use with control callbacks */
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_Search, CPX_MIPSEARCH_TRADITIONAL);
wrap_assert(!status, "CPLEX: setting traditional search");
/* Let MIP callbacks work on the original model */
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_CallbackReducedLP, CPX_OFF);
wrap_assert(!status, "CPLEX: setting callbacks to work on orig model");
/// And
/* Set up to use MIP usercut callback */
status = dll_CPXsetusercutcallbackfunc(env, myusercutcallback, &usercutinfo);
wrap_assert(!status, "CPLEX: setting user cut callback");
}
if (cbui.cutMask & MaskConsType_Lazy) {
if (fVerbose)
cerr << " MIP_cplex_wrapper: lazy cut callback enabled, setting params" << endl;
CUTINFO lazyconinfo;
lazyconinfo.info = &cbui;
/* Init information on the node objval for the user cut callback.
No need to initialize the information on the node objval,
for the lazy constraint callback, because those information are
used only in the user cut callback. */
initnodeobjvalinfo(this, env, lp, &lazyconinfo);
/* Assure linear mappings between the presolved and original
models */
status = dll_CPXsetintparam(env, CPXPARAM_Preprocessing_Linear, 0);
wrap_assert(!status, "CPLEX: setting prepro_linear");
/* Turn on traditional search for use with control callbacks */
// status = dll_CPXsetintparam (env, CPXPARAM_MIP_Strategy_Search,
// CPX_MIPSEARCH_TRADITIONAL);
// wrap_assert ( !status, "CPLEX: setting traditional search" );
/* Let MIP callbacks work on the original model */
status = dll_CPXsetintparam(env, CPXPARAM_MIP_Strategy_CallbackReducedLP, CPX_OFF);
wrap_assert(!status, "CPLEX: setting callbacks to work on orig model");
/* Set up to use MIP lazyconstraint callback. The callback funtion
* registered is the same, but the data will be different. */
status = dll_CPXsetlazyconstraintcallbackfunc(env, myusercutcallback, &lazyconinfo);
wrap_assert(!status, "CPLEX: setting lazy cut callback");
}
}
/// after all modifs
if (options->sWriteParams.size()) {
status = dll_CPXwriteparam(env, options->sWriteParams.c_str());
wrap_assert(!status, "Failed to write CPLEX parameters.", false);
}
// status = dll_CPXgettime (env, &output.dCPUTime);
// wrap_assert(!status, "Failed to get time stamp.", false);
cbui.pOutput->dWallTime0 = output.dWallTime0 = std::chrono::steady_clock::now();
cbui.pOutput->cCPUTime0 = std::clock();
/* Optimize the problem and obtain solution. */
status = dll_CPXmipopt(env, lp);
wrap_assert(!status, "Failed to optimize MIP.");
output.dWallTime =
std::chrono::duration<double>(std::chrono::steady_clock::now() - output.dWallTime0).count();
double tmNow = std::clock();
// status = dll_CPXgettime (env, &tmNow); Buggy in 12.7.1.0
wrap_assert(!status, "Failed to get time stamp.", false);
output.dCPUTime = (tmNow - cbui.pOutput->cCPUTime0) / CLOCKS_PER_SEC;
int solstat = dll_CPXgetstat(env, lp);
output.status = convertStatus(solstat);
output.statusName = dll_CPXgetstatstring(env, solstat, cplex_status_buffer);
/// Continuing to fill the output object:
if (Status::OPT == output.status || Status::SAT == output.status) {
status = dll_CPXgetobjval(env, lp, &output.objVal);
wrap_assert(!status, "No MIP objective value available.");
/* The size of the problem should be obtained by asking CPLEX what
the actual size is, rather than using what was passed to CPXcopylp.
cur_numrows and cur_numcols store the current number of rows and
columns, respectively. */ // ?????????????? TODO
// int cur_numrows = dll_CPXgetnumrows (env, lp);
int cur_numcols = dll_CPXgetnumcols(env, lp);
assert(cur_numcols == colObj.size());
x.resize(cur_numcols);
output.x = &x[0];
status = dll_CPXgetx(env, lp, &x[0], 0, cur_numcols - 1);
wrap_assert(!status, "Failed to get variable values.");
if (cbui.solcbfn /*&& (!options->flag_all_solutions || !cbui.printed)*/) {
cbui.solcbfn(output, cbui.psi);
}
}
output.bestBound = 1e308;
status = dll_CPXgetbestobjval(env, lp, &output.bestBound);
wrap_assert(!status, "Failed to get the best bound.", false);
output.nNodes = dll_CPXgetnodecnt(env, lp);
output.nOpenNodes = dll_CPXgetnodeleftcnt(env, lp);
}
void MIP_cplex_wrapper::setObjSense(int s) {
status = dll_CPXchgobjsen(env, lp, -s); // +1 for min in CPLEX
wrap_assert(!status, "Failed to set obj sense.");
}