93be33a6c2 Add complete predicate REVERT: 4f10c82056 Merge branch 'MiniZinc:master' into feature/on_restart REVERT: 0848ce7ec7 Add changelog for 2.5.5 REVERT: 44e2f770d5 Add test for insertion of ArrayLits into CSE REVERT: 8a68d3dea8 Don't insert par expressions into CSE map unless they're an ArrayLit REVERT: 6bf6f1180f Increase version number of development build REVERT: dcaac92a74 Make min/max on array of opt vars return non-optional var. This is consistent with other functions on optional arrays like sum, product, exists, forall. REVERT: 32aa288884 Update changelog REVERT: a4edf0669f Fix flattening of all-par set literals REVERT: 8c1c9605f6 Fix chain compressor, needs to ignore par constants REVERT: 0cad1c6306 Use file_path on include paths to ensure separator consistency REVERT: 05ad7d1931 Update changelog REVERT: 22f5e2557b Define HAVE_CONFIG_H only for UNIX REVERT: 81c7778d55 Define HAVE_CONFIG_H for CBC REVERT: 1f56608e10 mzn-test: don't check symmetry breaking constraints REVERT: 1a9767457e mzn-test: fix parsing %%%mzn-stat: ... output REVERT: a41533fd54 MIP: report CPU time in %%%mzn-stat: solveTime= REVERT: 9d490acd52 Updated docs on OR-Tools v8 installation REVERT: c513f6599f Add changelog for 2.5.4 REVERT: b2eef2772b Follow ids to declarations when flattening par arrays. Fixes #448. REVERT: c5c846d426 Check if result of flattening rhs of a vardecl is par. REVERT: c496052767 Escape strings when pretty printing include items REVERT: 9e379c995e Canonicalise file names before adding include items into "already seen" list REVERT: d5d5d0d88c Use generic flattening inside generators. Fixes #451. REVERT: dc8630a6e9 Small fix to multi-pass library change: use original include path if it is absolute REVERT: 79c6092bd8 Strip library paths from includes in multi-pass compilation. Fixes #455. REVERT: 897875d6d7 Compile infinite domains with holes into constraints. Fixes #457. REVERT: b4e700dc67 Don't create copies of global declarations when creating par versions of functions REVERT: 0e8cc42bb1 Fix typechecker to coerce bool to int in the objective. REVERT: e05523b344 Add test for dzn output of arrays REVERT: 1e0269000e Don't evaluate output_only arrays when generating dzn output. REVERT: 57018c31d6 Fix matrix transposition in lex2 globals REVERT: 2617c0c829 Fix output variables in lex_chain tests REVERT: ef1a250c98 another efort to fix tests REVERT: c00e199dfd Fix test globals_lex_chain.mzn REVERT: b5c997d045 Fix code analysis REVERT: 3352cf0bd5 SCIP constraint handler for lex_chain_..._orbitope REVERT: 4e71a2cc97 Globals lex_chain_..., including lex_chain_..._orbitope REVERT: d807428baf Move test specifcation into the correct folder REVERT: 5be74bc74d MIP decompositions for lex_less_(bool, int, float) REVERT: 36a554ba40 Don't modify infinte domain of optional variables. Fixes #456. REVERT: f9e5306d75 Run clang-format REVERT: 4b57667608 Fix comment reference to relevant test case REVERT: 648f2ab36d Fix equality of indirection annotations REVERT: ef7be5fd78 MIP decompositions for lex_lesseq_(bool=int, float) REVERT: 6511b14e73 Propagate cv flag correctly. REVERT: 6f27ecf1c0 Never insert par expressions into the CSE map. REVERT: 7414f3ca0f Fix cplex id in example configuration REVERT: 7ad7cec506 Update strictly_decreasing with documentation and opt version REVERT: 8029f6e957 Support undefined enums in type checker. REVERT: 79e0f0f546 Revert using mzn_in_symmetry_breaking_constraint() for SCIP's orbisack REVERT: e88efda76c Fix format REVERT: 4802031dc1 Added test mzn_in_symmetry_breaking_constraint() REVERT: e21cc2515a More format fixes 02 ... REVERT: 5bbd67c130 More format fixes... REVERT: d5f9b777ea Format fixes REVERT: 43757a09a0 Remove MIP-specific fzn_less(eq)_bool(_reif).mzn REVERT: c93b5736a3 SCIP: orbisack constraint handler 'fzn_lex_lesseq__orbisack' REVERT: 4516bb4e2c mzn-test.py: add OR-Tools as checker REVERT: e2176f017d Add fix and test for crash with empty enum. REVERT: ac7db35951 Fix documentation bugs. REVERT: 47ba245832 Fix the incorrect renaming of key in model output interface REVERT: 925796ed20 Fail on empty var domains in agenda REVERT: 1ec19d7025 Fix error messages in CMake FindGurobi, FindCPlex REVERT: 6d169475c1 mzn-test.py: Use -i for intermediate solutions REVERT: df2f3e423a Allow coercion of JSON lists to enum definitions REVERT: 2b0b8165e5 Fix clang-tidy errors REVERT: 6597bc1920 Change the CI build image location REVERT: 360c988452 Remove illegal duplicate keys in .gitlab-ci.yml REVERT: 6a5d69c64b Add missing par opt versions of coercion functions REVERT: 63014e3d8f Don't propagate annotations into annotation calls. Avoids infinite recursion. REVERT: 54b19428ab Don't use GRB_INT_PAR_NONCONVEX if it's undefined REVERT: a5bb56c47d Added piecewise_linear for non-continuous intervals git-subtree-dir: software/minizinc git-subtree-split: 93be33a6c254e54be7cd38abb7ebd6a6022f0c46
348 lines
12 KiB
C++
348 lines
12 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/. */
|
|
|
|
#pragma once
|
|
|
|
#include <utility>
|
|
#include <vector>
|
|
//#include <map>
|
|
#include <minizinc/solver_instance_defs.hh>
|
|
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
/// Facilitate lhs computation of a cut
|
|
inline double compute_sparse(int n, const int* ind, const double* coef, const double* dense,
|
|
int nVarsDense) {
|
|
assert(ind && coef && dense);
|
|
double val = 0.0;
|
|
for (int i = 0; i < n; ++i) {
|
|
assert(ind[i] >= 0);
|
|
assert(ind[i] < nVarsDense);
|
|
val += coef[i] * dense[ind[i]];
|
|
}
|
|
return val;
|
|
}
|
|
|
|
class MIPWrapper;
|
|
|
|
/// An abstract MIP wrapper.
|
|
/// Does not include MZN stuff so can be used independently
|
|
/// although it's limited to the MZN solving needs.
|
|
class MIPWrapper {
|
|
public:
|
|
typedef int VarId; // CPLEX uses int
|
|
enum VarType { REAL, INT, BINARY };
|
|
enum LinConType { LQ = -1, EQ = 0, GQ = 1 };
|
|
|
|
// CPLEX 12.6.2 advises anti-symmetry constraints to be user+lazy
|
|
static const int MaskConsType_Normal = 1;
|
|
/// User cut. Only cuts off fractional points, no integer feasible points
|
|
static const int MaskConsType_Usercut = 2;
|
|
/// Lazy cut. Can cut off otherwise feasible integer solutions.
|
|
/// Callback should be able to produce previously generated cuts again if needed [Gurobi]
|
|
static const int MaskConsType_Lazy = 4;
|
|
enum Status { OPT, SAT, UNSAT, UNBND, UNSATorUNBND, UNKNOWN, __ERROR };
|
|
|
|
/// Search strategy for the solver
|
|
enum SearchType { FIXED_SEARCH = 0, FREE_SEARCH = 1, UNIFORM_SEARCH = 2 };
|
|
|
|
/// Columns for SCIP upfront and with obj coefs:
|
|
std::vector<double> colObj, colLB, colUB;
|
|
std::vector<VarType> colTypes;
|
|
std::vector<std::string> colNames;
|
|
// , rowLB, rowUB, elements;
|
|
// veci whichInt
|
|
// , starts, column;
|
|
// double objUB;
|
|
// double qpu;
|
|
|
|
/// Parameter
|
|
bool fVerbose = false;
|
|
|
|
int nProbType = -2; // +-1: max/min; 0: sat
|
|
|
|
struct Output {
|
|
Status status;
|
|
std::string statusName = "Untouched";
|
|
double objVal = 1e308;
|
|
double bestBound = 1e308;
|
|
int nCols = 0;
|
|
int nObjVarIndex = -1;
|
|
const double* x = nullptr;
|
|
int nNodes = 0;
|
|
int nOpenNodes = 0;
|
|
double dWallTime = 0.0;
|
|
std::chrono::time_point<std::chrono::steady_clock> dWallTime0;
|
|
double dCPUTime = 0;
|
|
std::clock_t cCPUTime0 = 0;
|
|
};
|
|
Output output;
|
|
|
|
/// General cut definition, could be used for addRow() too
|
|
class CutDef {
|
|
CutDef() {}
|
|
|
|
public:
|
|
CutDef(LinConType s, int m) : sense(s), mask(m) {}
|
|
std::vector<int> rmatind;
|
|
std::vector<double> rmatval;
|
|
LinConType sense = LQ;
|
|
double rhs = 0.0;
|
|
int mask = 0; // need to know what type of cuts are registered before solve() TODO
|
|
std::string rowName = "";
|
|
void addVar(int i, double c) {
|
|
rmatind.push_back(i);
|
|
rmatval.push_back(c);
|
|
}
|
|
double computeViol(const double* x, int nCols) {
|
|
double lhs = compute_sparse(static_cast<int>(rmatind.size()), rmatind.data(), rmatval.data(),
|
|
x, nCols);
|
|
if (LQ == sense) {
|
|
return lhs - rhs;
|
|
}
|
|
if (GQ == sense) {
|
|
return rhs - lhs;
|
|
}
|
|
assert(0);
|
|
|
|
return 0.0;
|
|
}
|
|
};
|
|
/// Cut callback fills one
|
|
typedef std::vector<CutDef> CutInput;
|
|
|
|
/// solution callback handler, the wrapper might not have these callbacks implemented
|
|
typedef void (*SolCallbackFn)(const Output&, void*);
|
|
/// cut callback handler, the wrapper might not have these callbacks implemented
|
|
typedef void (*CutCallbackFn)(const Output&, CutInput&, void*,
|
|
bool fMIPSol // if with a MIP feas sol - lazy cuts only
|
|
);
|
|
struct CBUserInfo {
|
|
MIPWrapper* wrapper = nullptr;
|
|
MIPWrapper::Output* pOutput = nullptr;
|
|
MIPWrapper::Output* pCutOutput = nullptr;
|
|
void* psi = nullptr; // external info. Intended to keep MIPSolverinstance
|
|
SolCallbackFn solcbfn = nullptr;
|
|
CutCallbackFn cutcbfn = nullptr;
|
|
/// Union of all flags used for the registered callback cuts
|
|
/// See MaskConstrType_..
|
|
/// Solvers need to know this
|
|
/// In MIPSolverinstance, class CutGen defines getMask() which should return that
|
|
int cutMask = 0; // can be any combination of User/Lazy
|
|
bool fVerb = false; // used in Gurobi
|
|
bool printed = false; // whether any solution was output
|
|
double nTimeoutFeas = -1.0; // >=0 => stop that long after 1st feas
|
|
double nTime1Feas = -1e100; // time of the 1st feas
|
|
};
|
|
CBUserInfo cbui;
|
|
|
|
MIPWrapper() { cbui.wrapper = this; }
|
|
virtual ~MIPWrapper() { /* cleanup(); */
|
|
}
|
|
|
|
/// derived should overload and call the ancestor
|
|
// virtual void cleanup() {
|
|
// colObj.clear(); colLB.clear(); colUB.clear();
|
|
// colTypes.clear(); colNames.clear();
|
|
// }
|
|
/// re-create solver object. Called from the base class constructor
|
|
// virtual void resetModel() { };
|
|
|
|
// virtual void printVersion(ostream& os) { os << "Abstract MIP wrapper"; }
|
|
// virtual void printHelp(ostream& ) { }
|
|
|
|
bool fPhase1Over = false;
|
|
|
|
private:
|
|
/// adding a variable just internally (in Phase 1 only that). Not to be used directly.
|
|
virtual VarId addVarLocal(double obj, double lb, double ub, VarType vt,
|
|
const std::string& name = "") {
|
|
// cerr << " addVarLocal: colObj.size() == " << colObj.size()
|
|
// << " obj == " <<obj
|
|
// << " lb == " << lb
|
|
// << " ub == " << ub
|
|
// << " vt == " << vt
|
|
// << " nm == " << name
|
|
// << endl;
|
|
colObj.push_back(obj);
|
|
colLB.push_back(lb);
|
|
colUB.push_back(ub);
|
|
colTypes.push_back(vt);
|
|
colNames.push_back(name);
|
|
return static_cast<VarId>(colObj.size() - 1);
|
|
}
|
|
/// add the given var to the solver. Asserts all previous are added. Phase >=2. No direct use
|
|
virtual void addVar(int j) {
|
|
assert(j == getNCols());
|
|
assert(fPhase1Over);
|
|
doAddVars(1, &colObj[j], &colLB[j], &colUB[j], &colTypes[j], &colNames[j]);
|
|
}
|
|
/// actual adding new variables to the solver. "Updates" the model (e.g., Gurobi). No direct use
|
|
virtual void doAddVars(size_t n, double* obj, double* lb, double* ub, VarType* vt,
|
|
std::string* names) = 0;
|
|
|
|
public:
|
|
/// debugging stuff
|
|
// set<double> sLitValues;
|
|
std::unordered_map<double, VarId> sLitValues;
|
|
|
|
void setProbType(int t) { nProbType = t; }
|
|
|
|
/// adding a variable, at once to the solver, this is for the 2nd phase
|
|
virtual VarId addVar(double obj, double lb, double ub, VarType vt, const std::string& name = "") {
|
|
// cerr << " AddVar: " << lb << ": ";
|
|
VarId res = addVarLocal(obj, lb, ub, vt, name);
|
|
if (fPhase1Over) {
|
|
addVar(res);
|
|
}
|
|
return res;
|
|
}
|
|
int nLitVars = 0;
|
|
/// adding a literal as a variable. Should not happen in feasible models
|
|
virtual VarId addLitVar(double v) {
|
|
// Cannot do this: at least CBC does not support duplicated indexes TODO??
|
|
// ++nLitVars;
|
|
// auto itFound = sLitValues.find(v);
|
|
// if (sLitValues.end() != itFound)
|
|
// return itFound->second;
|
|
std::ostringstream oss;
|
|
oss << "lit_" << v << "__" << (nLitVars++);
|
|
std::string name = oss.str();
|
|
size_t pos = name.find('.');
|
|
if (std::string::npos != pos) {
|
|
name.replace(pos, 1, "p");
|
|
}
|
|
VarId res = addVarLocal(0.0, v, v, REAL, name);
|
|
if (fPhase1Over) {
|
|
addVar(res);
|
|
}
|
|
// cerr << " AddLitVar " << v << " (PROBABLY WRONG)" << endl;
|
|
sLitValues[v] = res;
|
|
return res;
|
|
}
|
|
/// adding all local variables upfront. Makes sure it's called only once
|
|
virtual void addPhase1Vars() {
|
|
assert(0 == getNColsModel());
|
|
assert(!fPhase1Over);
|
|
if (fVerbose) {
|
|
std::cerr << " MIPWrapper: adding the " << colObj.size() << " Phase-1 variables..."
|
|
<< std::flush;
|
|
}
|
|
if (!colObj.empty()) {
|
|
doAddVars(colObj.size(), &colObj[0], &colLB[0], &colUB[0], &colTypes[0], &colNames[0]);
|
|
}
|
|
if (fVerbose) {
|
|
std::cerr << " done." << std::endl;
|
|
}
|
|
fPhase1Over = true; // SCIP needs after adding
|
|
}
|
|
|
|
/// var bounds
|
|
virtual void setVarBounds(int iVar, double lb, double ub) { throw 0; }
|
|
virtual void setVarLB(int iVar, double lb) { throw 0; }
|
|
virtual void setVarUB(int iVar, double ub) { throw 0; }
|
|
/// adding a linear constraint
|
|
virtual void addRow(int nnz, int* rmatind, double* rmatval, LinConType sense, double rhs,
|
|
int mask = MaskConsType_Normal, const std::string& rowName = "") = 0;
|
|
/// Indicator constraint: x[iBVar]==bVal -> lin constr
|
|
virtual void addIndicatorConstraint(int iBVar, int bVal, int nnz, int* rmatind, double* rmatval,
|
|
LinConType sense, double rhs,
|
|
const std::string& rowName = "") {
|
|
throw std::runtime_error("Indicator constraints not supported. ");
|
|
}
|
|
virtual void addMinimum(int iResultVar, int nnz, int* ind, const std::string& rowName = "") {
|
|
throw std::runtime_error("This backend does not support the Minimum constraint");
|
|
}
|
|
|
|
/// Bounds disj for SCIP
|
|
virtual void addBoundsDisj(int n, double* fUB, double* bnd, int* vars, int nF, double* fUBF,
|
|
double* bndF, int* varsF, const std::string& rowName = "") {
|
|
throw std::runtime_error("Bounds disjunctions not supported. ");
|
|
}
|
|
/// Times constraint: var[x]*var[y] == var[z]
|
|
virtual void addTimes(int x, int y, int z, const std::string& rowName = "") {
|
|
throw std::runtime_error("Backend: [int/float]_times not supported. ");
|
|
}
|
|
|
|
/// Cumulative, currently SCIP only
|
|
virtual void addCumulative(int nnz, int* rmatind, double* d, double* r, double b,
|
|
const std::string& rowName = "") {
|
|
throw std::runtime_error("Cumulative constraints not supported. ");
|
|
}
|
|
/// 0: model-defined level, 1: free, 2: uniform search
|
|
virtual int getFreeSearch() { return SearchType::FREE_SEARCH; }
|
|
/// Return 0 if ignoring searches
|
|
virtual bool addSearch(const std::vector<VarId>& vars, const std::vector<int>& pri) {
|
|
return false;
|
|
}
|
|
/// Return 0 if ignoring warm starts
|
|
virtual bool addWarmStart(const std::vector<VarId>& vars, const std::vector<double>& vals) {
|
|
return false;
|
|
}
|
|
|
|
using MultipleObjectives = MiniZinc::MultipleObjectivesTemplate<VarId>;
|
|
virtual bool defineMultipleObjectives(const MultipleObjectives& mo) { return false; }
|
|
|
|
int nAddedRows = 0; // for name counting
|
|
int nIndicatorConstr = 0;
|
|
/// adding an implication
|
|
// virtual void addImpl() = 0;
|
|
virtual void setObjSense(int s) = 0; // +/-1 for max/min
|
|
|
|
virtual double getInfBound() = 0;
|
|
|
|
virtual int getNCols() = 0;
|
|
virtual int getNColsModel() { return getNCols(); } // from the solver
|
|
virtual int getNRows() = 0;
|
|
|
|
// void setObjUB(double ub) { objUB = ub; }
|
|
// void addQPUniform(double c) { qpu = c; } // also sets problem type to MIQP unless c=0
|
|
|
|
/// Set solution callback. Thread-safety??
|
|
/// solution callback handler, the wrapper might not have these callbacks implemented
|
|
virtual void provideSolutionCallback(SolCallbackFn cbfn, void* info) {
|
|
assert(cbfn);
|
|
cbui.pOutput = &output;
|
|
cbui.psi = info;
|
|
cbui.solcbfn = cbfn;
|
|
}
|
|
/// solution callback handler, the wrapper might not have these callbacks implemented
|
|
virtual void provideCutCallback(CutCallbackFn cbfn, void* info) {
|
|
assert(cbfn);
|
|
cbui.pCutOutput = nullptr; // &outpCuts; thread-safety: caller has to provide this
|
|
cbui.psi = info;
|
|
cbui.cutcbfn = cbfn;
|
|
}
|
|
|
|
virtual void solve() = 0;
|
|
|
|
/// OUTPUT, should also work in a callback
|
|
virtual const double* getValues() = 0;
|
|
virtual double getObjValue() = 0;
|
|
virtual double getBestBound() = 0;
|
|
virtual double getWallTimeElapsed() { return output.dWallTime; }
|
|
virtual double getCPUTime() = 0;
|
|
|
|
virtual Status getStatus() = 0;
|
|
virtual std::string getStatusName() = 0;
|
|
|
|
virtual int getNNodes() = 0;
|
|
virtual int getNOpen() = 0;
|
|
|
|
/// Default MZN library for MIP
|
|
static std::string getMznLib();
|
|
};
|