1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
on-restart-benchmarks/lib/chain_compressor.cpp
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

560 lines
19 KiB
C++

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main authors:
* Jip J. Dekker <jip.dekker@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/. */
#include <minizinc/ast.hh>
#include <minizinc/astiterator.hh>
#include <minizinc/chain_compressor.hh>
#include <minizinc/flatten_internal.hh>
namespace MiniZinc {
void ChainCompressor::removeItem(Item* i) {
CollectDecls cd(_env.varOccurrences, _deletedVarDecls, i);
if (auto* ci = i->dynamicCast<ConstraintI>()) {
top_down(cd, ci->e());
} else if (auto* vdi = i->dynamicCast<VarDeclI>()) {
top_down(cd, vdi->e());
} else {
assert(false); // CURRENTLY NOT SUPPORTED
}
i->remove();
}
int ChainCompressor::addItem(Item* i) {
_env.flatAddItem(i);
int item_idx = static_cast<int>(_env.flat()->size()) - 1;
trackItem(i);
return item_idx;
}
void ChainCompressor::updateCount() {
for (auto it = _items.begin(); it != _items.end();) {
if (it->second->removed()) {
it = _items.erase(it);
} else {
++it;
}
}
}
void ChainCompressor::replaceCallArgument(Item* i, Call* c, unsigned int n, Expression* e) {
CollectDecls cd(_env.varOccurrences, _deletedVarDecls, i);
top_down(cd, c->arg(n));
c->arg(n, e);
CollectOccurrencesE ce(_env.varOccurrences, i);
top_down(ce, e);
}
bool ImpCompressor::trackItem(Item* i) {
if (i->removed()) {
return false;
}
if (auto* ci = i->dynamicCast<ConstraintI>()) {
if (auto* c = ci->e()->dynamicCast<Call>()) {
// clause([y], [x]); i.e. x -> y
if (c->id() == constants().ids.clause) {
auto* positive = c->arg(0)->cast<ArrayLit>();
auto* negative = c->arg(1)->cast<ArrayLit>();
if (positive->length() == 1 && negative->length() == 1) {
auto* var = (*negative)[0]->cast<Id>();
storeItem(var->decl(), i);
return true;
}
} else if (c->id() == "mzn_reverse_map_var") {
auto* control = c->arg(0)->cast<Id>();
assert(control->type().isvarbool());
storeItem(control->decl(), i);
return true;
// pred_imp(..., b); i.e. b -> pred(...)
} else if (c->id().endsWith("_imp")) {
auto* control = c->arg(c->argCount() - 1)->cast<Id>();
assert(control->type().isvarbool());
storeItem(control->decl(), i);
return true;
}
}
} else if (auto* vdi = i->dynamicCast<VarDeclI>()) {
if (vdi->e()->type().isvarbool() && (vdi->e() != nullptr) && (vdi->e()->e() != nullptr)) {
if (auto* c = vdi->e()->e()->dynamicCast<Call>()) {
// x = forall([y,z,...]); potentially: x -> (y /\ z /\ ...)
if (c->id() == constants().ids.forall) {
storeItem(vdi->e(), i);
return true;
// x ::ctx_pos = pred(...); potentially: pred_imp(..., x); i.e. x -> pred(...)
}
if (_env.fopts.enableHalfReification && vdi->e()->ann().contains(constants().ctx.pos)) {
GCLock lock;
auto cid = EnvI::halfReifyId(c->id());
std::vector<Type> args;
args.reserve(c->argCount() + 1);
for (int j = 0; j < c->argCount(); ++j) {
args.push_back(c->arg(j)->type());
}
args.push_back(Type::varbool());
FunctionI* decl = _env.model->matchFn(_env, cid, args, false);
if (decl != nullptr) {
storeItem(vdi->e(), i);
return true;
}
}
}
}
}
return false;
}
void ImpCompressor::compress() {
for (auto it = _items.begin(); it != _items.end();) {
VarDecl* lhs = nullptr;
VarDecl* rhs = nullptr;
// Check if compression is possible
if (auto* ci = it->second->dynamicCast<ConstraintI>()) {
auto* c = ci->e()->cast<Call>();
if (c->id() == constants().ids.clause) {
auto* positive = c->arg(0)->cast<ArrayLit>();
auto* var = (*positive)[0]->cast<Id>();
bool output_var = var->decl()->ann().contains(constants().ann.output_var);
auto usages = _env.varOccurrences.usages(var->decl());
output_var = output_var || usages.second;
int occurrences = usages.first;
unsigned long lhs_occurences = count(var->decl());
// Compress if:
// - There is one occurrence on the RHS of a clause and the others are on the LHS of a
// clause
// - There is one occurrence on the RHS of a clause, that Id is a reified forall that has no
// other occurrences
// - There is one occurrence on the RHS of a clause, that Id is a reification in a positive
// context, and all other occurrences are on the LHS of a clause
bool compress = !output_var && lhs_occurences > 0;
if ((var->decl()->e() != nullptr) && (var->decl()->e()->dynamicCast<Call>() != nullptr)) {
auto* call = var->decl()->e()->cast<Call>();
if (call->id() == constants().ids.forall) {
compress = compress && (occurrences == 1 && lhs_occurences == 1);
} else {
compress = compress && (occurrences == lhs_occurences);
}
} else {
compress = compress && (occurrences == lhs_occurences + 1);
}
if (compress) {
rhs = var->decl();
auto* negative = c->arg(1)->cast<ArrayLit>();
lhs = (*negative)[0]->cast<Id>()->decl();
if (lhs == rhs) {
continue;
}
}
// TODO: Detect equivalences for output variables.
}
}
if ((lhs != nullptr) && (rhs != nullptr)) {
assert(count(rhs) > 0);
auto range = find(rhs);
std::vector<Item*> to_process;
for (auto match = range.first; match != range.second; ++match) {
to_process.push_back(match->second);
}
_items.erase(range.first, range.second);
for (auto* item : to_process) {
bool success = compressItem(item, lhs);
assert(success);
_env.counters.impDel++;
}
assert(!rhs->ann().contains(constants().ann.output_var));
removeItem(it->second);
it = _items.erase(it);
} else {
++it;
}
}
}
bool ImpCompressor::compressItem(Item* i, VarDecl* newLHS) {
GCLock lock;
if (auto* ci = i->dynamicCast<ConstraintI>()) {
auto* c = ci->e()->cast<Call>();
// Given (x -> y) /\ (y -> z), produce x -> z
if (c->id() == constants().ids.clause) {
auto* positive = c->arg(0)->cast<ArrayLit>();
auto* rhs = (*positive)[0]->cast<Id>();
if (rhs->decl() != newLHS) {
ConstraintI* nci = constructClause(positive, newLHS->id());
_boolConstraints.push_back(addItem(nci));
}
removeItem(i);
return true;
// Given (x -> y) /\ (y -> pred(...)), produce x -> pred(...)
}
if (c->id() == "mzn_reverse_map_var") {
return true;
}
if (c->id().endsWith("_imp")) {
replaceCallArgument(i, c, c->argCount() - 1, newLHS->id());
trackItem(i);
return true;
}
} else if (auto* vdi = i->dynamicCast<VarDeclI>()) {
auto* c = vdi->e()->e()->dynamicCast<Call>();
// Given: (x -> y) /\ (y -> (a /\ b /\ ...)), produce (x -> a) /\ (x -> b) /\ ...
if (c->id() == constants().ids.forall) {
auto* exprs = c->arg(0)->cast<ArrayLit>();
for (int j = 0; j < exprs->size(); ++j) {
auto* rhs = (*exprs)[j]->cast<Id>();
if (rhs->decl() != newLHS) {
ConstraintI* nci = constructClause(rhs, newLHS->id());
_boolConstraints.push_back(addItem(nci));
}
}
return true;
// x ::ctx_pos = pred(...); potentially: pred_imp(..., x); i.e. x -> pred(...)
}
if (vdi->e()->ann().contains(constants().ctx.pos)) {
ConstraintI* nci = constructHalfReif(c, newLHS->id());
assert(nci);
addItem(nci);
return true;
}
}
return false;
}
ConstraintI* ImpCompressor::constructClause(Expression* pos, Expression* neg) {
assert(GC::locked());
std::vector<Expression*> args(2);
if (pos->dynamicCast<ArrayLit>() != nullptr) {
args[0] = pos;
} else {
assert(neg->type().isbool());
std::vector<Expression*> eVec(1);
eVec[0] = pos;
args[0] = new ArrayLit(pos->loc().introduce(), eVec);
args[0]->type(Type::varbool(1));
}
if (neg->dynamicCast<ArrayLit>() != nullptr) {
args[1] = neg;
} else {
assert(neg->type().isbool());
std::vector<Expression*> eVec(1);
eVec[0] = neg;
args[1] = new ArrayLit(neg->loc().introduce(), eVec);
args[1]->type(Type::varbool(1));
}
// NEVER CREATE (a -> a)
assert((*args[0]->dynamicCast<ArrayLit>())[0]->dynamicCast<Id>()->decl() !=
(*args[1]->dynamicCast<ArrayLit>())[0]->dynamicCast<Id>()->decl());
auto* nc = new Call(MiniZinc::Location().introduce(), constants().ids.clause, args);
nc->type(Type::varbool());
nc->decl(_env.model->matchFn(_env, nc, false));
assert(nc->decl());
return new ConstraintI(MiniZinc::Location().introduce(), nc);
}
ConstraintI* ImpCompressor::constructHalfReif(Call* call, Id* control) {
assert(_env.fopts.enableHalfReification);
assert(GC::locked());
auto cid = EnvI::halfReifyId(call->id());
std::vector<Expression*> args(call->argCount());
for (int i = 0; i < call->argCount(); ++i) {
args[i] = call->arg(i);
}
args.push_back(control);
FunctionI* decl = _env.model->matchFn(_env, cid, args, false);
if (decl != nullptr) {
auto* nc = new Call(call->loc().introduce(), cid, args);
nc->decl(decl);
nc->type(Type::varbool());
return new ConstraintI(call->loc().introduce(), nc);
}
return nullptr;
}
bool LECompressor::trackItem(Item* i) {
if (i->removed()) {
return false;
}
bool added = false;
if (auto* ci = i->dynamicCast<ConstraintI>()) {
if (auto* call = ci->e()->dynamicCast<Call>()) {
// {int,float}_lin_le([c1,c2,...], [x, y,...], 0);
if (call->id() == constants().ids.int_.lin_le ||
call->id() == constants().ids.float_.lin_le) {
auto* as = follow_id(call->arg(0))->cast<ArrayLit>();
auto* bs = follow_id(call->arg(1))->cast<ArrayLit>();
assert(as->size() == bs->size());
for (int j = 0; j < as->size(); ++j) {
if (as->type().isIntArray()) {
if (follow_id((*as)[j])->cast<IntLit>()->v() > IntVal(0)) {
// Check if left hand side is a variable (could be constant)
if (auto* decl = follow_id_to_decl((*bs)[j])->dynamicCast<VarDecl>()) {
storeItem(decl, i);
added = true;
}
}
} else {
if (follow_id((*as)[j])->cast<FloatLit>()->v() > FloatVal(0)) {
// Check if left hand side is a variable (could be constant)
if (auto* decl = follow_id_to_decl((*bs)[j])->dynamicCast<VarDecl>()) {
storeItem(decl, i);
added = true;
}
}
}
}
}
assert(call->id() != constants().ids.int2float);
}
} else if (auto* vdi = i->dynamicCast<VarDeclI>()) {
assert(vdi->e());
if (Expression* vde = vdi->e()->e()) {
if (auto* call = vde->dynamicCast<Call>()) {
if (call->id() == constants().ids.int2float) {
if (auto* vd = follow_id_to_decl(call->arg(0))->dynamicCast<VarDecl>()) {
auto* alias = follow_id_to_decl(vdi->e())->cast<VarDecl>();
_aliasMap[vd] = alias;
}
}
}
}
}
return added;
}
void LECompressor::compress() {
for (auto it = _items.begin(); it != _items.end();) {
VarDecl* lhs = nullptr;
VarDecl* rhs = nullptr;
VarDecl* alias = nullptr;
// Check if compression is possible
if (auto* ci = it->second->dynamicCast<ConstraintI>()) {
auto* call = ci->e()->cast<Call>();
if (call->id() == constants().ids.int_.lin_le) {
auto* as = follow_id(call->arg(0))->cast<ArrayLit>();
auto* bs = follow_id(call->arg(1))->cast<ArrayLit>();
auto* c = follow_id(call->arg(2))->cast<IntLit>();
if (bs->size() == 2 && c->v() == IntVal(0)) {
auto a0 = follow_id((*as)[0])->cast<IntLit>()->v();
auto a1 = follow_id((*as)[1])->cast<IntLit>()->v();
if (a0 == -a1 && eqBounds((*bs)[0], (*bs)[1])) {
int i = a0 < a1 ? 0 : 1;
if (!(*bs)[i]->isa<Id>()) {
break;
}
auto* neg = follow_id_to_decl((*bs)[i])->cast<VarDecl>();
bool output_var = neg->ann().contains(constants().ann.output_var);
auto usages = _env.varOccurrences.usages(neg);
int occurrences = usages.first;
output_var = output_var || usages.second;
unsigned long lhs_occurences = count(neg);
bool compress = !output_var;
auto search = _aliasMap.find(neg);
if (search != _aliasMap.end()) {
alias = search->second;
auto alias_usages = _env.varOccurrences.usages(alias);
int alias_occ = alias_usages.first;
compress = compress && (!alias_usages.second);
unsigned long alias_lhs_occ = count(alias);
// neg is only allowed to occur:
// - once in the "implication"
// - once in the aliasing
// - on a lhs of other expressions
// alias is only allowed to occur on a lhs of an expression.
compress = compress && (lhs_occurences + alias_lhs_occ > 0) &&
(occurrences == lhs_occurences + 2) && (alias_occ == alias_lhs_occ);
} else {
// neg is only allowed to occur:
// - once in the "implication"
// - on a lhs of other expressions
compress = compress && (lhs_occurences > 0) && (occurrences == lhs_occurences + 1);
}
auto* pos = follow_id_to_decl((*bs)[1 - i])->dynamicCast<VarDecl>();
if ((pos != nullptr) && compress) {
rhs = neg;
lhs = pos;
assert(lhs != rhs);
}
// TODO: Detect equivalences for output variables.
}
}
}
}
if ((lhs != nullptr) && (rhs != nullptr)) {
assert(count(rhs) + count(alias) > 0);
auto range = find(rhs);
{
std::vector<Item*> to_process;
for (auto match = range.first; match != range.second; ++match) {
to_process.push_back(match->second);
}
_items.erase(range.first, range.second);
for (auto* item : to_process) {
leReplaceVar<IntLit>(item, rhs, lhs);
}
}
if (alias != nullptr) {
VarDecl* i2f_lhs;
auto search = _aliasMap.find(lhs);
if (search != _aliasMap.end()) {
i2f_lhs = search->second;
} else {
// Create new int2float
Call* i2f = new Call(lhs->loc().introduce(), constants().ids.int2float, {lhs->id()});
i2f->decl(_env.model->matchFn(_env, i2f, false));
assert(i2f->decl());
i2f->type(Type::varfloat());
auto* domain =
new SetLit(lhs->loc().introduce(), eval_floatset(_env, lhs->ti()->domain()));
auto* i2f_ti = new TypeInst(lhs->loc().introduce(), Type::varfloat(), domain);
i2f_lhs = new VarDecl(lhs->loc().introduce(), i2f_ti, _env.genId(), i2f);
i2f_lhs->type(Type::varfloat());
addItem(new VarDeclI(lhs->loc().introduce(), i2f_lhs));
}
auto arange = find(alias);
{
std::vector<Item*> to_process;
for (auto match = arange.first; match != arange.second; ++match) {
to_process.push_back(match->second);
}
_items.erase(arange.first, arange.second);
for (auto* item : to_process) {
leReplaceVar<FloatLit>(item, alias, i2f_lhs);
}
}
}
assert(!rhs->ann().contains(constants().ann.output_var));
removeItem(it->second);
_env.counters.linDel++;
it = _items.erase(it);
} else {
++it;
}
}
}
template <class Lit>
void LECompressor::leReplaceVar(Item* i, VarDecl* oldVar, VarDecl* newVar) {
typedef typename LinearTraits<Lit>::Val Val;
GCLock lock;
auto* ci = i->cast<ConstraintI>();
auto* call = ci->e()->cast<Call>();
assert(call->id() == constants().ids.int_.lin_le || call->id() == constants().ids.float_.lin_le);
// Remove old occurrences
CollectDecls cd(_env.varOccurrences, _deletedVarDecls, i);
top_down(cd, ci->e());
ArrayLit* al_c = eval_array_lit(_env, call->arg(0));
std::vector<Val> coeffs(al_c->size());
for (int j = 0; j < al_c->size(); j++) {
coeffs[j] = LinearTraits<Lit>::eval(_env, (*al_c)[j]);
}
ArrayLit* al_x = eval_array_lit(_env, call->arg(1));
std::vector<KeepAlive> x(al_x->size());
for (int j = 0; j < al_x->size(); j++) {
Expression* decl = follow_id_to_decl((*al_x)[j]);
if (decl && decl->cast<VarDecl>() == oldVar) {
x[j] = newVar->id();
} else {
x[j] = (*al_x)[j];
}
}
Val d = LinearTraits<Lit>::eval(_env, call->arg(2));
simplify_lin<Lit>(coeffs, x, d);
if (coeffs.empty()) {
i->remove();
_env.counters.linDel++;
return;
}
std::vector<Expression*> coeffs_e(coeffs.size());
std::vector<Expression*> x_e(coeffs.size());
for (unsigned int j = 0; j < coeffs.size(); j++) {
coeffs_e[j] = Lit::a(coeffs[j]);
x_e[j] = x[j]();
Expression* decl = follow_id_to_decl(x_e[j]);
if (decl && decl->cast<VarDecl>() == newVar) {
storeItem(newVar, i);
}
}
if (auto* arg0 = call->arg(0)->dynamicCast<ArrayLit>()) {
arg0->setVec(coeffs_e);
} else {
auto* al_c_new = new ArrayLit(al_c->loc().introduce(), coeffs_e);
al_c_new->type(al_c->type());
call->arg(0, al_c_new);
}
if (auto* arg1 = call->arg(1)->dynamicCast<ArrayLit>()) {
arg1->setVec(x_e);
} else {
auto* al_x_new = new ArrayLit(al_x->loc().introduce(), x_e);
al_x_new->type(al_x->type());
call->arg(1, al_x_new);
}
call->arg(2, Lit::a(d));
// Add new occurences
CollectOccurrencesE ce(_env.varOccurrences, i);
top_down(ce, ci->e());
}
bool LECompressor::eqBounds(Expression* a, Expression* b) {
// TODO: (To optimise) Check lb(lhs) >= lb(rhs) and enforce ub(lhs) <= ub(rhs)
IntSetVal* dom_a = nullptr;
IntSetVal* dom_b = nullptr;
if (auto* a_decl = follow_id_to_decl(a)->dynamicCast<VarDecl>()) {
if (a_decl->ti()->domain() != nullptr) {
dom_a = eval_intset(_env, a_decl->ti()->domain());
}
} else {
assert(a->dynamicCast<IntLit>());
auto* a_val = a->cast<IntLit>();
dom_a = IntSetVal::a(a_val->v(), a_val->v());
}
if (auto* b_decl = follow_id_to_decl(b)->dynamicCast<VarDecl>()) {
if (b_decl->ti()->domain() != nullptr) {
dom_b = eval_intset(_env, b_decl->ti()->domain());
}
} else {
assert(b->dynamicCast<IntLit>());
auto* b_val = b->cast<IntLit>();
dom_b = IntSetVal::a(b_val->v(), b_val->v());
}
return ((dom_a != nullptr) && (dom_b != nullptr) && (dom_a->min() == dom_b->min()) &&
(dom_a->max() == dom_b->max())) ||
((dom_a == nullptr) && (dom_b == nullptr));
}
} // namespace MiniZinc