/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Gleb Belov */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was ! 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 #include #include #include #include #include #include #include // temporary #include //#include #include #include #include /// TODOs /// TODO Not going to work for float vars because of round-offs in the domain interval sorting... /// set_in etc. are ! propagated between views /// CLEANUP after work: ~destructor /// Also check initexpr of all vars? DONE /// In case of only_range_domains we'd need to register inequalities /// - so better turn that off TODO /// CSE for lineq coefs TODO /// TODO use integer division instead of INT_EPS #define INT_EPS 1e-5 // the absolute epsilon for integrality of integer vars. #define __MZN__MIPDOMAINS__PRINTMORESTATS #define MZN_DBG_CHECK_ITER_CUTOUT // #define __MZN__DBGOUT__MIPDOMAINS__ #ifdef __MZN__DBGOUT__MIPDOMAINS__ #define DBGOUT_MIPD(s) std::cerr << s << std::endl #define DBGOUT_MIPD__(s) std::cerr << s << std::flush #define DBGOUT_MIPD_SELF(op) op #else #define DBGOUT_MIPD(s) \ do { \ } while (false) #define DBGOUT_MIPD__(s) \ do { \ } while (false) #define DBGOUT_MIPD_SELF(op) \ do { \ } while (false) #endif namespace MiniZinc { enum EnumStatIdx__MIPD { N_POSTs__all, // N all POSTs in the model N_POSTs__intCmpReif, N_POSTs__floatCmpReif, // in detail N_POSTs__intNE, N_POSTs__floatNE, N_POSTs__setIn, N_POSTs__domain, N_POSTs__setInReif, N_POSTs__eq_encode, N_POSTs__intAux, N_POSTs__floatAux, // Kind of equality connections between involved variables N_POSTs__eq2intlineq, N_POSTs__eq2floatlineq, N_POSTs__int2float, N_POSTs__internalvarredef, N_POSTs__initexpr1id, N_POSTs__initexpr1linexp, N_POSTs__initexprN, N_POSTs__eqNlineq, N_POSTs__eqNmapsize, // other N_POSTs__varsDirect, N_POSTs__varsInvolved, N_POSTs__NSubintvMin, N_POSTs__NSubintvSum, N_POSTs__NSubintvMax, // as N subintervals N_POSTs__SubSizeMin, N_POSTs__SubSizeSum, N_POSTs__SubSizeMax, // subintv. size N_POSTs__linCoefMin, N_POSTs__linCoefMax, N_POSTs__cliquesWithEqEncode, N_POSTs__clEEEnforced, N_POSTs__clEEFound, N_POSTs__size }; extern std::vector MIPD__stats; enum EnumReifType { RIT_None, RIT_Static, RIT_Reif, RIT_Halfreif }; enum EnumConstrType { CT_None, CT_Comparison, CT_SetIn, CT_Encode }; enum EnumCmpType { CMPT_None = 0, CMPT_LE = -4, CMPT_GE = 4, CMPT_EQ = 1, CMPT_NE = 3, CMPT_LT = -5, CMPT_GT = 5, CMPT_LE_0 = -6, CMPT_GE_0 = 6, CMPT_EQ_0 = 2, CMPT_LT_0 = -7, CMPT_GT_0 = 7 }; enum EnumVarType { VT_None, VT_Int, VT_Float }; /// struct DomainCallType describes & characterizes a possible domain constr call struct DCT { const char* sFuncName = nullptr; const std::vector& aParams; // unsigned iItem; // call's item number in the flat EnumReifType nReifType = RIT_None; // 0/static/halfreif/reif EnumConstrType nConstrType = CT_None; // EnumCmpType nCmpType = CMPT_None; EnumVarType nVarType = VT_None; FunctionI*& pfi; // double dEps = -1.0; DCT(const char* fn, const std::vector& prm, EnumReifType er, EnumConstrType ec, EnumCmpType ecmp, EnumVarType ev, FunctionI*& pfi__) : sFuncName(fn), aParams(prm), nReifType(er), nConstrType(ec), nCmpType(ecmp), nVarType(ev), pfi(pfi__) {} }; template struct Interval { N left = infMinus(), right = infPlus(); mutable VarDecl* varFlag = nullptr; /*constexpr*/ static N infMinus() { return (std::numeric_limits::has_infinity) ? -std::numeric_limits::infinity() : std::numeric_limits::lowest(); } /*constexpr*/ static N infPlus() { return (std::numeric_limits::has_infinity) ? std::numeric_limits::infinity() : std::numeric_limits::max(); } Interval(N a = infMinus(), N b = infPlus()) : left(a), right(b) {} bool operator<(const Interval& intv) const { return left < intv.left; } }; typedef Interval IntvReal; template std::ostream& operator<<(std::ostream& os, const Interval& ii) { os << "[ " << ii.left << ", " << ii.right << " ] "; return os; } template class SetOfIntervals : public std::multiset > { public: using Intv = Interval; typedef std::multiset > Base; typedef typename Base::iterator iterator; SetOfIntervals() : Base() {} SetOfIntervals(std::initializer_list > il) : Base(il) {} template SetOfIntervals(Iter i1, Iter i2) : Base(i1, i2) {} /// Number of integer values in all the intervals /// Assumes the interval bounds are ints int cardInt() const; /// Max interval length N maxInterval() const; /// Special insert function: check if interval is ok iterator insert(const Interval& iv) { if (iv.left > iv.right) { DBGOUT_MIPD("Interval " << iv.left << ".." << iv.right << " is empty, difference: " << (iv.right - iv.left) << ". Skipping"); return Base::end(); } return Base::insert(iv); } template void intersect(const SetOfIntervals& s2); /// Assumes open intervals to cut out from closed template void cutDeltas(const SetOfIntervals& s2, N1 delta); template void cutDeltas(N1 left, N1 right, N1 delta) { SetOfIntervals soi; soi.insert(Interval(left, right)); cutDeltas(soi, delta); } /// Cut out an open interval from a set of closed ones (except for infinities) void cutOut(const Interval& intv); typedef std::pair SplitResult; SplitResult split(iterator& it, N pos); bool checkFiniteBounds(); /// Check there are no useless interval splittings bool checkDisjunctStrict(); Interval getBounds() const; /// Split domain into the integer values /// May assume integer bounds void split2Bits(); }; // class SetOfIntervals typedef SetOfIntervals SetOfIntvReal; template std::ostream& operator<<(std::ostream& os, const SetOfIntervals& soi) { os << "[[ "; for (auto& ii : soi) { os << "[ " << ii.left << ", " << ii.right; if (ii.varFlag) { os << " @" << ii.varFlag; } os << " ] "; } os << "]]"; return os; } template class LinEqHelper { public: Coefs coefs; Vars vd; double rhs; }; template static std::ostream& operator<<(std::ostream& os, LinEqHelper& led) { os << "( ["; for (auto c : led.coefs) { os << c << ' '; } os << " ] * [ "; for (auto v : led.vd) { os << v->id()->str() << ' '; } os << " ] ) == " << led.rhs; return os; } typedef LinEqHelper, std::array > LinEq2Vars; typedef LinEqHelper, std::vector > LinEq; // struct LinEq2Vars { // std::array coefs; // std::array vd = { { 0, 0 } }; // double rhs; // }; // // struct LinEq { // std::vector coefs; // std::vector vd; // double rhs; // }; std::vector MIPD__stats(N_POSTs__size); template static std::vector make_vec(T t1, T t2) { T c_array[] = {t1, t2}; std::vector result(c_array, c_array + sizeof(c_array) / sizeof(c_array[0])); return result; } template static std::vector make_vec(T t1, T t2, T t3) { T c_array[] = {t1, t2, t3}; std::vector result(c_array, c_array + sizeof(c_array) / sizeof(c_array[0])); return result; } template static std::vector make_vec(T t1, T t2, T t3, T t4) { T c_array[] = {t1, t2, t3, t4}; std::vector result(c_array, c_array + sizeof(c_array) / sizeof(c_array[0])); return result; } class MIPD { public: MIPD(Env* env, bool fV, int nmi, double dmd) : nMaxIntv2Bits(nmi), dMaxNValueDensity(dmd), _env(env) { getEnv(); fVerbose = fV; } static bool fVerbose; const int nMaxIntv2Bits = 0; // Maximal interval length to enforce equality encoding const double dMaxNValueDensity = 3.0; // Maximal ratio cardInt() / size() of a domain // to enforce ee bool doMIPdomains() { MIPD__stats[N_POSTs__NSubintvMin] = 1e100; MIPD__stats[N_POSTs__SubSizeMin] = 1e100; if (!registerLinearConstraintDecls()) { return true; } if (!registerPOSTConstraintDecls()) { // not declared => no conversions return true; } registerPOSTVariables(); if (_vVarDescr.empty()) { return true; } constructVarViewCliques(); if (!decomposeDomains()) { return false; } if (fVerbose) { printStats(std::cerr); } return true; } private: Env* _env = nullptr; Env* getEnv() { MZN_MIPD__assert_hard(_env); return _env; } typedef VarDecl* PVarDecl; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_lin_eq; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_lin_le; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_lin_eq; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_lin_le; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int2float; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* lin_exp_int; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* lin_exp_float; // NOLINTNEXTLINE(readability-identifier-naming) std::vector int_lin_eq_t = make_vec(Type::parint(1), Type::varint(1), Type::parint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector float_lin_eq_t = make_vec(Type::parfloat(1), Type::varfloat(1), Type::parfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VIVF = make_vec(Type::varint(), Type::varfloat()); // double float_lt_EPS_coef__ = 1e-5; bool registerLinearConstraintDecls() { EnvI& env = getEnv()->envi(); GCLock lock; int_lin_eq = env.model->matchFn(env, constants().ids.int_.lin_eq, int_lin_eq_t, false); DBGOUT_MIPD(" int_lin_eq = " << int_lin_eq); // MZN_MIPD__assert_hard(fi); // int_lin_eq = (fi && fi->e()) ? fi : NULL; int_lin_le = env.model->matchFn(env, constants().ids.int_.lin_le, int_lin_eq_t, false); float_lin_eq = env.model->matchFn(env, constants().ids.float_.lin_eq, float_lin_eq_t, false); float_lin_le = env.model->matchFn(env, constants().ids.float_.lin_le, float_lin_eq_t, false); int2float = env.model->matchFn(env, constants().ids.int2float, t_VIVF, false); lin_exp_int = env.model->matchFn(env, constants().ids.lin_exp, int_lin_eq_t, false); lin_exp_float = env.model->matchFn(env, constants().ids.lin_exp, float_lin_eq_t, false); return (int_lin_eq != nullptr) && (int_lin_le != nullptr) && (float_lin_eq != nullptr) && (float_lin_le != nullptr); // say something... // std::cerr << " lin_exp_int=" << lin_exp_int << std::endl; // std::cerr << " lin_exp_float=" << lin_exp_float << std::endl; // For this to work, need to define a function, see mzn_only_range_domains() // { // GCLock lock; // Call* call_EPS_for_LT = // new Call(Location(),"mzn_float_lt_EPS_coef__", std::vector()); // call_EPS_for_LT->type(Type::parfloat()); // call_EPS_for_LT->decl(env.model->matchFn(getEnv()->envi(), call_EPS_for_LT)); // float_lt_EPS_coef__ = eval_float(getEnv()->envi(), call_EPS_for_LT); // } } // bool matchAndMarkFunction(); // std::set funcs; /// Possible function param sets // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VII = make_vec(Type::varint(), Type::parint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VIVI = make_vec(Type::varint(), Type::varint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VIIVI = make_vec(Type::varint(), Type::parint(), Type::varint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVI = make_vec(Type::varfloat(), Type::varint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVF = make_vec(Type::varfloat(), Type::varfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFFVI = make_vec(Type::varfloat(), Type::parfloat(), Type::varint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFFVIF = make_vec(Type::varfloat(), Type::parfloat(), Type::varint(), Type::parfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVIF = make_vec(Type::varfloat(), Type::varint(), Type::parfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVIVF = make_vec(Type::varfloat(), Type::varint(), Type::varfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVIVFF = make_vec(Type::varfloat(), Type::varint(), Type::varfloat(), Type::parfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFVFF = make_vec(Type::varfloat(), Type::varfloat(), Type::parfloat()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VFFF = make_vec(Type::varfloat(), Type::parfloat(), Type::parfloat()); // std::vector t_VFVFVIF({ Type::varfloat(), Type::varfloat(), Type::varint(), // Type::parfloat() }); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VIAVI = make_vec(Type::varint(), Type::varint(1)); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VISI = make_vec(Type::varint(), Type::parsetint()); // NOLINTNEXTLINE(readability-identifier-naming) std::vector t_VISIVI = make_vec(Type::varint(), Type::parsetint(), Type::varint()); // std::vector t_intarray(1); // t_intarray[0] = Type::parint(-1); typedef std::unordered_map M__POSTCallTypes; M__POSTCallTypes _mCallTypes; // actually declared in the input std::vector _aCT; // all possible // Fails: // DomainCallType a = { NULL, t_VII, RIT_Halfreif, CT_Comparison, CMPT_EQ, VT_Float }; /// struct VarDescr stores some info about variables involved in domain constr struct VarDescr { typedef unsigned char boolShort; VarDescr(VarDecl* vd_, boolShort fi, double l_ = 0.0, double u_ = 0.0) : lb(l_), ub(u_), vd(vd_), fInt(fi) {} double lb, ub; VarDecl* vd = nullptr; int nClique = -1; // clique number // std::vector aCalls; std::vector aCalls; boolShort fInt = 0; ConstraintI* pEqEncoding = nullptr; boolShort fDomainConstrProcessed = 0; // boolShort fPropagatedViews=0; // boolShort fPropagatedLargerEqns=0; }; std::vector _vVarDescr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_le_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_ge_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_eq_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* int_ne__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_le_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_ge_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* aux_float_lt_zero_iff_1__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_eq_reif__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* float_ne__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* aux_float_eq_zero_if_1__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* aux_int_le_zero_if_1__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* aux_float_le_zero_if_1__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* aux_float_lt_zero_if_1__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* equality_encoding__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* set_in__POST = nullptr; // NOLINTNEXTLINE(readability-identifier-naming) FunctionI* set_in_reif__POST = nullptr; bool registerPOSTConstraintDecls() { EnvI& env = getEnv()->envi(); GCLock lock; _aCT.clear(); _aCT.emplace_back("int_le_reif__POST", t_VIIVI, RIT_Reif, CT_Comparison, CMPT_LE, VT_Int, int_le_reif__POST); _aCT.emplace_back("int_ge_reif__POST", t_VIIVI, RIT_Reif, CT_Comparison, CMPT_GE, VT_Int, int_ge_reif__POST); _aCT.emplace_back("int_eq_reif__POST", t_VIIVI, RIT_Reif, CT_Comparison, CMPT_EQ, VT_Int, int_eq_reif__POST); _aCT.emplace_back("int_ne__POST", t_VII, RIT_Static, CT_Comparison, CMPT_NE, VT_Int, int_ne__POST); _aCT.emplace_back("float_le_reif__POST", t_VFFVIF, RIT_Reif, CT_Comparison, CMPT_LE, VT_Float, float_le_reif__POST); _aCT.emplace_back("float_ge_reif__POST", t_VFFVIF, RIT_Reif, CT_Comparison, CMPT_GE, VT_Float, float_ge_reif__POST); _aCT.emplace_back("aux_float_lt_zero_iff_1__POST", t_VFVIF, RIT_Reif, CT_Comparison, CMPT_LT, VT_Float, aux_float_lt_zero_iff_1__POST); _aCT.emplace_back("float_eq_reif__POST", t_VFFVIF, RIT_Reif, CT_Comparison, CMPT_EQ, VT_Float, float_eq_reif__POST); _aCT.emplace_back("float_ne__POST", t_VFFF, RIT_Static, CT_Comparison, CMPT_NE, VT_Float, float_ne__POST); _aCT.emplace_back("aux_float_eq_zero_if_1__POST", t_VFVIVF, RIT_Halfreif, CT_Comparison, CMPT_EQ_0, VT_Float, aux_float_eq_zero_if_1__POST); _aCT.emplace_back("aux_int_le_zero_if_1__POST", t_VIVI, RIT_Halfreif, CT_Comparison, CMPT_LE_0, VT_Int, aux_int_le_zero_if_1__POST); _aCT.emplace_back("aux_float_le_zero_if_1__POST", t_VFVIVF, RIT_Halfreif, CT_Comparison, CMPT_LE_0, VT_Float, aux_float_le_zero_if_1__POST); _aCT.emplace_back("aux_float_lt_zero_if_1__POST", t_VFVIVFF, RIT_Halfreif, CT_Comparison, CMPT_LT_0, VT_Float, aux_float_lt_zero_if_1__POST); _aCT.emplace_back("equality_encoding__POST", t_VIAVI, RIT_Static, CT_Encode, CMPT_None, VT_Int, equality_encoding__POST); _aCT.emplace_back("set_in__POST", t_VISI, RIT_Static, CT_SetIn, CMPT_None, VT_Int, set_in__POST); _aCT.emplace_back("set_in_reif__POST", t_VISIVI, RIT_Reif, CT_SetIn, CMPT_None, VT_Int, set_in_reif__POST); /// Registering all declared & compatible __POST constraints /// (First, cleanup FunctionIs' payload: -- ! doing now) for (int i = 0; i < _aCT.size(); ++i) { FunctionI* fi = env.model->matchFn(env, ASTString(_aCT[i].sFuncName), _aCT[i].aParams, false); if (fi != nullptr) { _mCallTypes[fi] = _aCT.data() + i; _aCT[i].pfi = fi; // fi->pPayload = (void*)this; // std::cerr << " FOund declaration: " << _aCT[i].sFuncName << std::endl; } else { _aCT[i].pfi = nullptr; DBGOUT_MIPD(" MIssing declaration: " << _aCT[i].sFuncName); return false; } } return true; } /// Registering all __POST calls' domain-constrained variables void registerPOSTVariables() { EnvI& env = getEnv()->envi(); GCLock lock; Model& mFlat = *getEnv()->flat(); // First, cleanup VarDecls' payload which stores index in _vVarDescr for (VarDeclIterator ivd = mFlat.vardecls().begin(); ivd != mFlat.vardecls().end(); ++ivd) { ivd->e()->payload(-1); } // Now add variables with non-contiguous domain for (VarDeclIterator ivd = mFlat.vardecls().begin(); ivd != mFlat.vardecls().end(); ++ivd) { VarDecl* vd0 = ivd->e(); bool fNonCtg = false; if (vd0->type().isint()) { // currently only for int vars TODO if (Expression* eDom = vd0->ti()->domain()) { IntSetVal* dom = eval_intset(env, eDom); fNonCtg = (dom->size() > 1); } } if (fNonCtg) { DBGOUT_MIPD(" Variable " << vd0->id()->str() << ": non-contiguous domain " << (*(vd0->ti()->domain()))); if (vd0->payload() == -1) { // ! yet visited vd0->payload(static_cast(_vVarDescr.size())); _vVarDescr.emplace_back(vd0, vd0->type().isint()); // can use /prmTypes/ as well if (vd0->e() != nullptr) { checkInitExpr(vd0); } } else { DBGOUT_MIPD__(" (already touched)"); } ++MIPD__stats[N_POSTs__domain]; ++MIPD__stats[N_POSTs__all]; } } // Iterate thru original __POST constraints to mark constrained vars: for (ConstraintIterator ic = mFlat.constraints().begin(); ic != mFlat.constraints().end(); ++ic) { if (ic->removed()) { continue; } if (Call* c = ic->e()->dynamicCast()) { auto ipct = _mCallTypes.find(c->decl()); if (ipct != _mCallTypes.end()) { // No ! here because might be deleted immediately in later versions. // ic->remove(); // mark removed at once MZN_MIPD__assert_hard(c->argCount() > 1); ++MIPD__stats[N_POSTs__all]; VarDecl* vd0 = expr2VarDecl(c->arg(0)); if (nullptr == vd0) { DBGOUT_MIPD__(" Call " << *c << ": 1st arg not a VarDecl, removing if eq_encoding..."); /// Only allow literals as main argument for equality_encoding if (equality_encoding__POST == ipct->first) { // was MZN_MIPD__assert_hard before MZN 2017 ic->remove(); } continue; // ignore this call } DBGOUT_MIPD__(" Call " << c->id().str() << " uses variable " << vd0->id()->str()); if (vd0->payload() == -1) { // ! yet visited vd0->payload(static_cast(_vVarDescr.size())); _vVarDescr.emplace_back(vd0, vd0->type().isint()); // can use /prmTypes/ as well // bounds/domains later for each involved var TODO if (vd0->e() != nullptr) { checkInitExpr(vd0); } } else { DBGOUT_MIPD__(" (already touched)"); } DBGOUT_MIPD(""); if (equality_encoding__POST == c->decl()) { MZN_MIPD__assert_hard(!_vVarDescr[vd0->payload()].pEqEncoding); _vVarDescr[vd0->payload()].pEqEncoding = &*ic; DBGOUT_MIPD(" Variable " << vd0->id()->str() << " has eq_encode."); } // + if has aux_ constraints? else { _vVarDescr[vd0->payload()].aCalls.push_back(&*ic); } } } } MIPD__stats[N_POSTs__varsDirect] = static_cast(_vVarDescr.size()); } // Should only be called on a newly added variable // OR when looking thru all non-touched vars /// Checks init expr of a variable /// Return true IFF new connection /// The bool param requires RHS to be POST-touched // Guido: can! be recursive in FZN bool checkInitExpr(VarDecl* vd, bool fCheckArg = false) { MZN_MIPD__assert_hard(vd->e()); if (!vd->type().isint() && !vd->type().isfloat()) { return false; } if (!fCheckArg) { MZN_MIPD__assert_hard(vd->payload() >= 0); } if (Id* id = vd->e()->dynamicCast()) { // const int f1 = ( vd->payload()>=0 ); // const int f2 = ( id->decl()->payload()>=0 ); if (!fCheckArg || (id->decl()->payload() >= 0)) { DBGOUT_MIPD__(" Checking init expr "); DBGOUT_MIPD_SELF(debugprint(vd)); LinEq2Vars led; // FAILS: // led.vd = { vd, expr2VarDecl(id->decl()->e()) }; led.vd = {{vd, expr2VarDecl(vd->e())}}; led.coefs = {{1.0, -1.0}}; led.rhs = 0.0; put2VarsConnection(led, false); ++MIPD__stats[N_POSTs__initexpr1id]; if (id->decl()->e() != nullptr) { // no initexpr for initexpr FAILS on cc-base.mzn checkInitExpr(id->decl()); } return true; // in any case } } else if (Call* c = vd->e()->dynamicCast()) { if (lin_exp_int == c->decl() || lin_exp_float == c->decl()) { // std::cerr << " !E call " << std::flush; // debugprint(c); MZN_MIPD__assert_hard(c->argCount() == 3); // ArrayLit* al = c->args()[1]->dynamicCast(); auto* al = follow_id(c->arg(1))->cast(); MZN_MIPD__assert_hard(al); MZN_MIPD__assert_hard(al->size() >= 1); if (al->size() == 1) { // 1-term scalar product in the rhs LinEq2Vars led; led.vd = {{vd, expr2VarDecl((*al)[0])}}; // const int f1 = ( vd->payload()>=0 ); // const int f2 = ( led.vd[1]->payload()>=0 ); if (!fCheckArg || (led.vd[1]->payload() >= 0)) { // Can use a!her map here: // if ( _sCallLinEq2.end() != _sCallLinEq2.find(c) ) // continue; // _sCallLinEq2.insert(c); // memorize this call DBGOUT_MIPD__(" REG 1-LINEXP "); DBGOUT_MIPD_SELF(debugprint(vd)); std::array coef0; expr2Array(c->arg(0), coef0); led.coefs = {{-1.0, coef0[0]}}; led.rhs = -expr2Const(c->arg(2)); // MINUS put2VarsConnection(led, false); ++MIPD__stats[N_POSTs__initexpr1linexp]; if (led.vd[1]->e() != nullptr) { // no initexpr for initexpr FAILS TODO checkInitExpr(led.vd[1]); } return true; // in any case } } else if (true) { // NOLINT: check larger views always. OK? TODO // if ( vd->payload()>=0 ) { // larger views // TODO should be here? // std::cerr << " LE_" << al->v().size() << ' ' << std::flush; DBGOUT_MIPD(" REG N-LINEXP "); DBGOUT_MIPD_SELF(debugprint(vd)); // Checking all but adding only touched defined vars? return findOrAddDefining(vd->id(), c); } } } return false; } /// Build var cliques (i.e. of var pairs viewing each other) void constructVarViewCliques() { // std::cerr << " Model: " << std::endl; // debugprint(getEnv()->flat()); // TAgenda agenda(_vVarDescr.size()), agendaNext; // for ( int i=0; i(_vVarDescr.size()); } void propagateViews(bool& fChanges) { // EnvI& env = getEnv()->envi(); GCLock lock; // Iterate thru original 2-variable equalities to mark views: Model& mFlat = *getEnv()->flat(); DBGOUT_MIPD(" CHECK ALL INITEXPR if they access a touched variable:"); for (VarDeclIterator ivd = mFlat.vardecls().begin(); ivd != mFlat.vardecls().end(); ++ivd) { if (ivd->removed()) { continue; } if ((ivd->e()->e() != nullptr) && ivd->e()->payload() < 0 // untouched && (ivd->e()->type().isint() || ivd->e()->type().isfloat())) { // scalars if (checkInitExpr(ivd->e(), true)) { fChanges = true; } } } DBGOUT_MIPD(" CHECK ALL CONSTRAINTS for 2-var equations:"); for (ConstraintIterator ic = mFlat.constraints().begin(); ic != mFlat.constraints().end(); ++ic) { // std::cerr << " SEE constraint: " << " "; // debugprint(&*ic); // debugprint(c->decl()); if (ic->removed()) { continue; } if (Call* c = ic->e()->dynamicCast()) { const bool fIntLinEq = int_lin_eq == c->decl(); const bool fFloatLinEq = float_lin_eq == c->decl(); if (fIntLinEq || fFloatLinEq) { // std::cerr << " !E call " << std::flush; // debugprint(c); MZN_MIPD__assert_hard(c->argCount() == 3); auto* al = follow_id(c->arg(1))->cast(); MZN_MIPD__assert_hard(al); if (al->size() == 2) { // 2-term eqn LinEq2Vars led; expr2DeclArray(c->arg(1), led.vd); // At least 1 touched var: if (nullptr != led.vd[0] && nullptr != led.vd[1]) { if (led.vd[0]->payload() >= 0 || led.vd[1]->payload() >= 0) { if (_sCallLinEq2.end() != _sCallLinEq2.find(c)) { continue; } _sCallLinEq2.insert(c); // memorize this call DBGOUT_MIPD(" REG 2-call "); DBGOUT_MIPD_SELF(debugprint(c)); led.rhs = expr2Const(c->arg(2)); expr2Array(c->arg(0), led.coefs); MZN_MIPD__assert_hard(2 == led.coefs.size()); fChanges = true; put2VarsConnection(led); ++MIPD__stats[fIntLinEq ? N_POSTs__eq2intlineq : N_POSTs__eq2floatlineq]; } } } else if (al->size() == 1) { static int nn = 0; if (++nn <= 7) { std::cerr << " MIPD: LIN_EQ with 1 variable::: " << std::flush; std::cerr << (*c) << std::endl; } } else { // larger eqns // TODO should be here? auto* eVD = get_annotation(c->ann(), constants().ann.defines_var); if (eVD != nullptr) { if (_sCallLinEqN.end() != _sCallLinEqN.find(c)) { continue; } _sCallLinEqN.insert(c); // memorize this call DBGOUT_MIPD(" REG N-call "); DBGOUT_MIPD_SELF(debugprint(c)); Call* pC = eVD->dynamicCast(); MZN_MIPD__assert_hard(pC); MZN_MIPD__assert_hard(pC->argCount()); // Checking all but adding only touched defined vars? Seems too long. VarDecl* vd = expr2VarDecl(pC->arg(0)); if ((vd != nullptr) && vd->payload() >= 0) { // only if touched if (findOrAddDefining(pC->arg(0), c)) { fChanges = true; } } } } } else // const bool fI2F = (int2float==c->decl()); // const bool fIVR = (constants().varRedef==c->decl()); // if ( fI2F || fIVR ) { if (int2float == c->decl() || constants().varRedef == c->decl()) { // std::cerr << " !E call " << std::flush; // debugprint(c); MZN_MIPD__assert_hard(c->argCount() == 2); LinEq2Vars led; // led.vd.resize(2); led.vd[0] = expr2VarDecl(c->arg(0)); led.vd[1] = expr2VarDecl(c->arg(1)); // At least 1 touched var: if (led.vd[0]->payload() >= 0 || led.vd[1]->payload() >= 0) { if (_sCallInt2Float.end() != _sCallInt2Float.find(c)) { continue; } _sCallInt2Float.insert(c); // memorize this call DBGOUT_MIPD(" REG call "); DBGOUT_MIPD_SELF(debugprint(c)); led.rhs = 0.0; led.coefs = {{1.0, -1.0}}; fChanges = true; put2VarsConnection(led); ++MIPD__stats[int2float == c->decl() ? N_POSTs__int2float : N_POSTs__internalvarredef]; } } } } } /// This vector stores the linear part of a general view /// x = + rhs typedef std::vector > TLinExpLin; /// This struct has data describing the rest of a general view struct NViewData { VarDecl* pVarDefined = nullptr; double coef0 = 1.0; double rhs; }; typedef std::map NViewMap; NViewMap _mNViews; /// compare to an existing defining linexp, || just add it to the map /// adds only touched defined vars /// return true iff new linear connection // linexp: z = a^T x+b // _lin_eq: a^T x == b bool findOrAddDefining(Expression* exp, Call* pC) { Id* pId = exp->dynamicCast(); MZN_MIPD__assert_hard(pId); VarDecl* vd = pId->decl(); MZN_MIPD__assert_hard(vd); MZN_MIPD__assert_hard(pC->argCount() == 3); TLinExpLin rhsLin; NViewData nVRest; nVRest.pVarDefined = vd; nVRest.rhs = expr2Const(pC->arg(2)); std::vector vars; expr2DeclArray(pC->arg(1), vars); std::vector coefs; expr2Array(pC->arg(0), coefs); MZN_MIPD__assert_hard(vars.size() == coefs.size()); int nVD = 0; for (int i = 0; i < vars.size(); ++i) { // MZN_MIPD__assert_hard( 0.0!=std::fabs if (vd == vars[i]) { // when int/float_lin_eq :: defines_var(vd) "Recursive definition of " << *vd nVRest.coef0 = -coefs[i]; nVRest.rhs = -nVRest.rhs; ++nVD; } else { rhsLin.push_back(std::make_pair(vars[i], coefs[i])); } } MZN_MIPD__assert_hard(1 >= nVD); std::sort(rhsLin.begin(), rhsLin.end()); // Divide the equation by the 1st coef const double coef1 = rhsLin.begin()->second; MZN_MIPD__assert_hard(0.0 != std::fabs(coef1)); nVRest.coef0 /= coef1; nVRest.rhs /= coef1; for (auto& rhsL : rhsLin) { rhsL.second /= coef1; } auto it = _mNViews.find(rhsLin); if (_mNViews.end() != it && nVRest.pVarDefined != it->second.pVarDefined) { // don't connect to itself LinEq2Vars leq; leq.vd = {{nVRest.pVarDefined, it->second.pVarDefined}}; leq.coefs = {{nVRest.coef0, -it->second.coef0}}; // +, - leq.rhs = nVRest.rhs - it->second.rhs; put2VarsConnection(leq, false); ++MIPD__stats[nVD != 0 ? N_POSTs__eqNlineq : N_POSTs__initexprN]; return true; } if (vd->payload() >= 0) { // only touched _mNViews[rhsLin] = nVRest; return true; // can lead to a new connection } return false; } static void propagateImplViews(bool& fChanges) { // EnvI& env = getEnv()->envi(); GCLock lock; // TODO } /// Could be better to mark the calls instead: std::unordered_set _sCallLinEq2, _sCallInt2Float, _sCallLinEqN; class TClique : public std::vector { // need more info? public: /// This function takes the 1st variable && relates all to it /// Return false if contrad / disconnected graph // bool findRelations0() { // return true; // } }; typedef std::vector TCLiqueList; TCLiqueList _aCliques; /// register a 2-variable lin eq /// add it to the var clique, joining the participants' cliques if needed void put2VarsConnection(LinEq2Vars& led, bool fCheckinitExpr = true) { MZN_MIPD__assert_hard(led.coefs.size() == led.vd.size()); MZN_MIPD__assert_hard(led.vd.size() == 2); DBGOUT_MIPD__(" Register 2-var connection: " << led); /// Check it's not same 2 vars if (led.vd[0] == led.vd[1]) { MZN_MIPD__assert_soft( 0, "MIPD: STRANGE: registering var connection to itself: " << led << ", skipping"); MZN_MIPD__ASSERT_FOR_SAT(fabs(led.coefs[0] + led.coefs[1]) < 1e-6, // TODO param getEnv()->envi(), led.vd[0]->loc(), "Var connection to itself seems to indicate UNSAT: " << led); return; } // register if new variables // std::vector fHaveClq(led.vd.size(), false); int nCliqueAvailable = -1; for (auto* vd : led.vd) { if (vd->payload() < 0) { // ! yet visited vd->payload(static_cast(_vVarDescr.size())); _vVarDescr.emplace_back(vd, vd->type().isint()); // can use /prmTypes/ as well if (fCheckinitExpr && (vd->e() != nullptr)) { checkInitExpr(vd); } } else { int nMaybeClq = _vVarDescr[vd->payload()].nClique; if (nMaybeClq >= 0) { nCliqueAvailable = nMaybeClq; } // MZN_MIPD__assert_hard( nCliqueAvailable>=0 ); // fHaveClq[i] = true; } } if (nCliqueAvailable < 0) { // no clique found nCliqueAvailable = static_cast(_aCliques.size()); _aCliques.resize(_aCliques.size() + 1); } DBGOUT_MIPD(" ...adding to clique " << nCliqueAvailable << " of size " << _aCliques[nCliqueAvailable].size()); TClique& clqNew = _aCliques[nCliqueAvailable]; clqNew.push_back(led); for (auto* vd : led.vd) { // merging cliques int& nMaybeClq = _vVarDescr[vd->payload()].nClique; if (nMaybeClq >= 0 && nMaybeClq != nCliqueAvailable) { TClique& clqOld = _aCliques[nMaybeClq]; MZN_MIPD__assert_hard(clqOld.size()); for (auto& eq2 : clqOld) { for (auto* vd : eq2.vd) { // point all the variables to the new clique _vVarDescr[vd->payload()].nClique = nCliqueAvailable; } } clqNew.insert(clqNew.end(), clqOld.begin(), clqOld.end()); clqOld.clear(); // Can use C++11 move TODO DBGOUT_MIPD(" +++ Joining cliques"); } nMaybeClq = nCliqueAvailable; // Could mark as 'unused' TODO } } /// Finds a clique variable to which all domain constr are related class TCliqueSorter { MIPD& _mipd; const int _iVarStart; // this is the first var to which all others are related public: // VarDecl* varRef0=0; // this is the first var to which all others are related VarDecl* varRef1 = nullptr; // this is the 2nd main reference. // it is a var with eq_encode, || // an (integer if any) variable with the least rel. factor bool fRef1HasEqEncode = false; /// This map stores the relations y = ax+b of all the clique's vars to y typedef std::unordered_map > TMapVars; TMapVars mRef0, mRef1; // to the main var 0, 1 class TMatrixVars : public std::unordered_map { public: /// Check existing connection template bool checkExistingArc(IVarDecl begV, double A, double B, bool fReportRepeat = true) { auto it1 = this->find(*begV); if (this->end() != it1) { auto it2 = it1->second.find(*(begV + 1)); if (it1->second.end() != it2) { MZN_MIPD__assert_hard(std::fabs(it2->second.first - A) < 1e-6 * std::max(std::fabs(it2->second.first), std::fabs(A))); MZN_MIPD__assert_hard(std::fabs(it2->second.second - B) < 1e-6 * std::max(std::fabs(it2->second.second), std::fabs(B)) + 1e-6); MZN_MIPD__assert_hard(std::fabs(A) != 0.0); MZN_MIPD__assert_soft(!fVerbose || std::fabs(A) > 1e-12, " Very small coef: " << (*begV)->id()->str() << " = " << A << " * " << (*(begV + 1))->id()->str() << " + " << B); if (fReportRepeat) { MZN_MIPD__assert_soft(!fVerbose, "LinEqGraph: eqn between " << (*begV)->id()->str() << " && " << (*(begV + 1))->id()->str() << " is repeated. "); } return true; } } return false; } }; class LinEqGraph : public TMatrixVars { public: static double dCoefMin, dCoefMax; /// Stores the arc (x1, x2) as x1 = a*x2 + b /// so that a constraint on x2, say x2<=c <-> f, /// is equivalent to one for x1: x1 <=/>= a*c+b <-> f //// ( the other way involves division: //// so that a constraint on x1, say x1<=c <-> f, //// can easily be converted into one for x2 as a*x2 <= c-b <-> f //// <=> x2 (care for sign) (c-b)/a <-> f ) template void addArc(ICoef begC, IVarDecl begV, double rhs) { MZN_MIPD__assert_soft(!fVerbose || std::fabs(*begC) >= 1e-10, " Vars " << (*begV)->id()->str() << " to " << (*(begV + 1))->id()->str() << ": coef=" << (*begC)); // Transform Ax+By=C into x = -B/Ay+C/A const double negBA = -(*(begC + 1)) / (*begC); const double CA = rhs / (*begC); checkExistingArc(begV, negBA, CA); (*this)[*begV][*(begV + 1)] = std::make_pair(negBA, CA); const double dCoefAbs = std::fabs(negBA); if (dCoefAbs < dCoefMin) { dCoefMin = dCoefAbs; } if (dCoefAbs > dCoefMax) { dCoefMax = dCoefAbs; } } void addEdge(const LinEq2Vars& led) { addArc(led.coefs.begin(), led.vd.begin(), led.rhs); addArc(led.coefs.rbegin(), led.vd.rbegin(), led.rhs); } /// Propagate linear relations from the given variable void propagate(iterator itStart, TMapVars& mWhereStore) { MZN_MIPD__assert_hard(this->end() != itStart); TMatrixVars mTemp; mTemp[itStart->first] = itStart->second; // init with existing DBGOUT_MIPD("Propagation started from " << itStart->first->id()->str() << " having " << itStart->second.size() << " connections"); propagate2(itStart, itStart, std::make_pair(1.0, 0.0), mTemp); mWhereStore = mTemp.begin()->second; MZN_MIPD__assert_hard_msg( mWhereStore.size() == this->size() - 1, "Variable " << (*(mTemp.begin()->first)) << " should be connected to all others in the clique, but " << "|edges|==" << mWhereStore.size() << ", |all nodes|==" << this->size()); } /// Propagate linear relations from it1 via it2 void propagate2(iterator itSrc, iterator itVia, std::pair rel, TMatrixVars& mWhereStore) { for (auto itDst = itVia->second.begin(); itDst != itVia->second.end(); ++itDst) { // Transform x1=A1x2+B1, x2=A2x3+B2 into x1=A1A2x3+A1B2+B1 if (itDst->first == itSrc->first) { continue; } const double A1A2 = rel.first * itDst->second.first; const double A1B2plusB1 = rel.first * itDst->second.second + rel.second; bool fDive = true; if (itSrc != itVia) { PVarDecl vd[2] = {itSrc->first, itDst->first}; if (!mWhereStore.checkExistingArc(vd, A1A2, A1B2plusB1, false)) { mWhereStore[vd[0]][vd[1]] = std::make_pair(A1A2, A1B2plusB1); DBGOUT_MIPD(" PROPAGATING: " << vd[0]->id()->str() << " = " << A1A2 << " * " << vd[1]->id()->str() << " + " << A1B2plusB1); } else { fDive = false; } } if (fDive) { auto itDST = this->find(itDst->first); MZN_MIPD__assert_hard(this->end() != itDST); propagate2(itSrc, itDST, std::make_pair(A1A2, A1B2plusB1), mWhereStore); } } } }; LinEqGraph leg; TCliqueSorter(MIPD* pm, int iv) : _mipd(*pm), _iVarStart(iv) {} void doRelate() { MZN_MIPD__assert_hard(_mipd._vVarDescr[_iVarStart].nClique >= 0); const TClique& clq = _mipd._aCliques[_mipd._vVarDescr[_iVarStart].nClique]; for (const auto& eq2 : clq) { leg.addEdge(eq2); } DBGOUT_MIPD(" Clique " << _mipd._vVarDescr[_iVarStart].nClique << ": " << leg.size() << " variables, " << clq.size() << " connections."); for (auto& it1 : leg) { _mipd._vVarDescr[it1.first->payload()].fDomainConstrProcessed = 1U; } // Propagate the 1st var's relations: leg.propagate(leg.begin(), mRef0); // Find a best main variable according to: // 1. isInt 2. hasEqEncode 3. abs linFactor to ref0 varRef1 = leg.begin()->first; std::array aCrit = { {(double)_mipd._vVarDescr[varRef1->payload()].fInt, static_cast(_mipd._vVarDescr[varRef1->payload()].pEqEncoding != nullptr), 1.0}}; for (auto& it2 : mRef0) { VarDescr& vard = _mipd._vVarDescr[it2.first->payload()]; std::array aCrit1 = {{(double)vard.fInt, static_cast(vard.pEqEncoding != nullptr), std::fabs(it2.second.first)}}; if (aCrit1 > aCrit) { varRef1 = it2.first; aCrit = aCrit1; } } leg.propagate(leg.find(varRef1), mRef1); } }; // class TCliqueSorter /// Build a domain decomposition for a clique /// a clique can consist of just 1 var without a clique object class DomainDecomp { public: MIPD& mipd; const int iVarStart; TCliqueSorter cls; SetOfIntvReal sDomain; DomainDecomp(MIPD* pm, int iv) : mipd(*pm), iVarStart(iv), cls(pm, iv) { sDomain.insert(IntvReal()); // the decomposed domain. Init to +-inf } void doProcess() { // Choose the main variable && relate all others to it const int nClique = mipd._vVarDescr[iVarStart].nClique; if (nClique >= 0) { cls.doRelate(); } else { cls.varRef1 = mipd._vVarDescr[iVarStart].vd; } // Adding itself: cls.mRef1[cls.varRef1] = std::make_pair(1.0, 0.0); int iVarRef1 = cls.varRef1->payload(); MZN_MIPD__assert_hard(nClique == mipd._vVarDescr[iVarRef1].nClique); cls.fRef1HasEqEncode = (mipd._vVarDescr[iVarRef1].pEqEncoding != nullptr); // First, construct the domain decomposition in any case // projectVariableConstr( cls.varRef1, std::make_pair(1.0, 0.0) ); // if ( nClique >= 0 ) { for (auto& iRef1 : cls.mRef1) { projectVariableConstr(iRef1.first, iRef1.second); } DBGOUT_MIPD("Clique " << nClique << ": main ref var " << cls.varRef1->id()->str() << ", domain dec: " << sDomain); MZN_MIPD__ASSERT_FOR_SAT(!sDomain.empty(), mipd.getEnv()->envi(), cls.varRef1->loc(), "clique " << nClique << ": main ref var " << *cls.varRef1->id() << ", domain decomposition seems empty: " << sDomain); MZN_MIPD__FLATTENING_ERROR__IF_NOT(sDomain.checkFiniteBounds(), mipd.getEnv()->envi(), cls.varRef1->loc(), "variable " << *cls.varRef1->id() << " needs finite bounds for linearisation." " Or, use indicator constraints. " << "Current domain is " << sDomain); MZN_MIPD__assert_hard(sDomain.checkDisjunctStrict()); makeRangeDomains(); // Then, use equality_encoding if available if (cls.fRef1HasEqEncode) { syncWithEqEncoding(); syncOtherEqEncodings(); } else { // ! cls.fRef1HasEqEncode if (sDomain.size() >= 2) { // need to simplify stuff otherwise considerDenseEncoding(); createDomainFlags(); } } implementPOSTs(); // Statistics if (sDomain.size() < MIPD__stats[N_POSTs__NSubintvMin]) { MIPD__stats[N_POSTs__NSubintvMin] = static_cast(sDomain.size()); } MIPD__stats[N_POSTs__NSubintvSum] += sDomain.size(); if (sDomain.size() > MIPD__stats[N_POSTs__NSubintvMax]) { MIPD__stats[N_POSTs__NSubintvMax] = static_cast(sDomain.size()); } for (const auto& intv : sDomain) { const auto nSubSize = intv.right - intv.left; if (nSubSize < MIPD__stats[N_POSTs__SubSizeMin]) { MIPD__stats[N_POSTs__SubSizeMin] = nSubSize; } MIPD__stats[N_POSTs__SubSizeSum] += nSubSize; if (nSubSize > MIPD__stats[N_POSTs__SubSizeMax]) { MIPD__stats[N_POSTs__SubSizeMax] = nSubSize; } } if (cls.fRef1HasEqEncode) { ++MIPD__stats[N_POSTs__cliquesWithEqEncode]; } } /// Project the domain-related constraints of a variable into the clique /// Deltas should be scaled but to a minimum of the target's discr /// COmparison sense changes on negated vars void projectVariableConstr(VarDecl* vd, std::pair eq1) { DBGOUT_MIPD__(" MIPD: projecting variable "); DBGOUT_MIPD_SELF(debugprint(vd)); // Always check if domain becomes empty? TODO const double A = eq1.first; // vd = A*arg + B. conversion const double B = eq1.second; // process domain info double lb = B; double ub = A + B; // projected bounds for bool if (vd->ti()->domain() != nullptr) { if (vd->type().isint() || vd->type().isfloat()) { // INT VAR OR FLOAT VAR SetOfIntvReal sD1; convertIntSet(vd->ti()->domain(), sD1, cls.varRef1, A, B); sDomain.intersect(sD1); DBGOUT_MIPD(" Clique domain after proj of the init. domain " << sD1 << " of " << (vd->type().isint() ? "varint" : "varfloat") << A << " * " << vd->id()->str() << " + " << B << ": " << sDomain); auto bnds = sD1.getBounds(); lb = bnds.left; ub = bnds.right; } else { MZN_MIPD__FLATTENING_ERROR__IF_NOT(0, mipd.getEnv()->envi(), cls.varRef1->loc(), "Variable " << vd->id()->str() << " of type " << vd->type().toString(mipd._env->envi()) << " has a domain."); } // /// Deleting var domain: // vd->ti()->domain( NULL ); } else { if (nullptr == vd->ti()->domain() && !vd->type().isbool()) { lb = IntvReal::infMinus(); ub = IntvReal::infPlus(); } } auto bnds = sDomain.getBounds(); // can change TODO // process calls. Can use the constr type info. auto& aCalls = mipd._vVarDescr[vd->payload()].aCalls; for (Item* pItem : aCalls) { auto* pCI = pItem->dynamicCast(); MZN_MIPD__assert_hard(pCI != nullptr); Call* pCall = pCI->e()->dynamicCast(); MZN_MIPD__assert_hard(pCall != nullptr); DBGOUT_MIPD__("PROPAG CALL "); DBGOUT_MIPD_SELF(debugprint(pCall)); // check the bounds for bool in reifs? TODO auto ipct = mipd._mCallTypes.find(pCall->decl()); MZN_MIPD__assert_hard(mipd._mCallTypes.end() != ipct); const DCT& dct = *ipct->second; int nCmpType_ADAPTED = dct.nCmpType; if (A < 0.0) { // negative factor if (std::abs(nCmpType_ADAPTED) >= 4) { // inequality nCmpType_ADAPTED = -nCmpType_ADAPTED; } } switch (dct.nConstrType) { case CT_SetIn: { SetOfIntvReal SS; convertIntSet(pCall->arg(1), SS, cls.varRef1, A, B); if (RIT_Static == dct.nReifType) { sDomain.intersect(SS); ++MIPD__stats[N_POSTs__setIn]; } else { sDomain.cutDeltas(SS, std::max(1.0, std::fabs(A))); // deltas to scale ++MIPD__stats[N_POSTs__setInReif]; } } break; case CT_Comparison: if (RIT_Reif == dct.nReifType) { const double rhs = (mipd.aux_float_lt_zero_iff_1__POST == pCall->decl()) ? B /* + A*0.0, relating to 0 */ // The 2nd argument is constant: : A * MIPD::expr2Const(pCall->arg(1)) + B; const double rhsUp = rndUpIfInt(cls.varRef1, rhs); const double rhsDown = rndDownIfInt(cls.varRef1, rhs); const double rhsRnd = rndIfInt(cls.varRef1, rhs); /// Strictly, for delta we should finish domain reductions first... TODO? const double delta = computeDelta(cls.varRef1, vd, bnds, A, pCall, 3); switch (nCmpType_ADAPTED) { case CMPT_LE: sDomain.cutDeltas(IntvReal::infMinus(), rhsDown, delta); break; case CMPT_GE: sDomain.cutDeltas(rhsUp, IntvReal::infPlus(), delta); break; case CMPT_LT_0: sDomain.cutDeltas(IntvReal::infMinus(), rhsDown - delta, delta); break; case CMPT_GT_0: sDomain.cutDeltas(rhsUp + delta, IntvReal::infPlus(), delta); break; case CMPT_EQ: if (!(cls.varRef1->type().isint() && // skip if int target var std::fabs(rhs - rhsRnd) > INT_EPS)) { // && fract value sDomain.cutDeltas(rhsRnd, rhsRnd, delta); } break; default: MZN_MIPD__assert_hard_msg(0, " No other reified cmp type "); } ++MIPD__stats[(vd->ti()->type().isint()) ? N_POSTs__intCmpReif : N_POSTs__floatCmpReif]; } else if (RIT_Static == dct.nReifType) { // _ne, later maybe static ineq TODO MZN_MIPD__assert_hard(CMPT_NE == dct.nCmpType); const double rhs = A * MIPD::expr2Const(pCall->arg(1)) + B; const double rhsRnd = rndIfInt(cls.varRef1, rhs); bool fSkipNE = (cls.varRef1->type().isint() && std::fabs(rhs - rhsRnd) > INT_EPS); if (!fSkipNE) { const double delta = computeDelta(cls.varRef1, vd, bnds, A, pCall, 2); sDomain.cutOut({rhsRnd - delta, rhsRnd + delta}); } ++MIPD__stats[(vd->ti()->type().isint()) ? N_POSTs__intNE : N_POSTs__floatNE]; } else { // aux_ relate to 0.0 // But we don't modify domain splitting for them currently ++MIPD__stats[(vd->ti()->type().isint()) ? N_POSTs__intAux : N_POSTs__floatAux]; MZN_MIPD__assert_hard(RIT_Halfreif == dct.nReifType); // const double rhs = B; // + A*0 // const double delta = vd->type().isint() ? 1.0 : 1e-5; // // TODO : eps } break; case CT_Encode: // See if any further constraints here? TODO ++MIPD__stats[N_POSTs__eq_encode]; break; default: MZN_MIPD__assert_hard_msg(0, "Unknown constraint type"); } } DBGOUT_MIPD(" Clique domain after proj of " << A << " * " << vd->id()->str() << " + " << B << ": " << sDomain); } static double rndIfInt(VarDecl* vdTarget, double v) { return vdTarget->type().isint() ? std::round(v) : v; } static double rndIfBothInt(VarDecl* vdTarget, double v) { if (!vdTarget->type().isint()) { return v; } const double vRnd = std::round(v); return (fabs(v - vRnd) < INT_EPS) ? vRnd : v; } static double rndUpIfInt(VarDecl* vdTarget, double v) { return vdTarget->type().isint() ? std::ceil(v - INT_EPS) : v; } static double rndDownIfInt(VarDecl* vdTarget, double v) { return vdTarget->type().isint() ? std::floor(v + INT_EPS) : v; } void makeRangeDomains() { auto bnds = sDomain.getBounds(); for (auto& iRef1 : cls.mRef1) { VarDecl* vd = iRef1.first; // projecting the bounds back: double lb0 = (bnds.left - iRef1.second.second) / iRef1.second.first; double ub0 = (bnds.right - iRef1.second.second) / iRef1.second.first; if (lb0 > ub0) { MZN_MIPD__assert_hard(iRef1.second.first < 0.0); std::swap(lb0, ub0); } if (vd->type().isint()) { lb0 = rndUpIfInt(vd, lb0); ub0 = rndDownIfInt(vd, ub0); } setVarDomain(vd, lb0, ub0); } } /// tightens element bounds in the existing eq_encoding of varRef1 /// necessary because if one exists, int_ne is not translated into it /// Can also back-check from there? TODO /// And further checks TODO void syncWithEqEncoding() { std::vector pp; auto bnds = sDomain.getBounds(); const long long iMin = mipd.expr2ExprArray( mipd._vVarDescr[cls.varRef1->payload()].pEqEncoding->e()->dynamicCast()->arg(1), pp); MZN_MIPD__assert_hard(pp.size() >= bnds.right - bnds.left + 1); MZN_MIPD__assert_hard(iMin <= bnds.left); long long vEE = iMin; DBGOUT_MIPD__( " SYNC EQ_ENCODE( " << (*cls.varRef1) << ", bitflags: " << *(mipd._vVarDescr[cls.varRef1->payload()].pEqEncoding->e()->dynamicCast()->arg( 1)) << " ): SETTING 0 FLAGS FOR VALUES: "); for (const auto& intv : sDomain) { for (; static_cast(vEE) < intv.left; ++vEE) { if (vEE >= static_cast(iMin + pp.size())) { return; } if (pp[vEE - iMin]->isa()) { if (pp[vEE - iMin]->dynamicCast()->decl()->type().isvar()) { DBGOUT_MIPD__(vEE << ", "); setVarDomain(pp[vEE - iMin]->dynamicCast()->decl(), 0.0, 0.0); } } } vEE = static_cast(intv.right + 1); } for (; vEE < static_cast(iMin + pp.size()); ++vEE) { if (pp[vEE - iMin]->isa()) { if (pp[vEE - iMin]->dynamicCast()->decl()->type().isvar()) { DBGOUT_MIPD__(vEE << ", "); setVarDomain(pp[vEE - iMin]->dynamicCast()->decl(), 0.0, 0.0); } } } DBGOUT_MIPD(""); } /// sync varRef1's eq_encoding with those of other variables void syncOtherEqEncodings() { // TODO This could be in the var projection? No, need the final domain } /// Depending on params, /// create an equality encoding for an integer variable /// TODO What if a float's domain is discrete? void considerDenseEncoding() { if (cls.varRef1->id()->type().isint()) { if (sDomain.maxInterval() <= mipd.nMaxIntv2Bits || sDomain.cardInt() <= mipd.dMaxNValueDensity * sDomain.size()) { sDomain.split2Bits(); ++MIPD__stats[N_POSTs__clEEEnforced]; } } } /// if ! eq_encoding, creates a flag for each subinterval in the domain /// && constrains sum(flags)==1 void createDomainFlags() { std::vector vVars(sDomain.size()); // flags for each subinterval std::vector vIntvLB(sDomain.size() + 1); std::vector vIntvUB__(sDomain.size() + 1); int i = 0; double dMaxIntv = -1.0; for (const auto& intv : sDomain) { intv.varFlag = addIntVar(0.0, 1.0); vVars[i] = intv.varFlag->id(); vIntvLB[i] = intv.left; vIntvUB__[i] = -intv.right; dMaxIntv = std::max(dMaxIntv, intv.right - intv.left); ++i; } // Sum of flags == 1 std::vector ones(sDomain.size(), 1.0); addLinConstr(ones, vVars, CMPT_EQ, 1.0); // Domain decomp vVars.push_back(cls.varRef1->id()); vIntvLB[i] = -1.0; // var1 >= sum(LBi*flagi) /// STRICT equality encoding if small intervals if (dMaxIntv > 1e-6) { // EPS = param? TODO vIntvUB__[i] = 1.0; // var1 <= sum(UBi*flagi) addLinConstr(vIntvLB, vVars, CMPT_LE, 0.0); addLinConstr(vIntvUB__, vVars, CMPT_LE, 0.0); } else { ++MIPD__stats[N_POSTs__clEEFound]; addLinConstr(vIntvLB, vVars, CMPT_EQ, 0.0); } } /// deletes them as well void implementPOSTs() { auto bnds = sDomain.getBounds(); for (auto& iRef1 : cls.mRef1) { // DBGOUT_MIPD__( " MIPD: implementing constraints of variable " ); // DBGOUT_MIPD_SELF( debugprint(vd) ); VarDecl* vd = iRef1.first; auto eq1 = iRef1.second; const double A = eq1.first; // vd = A*arg + B. conversion const double B = eq1.second; // process calls. Can use the constr type info. auto& aCalls = mipd._vVarDescr[vd->payload()].aCalls; for (Item* pItem : aCalls) { auto* pCI = pItem->dynamicCast(); MZN_MIPD__assert_hard(pCI); Call* pCall = pCI->e()->dynamicCast(); MZN_MIPD__assert_hard(pCall); DBGOUT_MIPD__("IMPL CALL "); DBGOUT_MIPD_SELF(debugprint(pCall)); // check the bounds for bool in reifs? TODO auto ipct = mipd._mCallTypes.find(pCall->decl()); MZN_MIPD__assert_hard(mipd._mCallTypes.end() != ipct); const DCT& dct = *ipct->second; int nCmpType_ADAPTED = dct.nCmpType; if (A < 0.0) { // negative factor if (std::abs(nCmpType_ADAPTED) >= 4) { // inequality nCmpType_ADAPTED = -nCmpType_ADAPTED; } } switch (dct.nConstrType) { case CT_SetIn: if (RIT_Reif == dct.nReifType) { SetOfIntvReal SS; convertIntSet(pCall->arg(1), SS, cls.varRef1, A, B); relateReifFlag(pCall->arg(2), SS); } break; case CT_Comparison: if (RIT_Reif == dct.nReifType) { const double rhs = (mipd.aux_float_lt_zero_iff_1__POST == pCall->decl()) ? B /* + A*0.0, relating to 0 */ // The 2nd argument is constant: : A * MIPD::expr2Const(pCall->arg(1)) + B; const double rhsUp = rndUpIfInt(cls.varRef1, rhs); const double rhsDown = rndDownIfInt(cls.varRef1, rhs); const double rhsRnd = rndIfBothInt( cls.varRef1, rhs); // if the ref var is int, need to round almost-int values const double delta = computeDelta(cls.varRef1, vd, bnds, A, pCall, 3); switch (nCmpType_ADAPTED) { case CMPT_LE: relateReifFlag(pCall->arg(2), {{IntvReal::infMinus(), rhsDown}}); break; case CMPT_GE: relateReifFlag(pCall->arg(2), {{rhsUp, IntvReal::infPlus()}}); break; case CMPT_LT_0: relateReifFlag(pCall->arg(1), {{IntvReal::infMinus(), rhsDown - delta}}); break; case CMPT_GT_0: relateReifFlag(pCall->arg(1), {{rhsUp + delta, IntvReal::infPlus()}}); break; case CMPT_EQ: relateReifFlag(pCall->arg(2), {{rhsRnd, rhsRnd}}); break; // ... but if the value is sign. fractional for an int var, the flag is // set=0 default: break; } } else if (RIT_Static == dct.nReifType) { // !hing here for NE MZN_MIPD__assert_hard(CMPT_NE == nCmpType_ADAPTED); } else { // aux_ relate to 0.0 // But we don't modify domain splitting for them currently MZN_MIPD__assert_hard(RIT_Halfreif == dct.nReifType); double rhs = B; // + A*0 const double rhsUp = rndUpIfInt(cls.varRef1, rhs); const double rhsDown = rndDownIfInt(cls.varRef1, rhs); const double rhsRnd = rndIfInt(cls.varRef1, rhs); double delta = 0.0; if (mipd.aux_float_lt_zero_if_1__POST == pCall->decl()) { // only float && lt delta = computeDelta(cls.varRef1, vd, bnds, A, pCall, 3); } if (nCmpType_ADAPTED < 0) { delta = -delta; } if (cls.varRef1->type().isint() && CMPT_EQ_0 != nCmpType_ADAPTED) { if (nCmpType_ADAPTED < 0) { rhs = rhsDown; } else { rhs = rhsUp; } } else { rhs += delta; } // Now we need rhs ! to be in the inner of the domain bool fUseDD = true; if (!cls.fRef1HasEqEncode) { switch (nCmpType_ADAPTED) { case CMPT_EQ_0: { auto itLB = sDomain.lower_bound(rhsRnd); fUseDD = (itLB->left == rhsRnd && itLB->right == rhsRnd); // exactly } break; case CMPT_LT_0: case CMPT_LE_0: { auto itUB = sDomain.upper_bound(rhsUp); bool fInner = false; if (sDomain.begin() != itUB) { --itUB; if (itUB->right > rhs) { fInner = true; } } fUseDD = !fInner; } break; case CMPT_GT_0: case CMPT_GE_0: { auto itLB = sDomain.lower_bound(rhsDown); bool fInner = false; if (sDomain.begin() != itLB) { --itLB; if (itLB->right >= rhs) { fInner = true; } } fUseDD = !fInner; } break; default: MZN_MIPD__assert_hard_msg(0, "Unknown halfreif cmp type"); } } if (fUseDD) { // use sDomain if (CMPT_EQ_0 == nCmpType_ADAPTED) { relateReifFlag(pCall->arg(1), {{rhsRnd, rhsRnd}}, RIT_Halfreif); } else if (nCmpType_ADAPTED < 0) { relateReifFlag(pCall->arg(1), {{IntvReal::infMinus(), rhsDown}}, RIT_Halfreif); } else { relateReifFlag(pCall->arg(1), {{rhsUp, IntvReal::infPlus()}}, RIT_Halfreif); } } else { // use big-M DBGOUT_MIPD(" AUX BY BIG-Ms: "); const bool fLE = (CMPT_EQ_0 == nCmpType_ADAPTED || 0 > nCmpType_ADAPTED); const bool fGE = (CMPT_EQ_0 == nCmpType_ADAPTED || 0 < nCmpType_ADAPTED); // Take integer || float indicator version, depending on the constrained var: const int nIdxInd = // (VT_Int==dct.nVarType) ? // No: vd->ti()->type().isint() ? 1 : 2; cls.varRef1->ti()->type().isint() ? 1 : 2; // need the type of the variable to be constr MZN_MIPD__assert_hard(static_cast(nIdxInd) < pCall->argCount()); Expression* pInd = pCall->arg(nIdxInd); if (fLE && rhs < bnds.right) { if (rhs >= bnds.left) { std::vector coefs = {1.0, bnds.right - rhs}; // Use the float version of indicator: std::vector vars = {cls.varRef1->id(), pInd}; addLinConstr(coefs, vars, CMPT_LE, bnds.right); } else { setVarDomain(MIPD::expr2VarDecl(pInd), 0.0, 0.0); } } if (fGE && rhs > bnds.left) { if (rhs <= bnds.right) { std::vector coefs = {-1.0, rhs - bnds.left}; std::vector vars = {cls.varRef1->id(), pInd}; addLinConstr(coefs, vars, CMPT_LE, -bnds.left); } else { setVarDomain(MIPD::expr2VarDecl(pInd), 0.0, 0.0); } } } } break; case CT_Encode: // See if any further constraints here? TODO break; default: MZN_MIPD__assert_hard_msg(0, "Unknown constraint type"); } pItem->remove(); // removing the call } // removing the eq_encoding call if (mipd._vVarDescr[vd->payload()].pEqEncoding != nullptr) { mipd._vVarDescr[vd->payload()].pEqEncoding->remove(); } } } /// sets varFlag = || <= sum( intv.varFlag : SS ) void relateReifFlag(Expression* expFlag, const SetOfIntvReal& SS, EnumReifType nRT = RIT_Reif) { MZN_MIPD__assert_hard(RIT_Reif == nRT || RIT_Halfreif == nRT); // MZN_MIPD__assert_hard( sDomain.size()>=2 ); VarDecl* varFlag = MIPD::expr2VarDecl(expFlag); std::vector vIntvFlags; if (cls.fRef1HasEqEncode) { // use eq_encoding MZN_MIPD__assert_hard(varFlag->type().isint()); std::vector pp; auto bnds = sDomain.getBounds(); const long long iMin = mipd.expr2ExprArray( mipd._vVarDescr[cls.varRef1->payload()].pEqEncoding->e()->dynamicCast()->arg(1), pp); MZN_MIPD__assert_hard(pp.size() >= bnds.right - bnds.left + 1); MZN_MIPD__assert_hard(iMin <= bnds.left); for (const auto& intv : SS) { for (long long vv = (long long)std::max(double(iMin), ceil(intv.left)); vv <= (long long)std::min(double(iMin) + pp.size() - 1, floor(intv.right)); ++vv) { vIntvFlags.push_back(pp[vv - iMin]); } } } else { MZN_MIPD__assert_hard(varFlag->type().isint()); for (const auto& intv : SS) { auto it1 = sDomain.lower_bound(intv.left); auto it2 = sDomain.upper_bound(intv.right); auto it11 = it1; // Check that we are looking ! into a subinterval: if (sDomain.begin() != it11) { --it11; MZN_MIPD__assert_hard(it11->right < intv.left); } auto it12 = it2; if (sDomain.begin() != it12) { --it12; MZN_MIPD__assert_hard_msg(it12->right <= intv.right, " relateReifFlag for " << intv << " in " << sDomain); } for (it12 = it1; it12 != it2; ++it12) { if (it12->varFlag != nullptr) { vIntvFlags.push_back(it12->varFlag->id()); } else { MZN_MIPD__assert_hard(1 == sDomain.size()); vIntvFlags.push_back(IntLit::a(1)); // just a constant then } } } } if (!vIntvFlags.empty()) { // Could find out if reif is true -- TODO && see above for 1 subinterval std::vector onesm(vIntvFlags.size(), -1.0); onesm.push_back(1.0); vIntvFlags.push_back(varFlag->id()); EnumCmpType nCmpType = (RIT_Reif == nRT) ? CMPT_EQ : CMPT_LE; addLinConstr(onesm, vIntvFlags, nCmpType, 0.0); } else { // the reif is false setVarDomain(varFlag, 0.0, 0.0); } } static void setVarDomain(VarDecl* vd, double lb, double ub) { // need to check if the new range is in the previous bounds... TODO if (vd->type().isfloat()) { // if ( 0.0==lb && 0.0==ub ) { auto* newDom = new BinOp(Location().introduce(), FloatLit::a(lb), BOT_DOTDOT, FloatLit::a(ub)); vd->ti()->domain(newDom); DBGOUT_MIPD(" NULL OUT: " << vd->id()->str()); // } } else if (vd->type().isint() || vd->type().isbool()) { auto* newDom = new SetLit( Location().introduce(), IntSetVal::a(static_cast(lb), static_cast(ub))); // TypeInst* nti = copy(mipd.getEnv()->envi(),varFlag->ti())->cast(); // nti->domain(newDom); vd->ti()->domain(newDom); } else { MZN_MIPD__assert_hard_msg(0, "Unknown var type "); } } VarDecl* addIntVar(double LB, double UB) { // GCLock lock; // Cache them? Only location can be different TODO auto* newDom = new SetLit(Location().introduce(), IntSetVal::a(static_cast(LB), static_cast(UB))); auto* ti = new TypeInst(Location().introduce(), Type::varint(), newDom); auto* newVar = new VarDecl(Location().introduce(), ti, mipd.getEnv()->envi().genId()); newVar->flat(newVar); mipd.getEnv()->envi().flatAddItem(new VarDeclI(Location().introduce(), newVar)); return newVar; } void addLinConstr(std::vector& coefs, std::vector& vars, EnumCmpType nCmpType, double rhs) { std::vector args(3); MZN_MIPD__assert_hard(vars.size() >= 2); for (auto* v : vars) { MZN_MIPD__assert_hard(&v); // throw std::string("addLinConstr: &var=NULL"); MZN_MIPD__assert_hard_msg(v->isa() || v->isa() || v->isa(), " expression at " << (&v) << " eid = " << v->eid() << " while E_INTLIT=" << Expression::E_INTLIT); // throw std::string("addLinConstr: only id's as variables allowed"); } MZN_MIPD__assert_hard(coefs.size() == vars.size()); MZN_MIPD__assert_hard(CMPT_EQ == nCmpType || CMPT_LE == nCmpType); DBGOUT_MIPD_SELF( // LinEq leq; leq.coefs=coefs; leq.vd=vars; leq.rhs=rhs; DBGOUT_MIPD__(" ADDING " << (CMPT_EQ == nCmpType ? "LIN_EQ" : "LIN_LE") << ": [ "); for (auto c : coefs) DBGOUT_MIPD__(c << ','); DBGOUT_MIPD__(" ] * [ "); for (auto v : vars) { MZN_MIPD__assert_hard(!v->isa()); if (v->isa()) DBGOUT_MIPD__(v->dynamicCast()->str() << ','); // else if ( v->isa() ) // MZN_MIPD__assert_hard ("addLinConstr: only id's as variables allowed"); else DBGOUT_MIPD__(mipd.expr2Const(v) << ','); } DBGOUT_MIPD(" ] " << (CMPT_EQ == nCmpType ? "== " : "<= ") << rhs);); std::vector nc_c; std::vector nx; bool fFloat = false; for (auto* v : vars) { if (!v->type().isint()) { fFloat = true; break; } } auto sName = constants().ids.float_.lin_eq; // "int_lin_eq"; FunctionI* fDecl = mipd.float_lin_eq; if (fFloat) { // MZN_MIPD__assert_hard all vars of same type TODO for (int i = 0; i < vars.size(); ++i) { if (fabs(coefs[i]) > 1e-8) /// Only add terms with non-0 coefs. TODO Eps=param { nc_c.push_back(FloatLit::a(coefs[i])); if (vars[i]->type().isint()) { std::vector i2f_args(1); i2f_args[0] = vars[i]; Call* i2f = new Call(Location().introduce(), constants().ids.int2float, i2f_args); i2f->type(Type::varfloat()); i2f->decl(mipd.getEnv()->model()->matchFn(mipd.getEnv()->envi(), i2f, false)); EE ret = flat_exp(mipd.getEnv()->envi(), Ctx(), i2f, nullptr, constants().varTrue); nx.push_back(ret.r()); } else { nx.push_back(vars[i]); // ->id(); once passing a general expression } } } args[2] = FloatLit::a(rhs); args[2]->type(Type::parfloat(0)); args[0] = new ArrayLit(Location().introduce(), nc_c); args[0]->type(Type::parfloat(1)); args[1] = new ArrayLit(Location().introduce(), nx); args[1]->type(Type::varfloat(1)); if (CMPT_LE == nCmpType) { sName = constants().ids.float_.lin_le; // "float_lin_le"; fDecl = mipd.float_lin_le; } } else { for (int i = 0; i < vars.size(); ++i) { if (fabs(coefs[i]) > 1e-8) /// Only add terms with non-0 coefs. TODO Eps=param { nc_c.push_back(IntLit::a(static_cast(coefs[i]))); nx.push_back(vars[i]); //->id(); } } args[2] = IntLit::a(static_cast(rhs)); args[2]->type(Type::parint(0)); args[0] = new ArrayLit(Location().introduce(), nc_c); args[0]->type(Type::parint(1)); args[1] = new ArrayLit(Location().introduce(), nx); args[1]->type(Type::varint(1)); if (CMPT_LE == nCmpType) { sName = constants().ids.int_.lin_le; // "int_lin_le"; fDecl = mipd.int_lin_le; } else { sName = constants().ids.int_.lin_eq; // "int_lin_eq"; fDecl = mipd.int_lin_eq; } } if (mipd.getEnv()->envi().cseMapEnd() != mipd.getEnv()->envi().cseMapFind(args[0])) { DBGOUT_MIPD__(" Found expr "); DBGOUT_MIPD_SELF(debugprint(args[0])); } auto* nc = new Call(Location().introduce(), ASTString(sName), args); nc->type(Type::varbool()); nc->decl(fDecl); mipd.getEnv()->envi().flatAddItem(new ConstraintI(Location().introduce(), nc)); } /// domain / reif set of one variable into that for a!her void convertIntSet(Expression* e, SetOfIntvReal& s, VarDecl* varTarget, double A, double B) { MZN_MIPD__assert_hard(A != 0.0); if (e->type().isIntSet()) { IntSetVal* S = eval_intset(mipd.getEnv()->envi(), e); IntSetRanges domr(S); for (; domr(); ++domr) { // * A + B IntVal mmin = domr.min(); IntVal mmax = domr.max(); if (A < 0.0) { std::swap(mmin, mmax); } s.insert(IntvReal( // * A + B mmin.isFinite() ? rndUpIfInt(varTarget, (static_cast(mmin.toInt()) * A + B)) : IntvReal::infMinus(), mmax.isFinite() ? rndDownIfInt(varTarget, (static_cast(mmax.toInt()) * A + B)) : IntvReal::infPlus())); } } else { assert(e->type().isFloatSet()); FloatSetVal* S = eval_floatset(mipd.getEnv()->envi(), e); FloatSetRanges domr(S); for (; domr(); ++domr) { // * A + B FloatVal mmin = domr.min(); FloatVal mmax = domr.max(); if (A < 0.0) { std::swap(mmin, mmax); } s.insert(IntvReal( // * A + B mmin.isFinite() ? rndUpIfInt(varTarget, (mmin.toDouble() * A + B)) : IntvReal::infMinus(), mmax.isFinite() ? rndDownIfInt(varTarget, (mmax.toDouble() * A + B)) : IntvReal::infPlus())); } } } /// compute the delta for float strict ineq static double computeDelta(VarDecl* var, VarDecl* varOrig, IntvReal bnds, double A, Call* pCall, int nArg) { double delta = varOrig->type().isfloat() ? MIPD::expr2Const(pCall->arg(nArg)) // * ( bnds.right-bnds.left ) ABANDONED 12.4.18 due to #207 : std::fabs(A); // delta should be scaled as well if (var->type().isint()) { // the projected-onto variable delta = std::max(1.0, delta); } return delta; } }; // class DomainDecomp /// Vars without explicit clique still need a decomposition. /// Have !iced all __POSTs, set_in's && eq_encode's to it BEFORE /// In each clique, relate all vars to one chosen /// Find all "smallest rel. factor" variables, integer && with eq_encode if avail /// Re-relate all vars to it /// Refer all __POSTs && dom() to it /// build domain decomposition /// Implement all domain constraints, incl. possible corresp, of eq_encode's /// /// REMARKS. /// ! impose effects of integrality scaling (e.g., int v = int k/3) /// BUT when using k's eq_encode? /// And when subdividing into intervals bool decomposeDomains() { // for (int iClq=0; iClq<_aCliques.size(); ++iClq ) { // TClique& clq = _aCliques[iClq]; // } bool fRetTrue = true; for (int iVar = 0; iVar < _vVarDescr.size(); ++iVar) { // VarDescr& var = _vVarDescr[iVar]; if (_vVarDescr[iVar].fDomainConstrProcessed == 0U) { GCLock lock; DomainDecomp dd(this, iVar); dd.doProcess(); _vVarDescr[iVar].fDomainConstrProcessed = 1U; } } // Clean up __POSTs: for (auto& vVar : _vVarDescr) { for (auto* pCallI : vVar.aCalls) { pCallI->remove(); } if (vVar.pEqEncoding != nullptr) { vVar.pEqEncoding->remove(); } } return fRetTrue; } static VarDecl* expr2VarDecl(Expression* arg) { // The requirement to have actual variable objects // might be a limitation if more optimizations are done before... // Might need to flexibilize this TODO // MZN_MIPD__assert_hard_msg( ! arg->dynamicCast(), // "Expression " << *arg << " is an IntLit!" ); // MZN_MIPD__assert_hard( ! arg->dynamicCast() ); // MZN_MIPD__assert_hard( ! arg->dynamicCast() ); Id* id = arg->dynamicCast(); // MZN_MIPD__assert_hard(id); if (nullptr == id) { return nullptr; // the call using this should be ignored? } VarDecl* vd = id->decl(); MZN_MIPD__assert_hard(vd); return vd; } /// Fills the vector of vardecls && returns the least index of the array template long long expr2DeclArray(Expression* arg, Array& aVD) { ArrayLit* al = eval_array_lit(getEnv()->envi(), arg); checkOrResize(aVD, al->size()); for (unsigned int i = 0; i < al->size(); i++) { aVD[i] = expr2VarDecl((*al)[i]); } return al->min(0); } /// Fills the vector of expressions && returns the least index of the array template long long expr2ExprArray(Expression* arg, Array& aVD) { ArrayLit* al = eval_array_lit(getEnv()->envi(), arg); checkOrResize(aVD, al->size()); for (unsigned int i = 0; i < al->size(); i++) { aVD[i] = ((*al)[i]); } return al->min(0); } static double expr2Const(Expression* arg) { if (auto* il = arg->dynamicCast()) { return (static_cast(il->v().toInt())); } if (auto* fl = arg->dynamicCast()) { return (fl->v().toDouble()); } if (auto* bl = arg->dynamicCast()) { return static_cast(bl->v()); } MZN_MIPD__assert_hard_msg(0, "unexpected expression instead of an int/float/bool literal: eid=" << arg->eid() << " while E_INTLIT=" << Expression::E_INTLIT); return 0.0; } template void checkOrResize(Container& cnt, size_t sz) { cnt.resize(sz); } template void checkOrResize(std::array& cnt, size_t sz) { MZN_MIPD__assert_hard(cnt.size() == sz); } template void expr2Array(Expression* arg, Array& vals) { ArrayLit* al = eval_array_lit(getEnv()->envi(), arg); // if ( typeid(typename Array::pointer) == typeid(typename Array::iterator) ) // fixed // array // MZN_MIPD__assert_hard( vals.size() == al->v().size() ); // else // vals.resize( al->v().size() ); checkOrResize(vals, al->size()); for (unsigned int i = 0; i < al->size(); i++) { vals[i] = expr2Const((*al)[i]); } } void printStats(std::ostream& os) { // if ( _aCliques.empty() ) // return; if (_vVarDescr.empty()) { return; } int nc = 0; for (auto& cl : _aCliques) { if (!cl.empty()) { ++nc; } } for (auto& var : _vVarDescr) { if (0 > var.nClique) { ++nc; // 1-var cliques } } // os << "N cliques " << _aCliques.size() << " total, " // << nc << " final" << std::endl; MZN_MIPD__assert_hard(nc); MIPD__stats[N_POSTs__eqNmapsize] = static_cast(_mNViews.size()); double nSubintvAve = MIPD__stats[N_POSTs__NSubintvSum] / nc; MZN_MIPD__assert_hard(MIPD__stats[N_POSTs__NSubintvSum]); double dSubSizeAve = MIPD__stats[N_POSTs__SubSizeSum] / MIPD__stats[N_POSTs__NSubintvSum]; os << MIPD__stats[N_POSTs__all] << " POSTs" #ifdef __MZN__MIPDOMAINS__PRINTMORESTATS " [ "; for (int i = N_POSTs__intCmpReif; i <= N_POSTs__floatAux; ++i) { os << MIPD__stats[i] << ','; } os << " ], LINEQ [ "; for (int i = N_POSTs__eq2intlineq; i <= N_POSTs__eqNmapsize; ++i) { os << MIPD__stats[i] << ','; } os << " ]" #endif ", " << MIPD__stats[N_POSTs__varsDirect] << " / " << MIPD__stats[N_POSTs__varsInvolved] << " vars, " << nc << " cliques, " << MIPD__stats[N_POSTs__NSubintvMin] << " / " << nSubintvAve << " / " << MIPD__stats[N_POSTs__NSubintvMax] << " NSubIntv m/a/m, " << MIPD__stats[N_POSTs__SubSizeMin] << " / " << dSubSizeAve << " / " << MIPD__stats[N_POSTs__SubSizeMax] << " SubIntvSize m/a/m, " << MIPD__stats[N_POSTs__cliquesWithEqEncode] << "+" << MIPD__stats[N_POSTs__clEEEnforced] << "(" << MIPD__stats[N_POSTs__clEEFound] << ")" << " clq eq_encoded "; // << std::flush if (TCliqueSorter::LinEqGraph::dCoefMax > 1.0) { os << TCliqueSorter::LinEqGraph::dCoefMin << "--" << TCliqueSorter::LinEqGraph::dCoefMax << " abs coefs"; } os << " ... "; } }; // namespace MiniZinc template template void SetOfIntervals::intersect(const SetOfIntervals& s2) { if (s2.empty()) { this->clear(); return; } this->cutOut(Interval(Interval::infMinus(), (N)s2.begin()->left)); for (auto is2 = s2.begin(); is2 != s2.end(); ++is2) { auto is2next = is2; ++is2next; this->cutOut( Interval(is2->right, s2.end() == is2next ? Interval::infPlus() : (N)is2next->left)); } } template template void SetOfIntervals::cutDeltas(const SetOfIntervals& s2, N1 delta) { if (this->empty()) { return; } // What if distance < delta? TODO for (auto is2 : s2) { if (is2.left > Interval::infMinus()) { this->cutOut(Interval(is2.left - delta, is2.left)); } if (is2.right < Interval::infPlus()) { this->cutOut(Interval(is2.right, is2.right + delta)); } } } template void SetOfIntervals::cutOut(const Interval& intv) { DBGOUT_MIPD__("Cutting " << intv << " from " << (*this)); if (this->empty()) { return; } auto it1 = (Interval::infMinus() == intv.left) ? this->lower_bound(Interval(intv.left, intv.right)) : this->upper_bound(Interval(intv.left, intv.right)); auto it2Del1 = it1; // from which to delete if (this->begin() != it1) { --it1; const N it1l = it1->left; MZN_MIPD__assert_hard(it1l <= intv.left); if (it1->right > intv.left) { // split it it2Del1 = split(it1, intv.left).second; // it1->right = intv.left; READ-ONLY // this->erase(it1); // it1 = this->end(); // auto iR = this->insert( Interval( it1l, intv.left ) ); // MZN_MIPD__assert_hard( iR.second ); } } DBGOUT_MIPD__("; after split 1: " << (*this)); // Processing the right end: auto it2 = this->lower_bound(Interval(intv.right, intv.right + 1)); auto it2Del2 = it2; if (this->begin() != it2) { --it2; MZN_MIPD__assert_hard(it2->left < intv.right); const N it2r = it2->right; if ((Interval::infPlus() == intv.right) ? (it2r > intv.right) : (it2r >= intv.right)) { // >=: split it // it2Del2 = split( it2, intv.right ).second; const bool fEEE = (it2Del1 == it2); this->erase(it2); it2 = this->end(); it2Del2 = this->insert(Interval(intv.right, it2r)); if (fEEE) { it2Del1 = it2Del2; } } } DBGOUT_MIPD__("; after split 2: " << (*this)); DBGOUT_MIPD__("; cutting out: " << SetOfIntervals(it2Del1, it2Del2)); #ifdef MZN_DBG_CHECK_ITER_CUTOUT { auto it = this->begin(); int nO = 0; do { if (it == it2Del1) { MZN_MIPD__assert_hard(!nO); ++nO; } if (it == it2Del2) { MZN_MIPD__assert_hard(1 == nO); ++nO; } if (this->end() == it) { break; } ++it; } while (true); MZN_MIPD__assert_hard(2 == nO); } #endif this->erase(it2Del1, it2Del2); DBGOUT_MIPD(" ... gives " << (*this)); } template typename SetOfIntervals::SplitResult SetOfIntervals::split(iterator& it, N pos) { MZN_MIPD__assert_hard(pos >= it->left); MZN_MIPD__assert_hard(pos <= it->right); Interval intvOld = *it; this->erase(it); auto it_01 = this->insert(Interval(intvOld.left, pos)); auto it_02 = this->insert(Interval(pos, intvOld.right)); it = this->end(); return std::make_pair(it_01, it_02); } template Interval SetOfIntervals::getBounds() const { if (this->empty()) { return Interval(Interval::infPlus(), Interval::infMinus()); } auto it2 = this->end(); --it2; return Interval(this->begin()->left, it2->right); } template bool SetOfIntervals::checkFiniteBounds() { if (this->empty()) { return false; } auto bnds = getBounds(); return bnds.left > Interval::infMinus() && bnds.right < Interval::infPlus(); } template bool SetOfIntervals::checkDisjunctStrict() { for (auto it = this->begin(); it != this->end(); ++it) { if (it->left > it->right) { return false; } if (this->begin() != it) { auto it_1 = it; --it_1; if (it_1->right >= it->left) { return false; } } } return true; } /// Assumes integer interval bounds template int SetOfIntervals::cardInt() const { int nn = 0; for (auto it = this->begin(); it != this->end(); ++it) { ++nn; nn += int(round(it->right - it->left)); } return nn; } template N SetOfIntervals::maxInterval() const { N ll = -1; for (auto it = this->begin(); it != this->end(); ++it) { ll = std::max(ll, it->right - it->left); } return ll; } /// Assumes integer interval bounds template void SetOfIntervals::split2Bits() { Base bsNew; for (auto it = this->begin(); it != this->end(); ++it) { for (int v = static_cast(round(it->left)); v <= round(it->right); ++v) { bsNew.insert(Intv(v, v)); } } *(Base*)this = std::move(bsNew); } bool MIPD::fVerbose = false; void mip_domains(Env& env, bool fVerbose, int nmi, double dmd) { MIPD mipd(&env, fVerbose, nmi, dmd); if (!mipd.doMIPdomains()) { GCLock lock; env.envi().fail(); } } double MIPD::TCliqueSorter::LinEqGraph::dCoefMin = +1e100; double MIPD::TCliqueSorter::LinEqGraph::dCoefMax = -1e100; } // namespace MiniZinc