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.
Jip J. Dekker 2c8ad0004a Squashed 'software/minizinc/' changes from 4f10c82056..93be33a6c2
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
2021-07-12 20:08:41 +10:00

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();
};