more work on sat and flatzinc presolve

This commit is contained in:
Laurent Perron
2016-11-18 15:32:26 +01:00
parent 1549119aa8
commit 3d1fca3efb
8 changed files with 490 additions and 83 deletions

View File

@@ -216,6 +216,67 @@ bool Domain::Contains(int64 value) const {
}
}
namespace {
bool IntervalOverlapValues(int64 lb, int64 ub, const std::vector<int64>& values) {
for (int64 value : values) {
if (lb <= value && value <= ub) {
return true;
}
}
return false;
}
} // namespace
bool Domain::OverlapsIntList(const std::vector<int64>& vec) const {
if (IsAllInt64()) {
return true;
}
if (is_interval) {
CHECK(!values.empty());
return IntervalOverlapValues(values[0], values[1], vec);
} else {
// TODO(user): Better algorithm, sort and compare increasingly.
const std::vector<int64>& to_scan =
values.size() <= vec.size() ? values : vec;
const std::unordered_set<int64> container =
values.size() <= vec.size()
? std::unordered_set<int64>(vec.begin(), vec.end())
: std::unordered_set<int64>(values.begin(), values.end());
for (int64 value : to_scan) {
if (ContainsKey(container, value)) {
return true;
}
}
return false;
}
}
bool Domain::OverlapsIntInterval(int64 lb, int64 ub) const {
if (IsAllInt64()) {
return true;
}
if (is_interval) {
CHECK(!values.empty());
const int64 dlb = values[0];
const int64 dub = values[1];
return !(dub < lb || dlb > ub);
} else {
return IntervalOverlapValues(lb, ub, values);
}
}
bool Domain::OverlapsDomain(const Domain& other) const {
if (other.is_interval) {
if (other.values.empty()) {
return true;
} else {
return OverlapsIntInterval(other.values[0], other.values[1]);
}
} else {
return OverlapsIntList(other.values);
}
}
bool Domain::RemoveValue(int64 value) {
if (is_interval) {
if (values.empty()) {

View File

@@ -67,7 +67,11 @@ struct Domain {
// Returns true if the domain is [kint64min..kint64max]
bool IsAllInt64() const;
// Various inclusion tests on a domain.
bool Contains(int64 value) const;
bool OverlapsIntList(const std::vector<int64>& values) const;
bool OverlapsIntInterval(int64 lb, int64 ub) const;
bool OverlapsDomain(const Domain& other) const;
// All the following modifiers change the internal representation
// list to interval or interval to list.

View File

@@ -81,6 +81,63 @@ std::unordered_set<int64> GetValueSet(const Argument& arg) {
return result;
}
void SetConstraintAsIntEq(Constraint* ct, IntegerVariable* var, int64 value) {
ct->type = "int_eq";
ct->arguments.clear();
ct->arguments.push_back(Argument::IntVarRef(var));
ct->arguments.push_back(Argument::IntegerValue(value));
}
bool OverlapsAt(const Argument& array, int pos, const Argument& other) {
if (array.type == Argument::INT_VAR_REF_ARRAY) {
const Domain& domain = array.variables[pos]->domain;
if (domain.IsAllInt64()) {
return true;
}
switch (other.type) {
case Argument::INT_VALUE: {
return domain.Contains(other.Value());
}
case Argument::INT_INTERVAL: {
return domain.OverlapsIntInterval(other.values[0], other.values[1]);
}
case Argument::INT_LIST: {
return domain.OverlapsIntList(other.values);
}
case Argument::INT_VAR_REF: {
return domain.OverlapsDomain(other.variables[0]->domain);
}
default: {
LOG(FATAL) << "Case not supported in OverlapsAt";
return false;
}
}
} else if (array.type == Argument::INT_LIST) {
const int64 value = array.values[pos];
switch (other.type) {
case Argument::INT_VALUE: {
return value == other.values[0];
}
case Argument::INT_INTERVAL: {
return other.values[0] <= value && value <= other.values[1];
}
case Argument::INT_LIST: {
return std::find(other.values.begin(), other.values.end(), value) !=
other.values.end();
}
case Argument::INT_VAR_REF: {
return other.variables[0]->domain.Contains(value);
}
default: {
LOG(FATAL) << "Case not supported in OverlapsAt";
return false;
}
}
} else {
LOG(FATAL) << "First argument not supported in OverlapsAt";
return false;
}
}
} // namespace
// For the author's reference, here is an indicative list of presolve rules
@@ -580,6 +637,17 @@ bool Presolver::PresolveIntTimes(Constraint* ct, std::string* log) {
// TODO(user): Treat overflow correctly.
}
}
// Special case: multiplication by zero.
if ((ct->arguments[0].HasOneValue() && ct->arguments[0].Value() == 0) ||
(ct->arguments[1].HasOneValue() && ct->arguments[1].Value() == 0)) {
ct->type = "int_eq";
ct->arguments[0] = ct->arguments[2];
ct->arguments.resize(1);
ct->arguments.push_back(Argument::IntegerValue(0));
return true;
}
return false;
}
@@ -865,25 +933,60 @@ bool Presolver::PresolveIntLinLt(Constraint* ct, std::string* log) {
// with (c1 == 1 or c2 % c1 == 0) and xx = eq, ne, lt, le, gt, ge
// Output: int_xx(x, c2 / c1) and int_xx_reif(x, c2 / c1, b)
bool Presolver::SimplifyUnaryLinear(Constraint* ct, std::string* log) {
if (!ct->arguments[0].HasOneValue()) {
if (!ct->arguments[0].HasOneValue() ||
ct->arguments[1].variables.size() != 1) {
return false;
}
const int64 coefficient = ct->arguments[0].Value();
const int64 coeff = ct->arguments[0].values.front();
const int64 rhs = ct->arguments[2].Value();
if (coefficient == 1 || (coefficient > 0 && rhs % coefficient == 0)) {
// TODO(user): Support coefficient = 0.
IntegerVariable* const var = ct->arguments[1].variables.front();
const std::string op = ct->type.substr(8, 2);
bool changed = false;
int64 new_rhs = 0;
if (coeff == 0) {
ct->arguments[0].values.clear();
ct->arguments[1].variables.clear();
// Will be process by PresolveLinear.
return true;
}
if (op == "eq") {
if (rhs % coeff == 0) {
changed = true;
new_rhs = rhs / coeff;
} else { // Equality always false.
if (ct->arguments.size() == 4) { // reified version.
SetConstraintAsIntEq(ct, ct->arguments[3].Var(), 0);
} else {
ct->SetAsFalse();
}
return true;
}
} else if (op == "ne") {
if (rhs % coeff == 0) {
changed = true;
new_rhs = rhs / coeff;
} else { // Equality always true.
if (ct->arguments.size() == 4) { // reified version.
SetConstraintAsIntEq(ct, ct->arguments[3].Var(), 1);
} else {
ct->MarkAsInactive();
}
return true;
}
} else if (coeff >= 0 && rhs % coeff == 0) {
// TODO(user): Support coefficient < 0 (and reverse the inequalities).
// TODO(user): Support rhs % coefficient != 0, and no the correct
// rounding in the case of inequalities, of false model in the case of
// equalities.
// TODO(user): Support rhs % coefficient != 0, and do the correct
// rounding in the case of inequalities.
changed = true;
new_rhs = rhs / coeff;
}
if (changed) {
log->append("remove linear part");
// transform arguments.
ct->arguments[0].type = Argument::INT_VAR_REF;
ct->arguments[0].values.clear();
ct->arguments[0].variables.push_back(ct->arguments[1].variables[0]);
ct->arguments[1].type = Argument::INT_VALUE;
ct->arguments[1].variables.clear();
ct->arguments[1].values.push_back(rhs / coefficient);
ct->arguments[0] = Argument::IntVarRef(var);
ct->arguments[1] = Argument::IntegerValue(new_rhs);
ct->RemoveArg(2);
// Change type (remove "_lin" part).
DCHECK(ct->type.size() >= 8 && ct->type.substr(3, 4) == "_lin");
@@ -1132,10 +1235,14 @@ bool Presolver::PresolveArrayIntElement(Constraint* ct, std::string* log) {
// with yyy is the opposite of xxx (eq -> eq, ne -> ne, le -> ge,
// lt -> gt, ge -> le, gt -> lt)
//
// Rule 2:
// Rule 2a:
// Input: int_lin_xxx[[c1, .., cn], [c'1, .., c'n], c0] (no variables)
// Output: inactive or false constraint.
//
// Rule 2b:
// Input: int_lin_xxx[[], [], c0] or int_lin_xxx_reif([], [], c0)
// Output: inactive or false constraint.
//
// Rule 3:
// Input: int_lin_xxx_reif[[c1, .., cn], [c'1, .., c'n], c0] (no variables)
// Output: bool_eq(c0, true or false).
@@ -1147,11 +1254,8 @@ bool Presolver::PresolveArrayIntElement(Constraint* ct, std::string* log) {
//
// TODO(user): The code is broken in case of integer-overflow.
bool Presolver::PresolveLinear(Constraint* ct, std::string* log) {
if (ct->arguments[0].values.empty()) {
return false;
}
// Rule 2.
if (ct->arguments[1].IsArrayOfValues()) {
// Rule 2a and 2b.
if (ct->arguments[0].values.empty() || ct->arguments[1].IsArrayOfValues()) {
log->append("rewrite constant linear equation");
int64 scalprod = 0;
for (int i = 0; i < ct->arguments[0].values.size(); ++i) {
@@ -1219,6 +1323,11 @@ bool Presolver::PresolveLinear(Constraint* ct, std::string* log) {
return true;
}
if (ct->arguments[0].values.empty()) {
return false;
}
// From now on, we assume the linear part is not empty.
// Rule 4.
if (!ct->arguments[1].variables.empty()) {
const int size = ct->arguments[1].variables.size();
@@ -1384,6 +1493,78 @@ bool Presolver::PropagatePositiveLinear(Constraint* ct, std::string* log) {
return modified;
}
// Input: int_lin_xx([c1, .., cn], [x1, .., xn], rhs) with ci >= 0.
//
// Computes the bounds on the rhs.
// Rule1: remove always true/false constraint or fix the reif Boolean.
// Rule2: transform ne/eq to gt/ge/lt/le if rhs is at one bound of its domain.
bool Presolver::SimplifyPositiveLinear(Constraint* ct, std::string* log) {
const int64 rhs = ct->arguments[2].Value();
if (ct->presolve_propagation_done || rhs < 0 ||
ct->arguments[1].variables.empty()) {
return false;
}
int64 rhs_min = 0;
int64 rhs_max = 0;
const int n = ct->arguments[0].values.size();
for (int i = 0; i < n; ++i) {
const int64 coeff = ct->arguments[0].values[i];
if (coeff < 0) return false;
rhs_min += coeff * ct->arguments[1].variables[i]->domain.Min();
rhs_max += coeff * ct->arguments[1].variables[i]->domain.Max();
}
if (rhs < rhs_min || rhs > rhs_max) {
if (ct->type == "int_lin_eq") {
ct->SetAsFalse();
return true;
} else if (ct->type == "int_lin_eq_reif") {
ct->type = "bool_eq";
ct->arguments[0] = ct->arguments[3];
ct->arguments.resize(1);
ct->arguments.push_back(Argument::IntegerValue(0));
return true;
} else if (ct->type == "int_lin_ne") {
ct->MarkAsInactive();
return true;
} else if (ct->type == "int_lin_ne_reif") {
ct->type = "bool_eq";
ct->arguments[0] = ct->arguments[3];
ct->arguments.resize(1);
ct->arguments.push_back(Argument::IntegerValue(1));
return true;
}
} else if (rhs == rhs_min) {
if (ct->type == "int_lin_eq") {
ct->type = "int_lin_le";
return true;
} else if (ct->type == "int_lin_eq_reif") {
ct->type = "int_lin_le_reif";
return true;
} else if (ct->type == "int_lin_ne") {
ct->type = "int_lin_gt";
return true;
} else if (ct->type == "int_lin_ne_reif") {
ct->type = "int_lin_gt_reif";
return true;
}
} else if (rhs == rhs_max) {
if (ct->type == "int_lin_eq") {
ct->type = "int_lin_ge";
return true;
} else if (ct->type == "int_lin_eq_reif") {
ct->type = "int_lin_ge_reif";
return true;
} else if (ct->type == "int_lin_ne") {
ct->type = "int_lin_lt";
return true;
} else if (ct->type == "int_lin_ne_reif") {
ct->type = "int_lin_lt_reif";
return true;
}
}
return false;
}
// Minizinc flattens 2d element constraints (x = A[y][z]) into 1d element
// constraint with an affine mapping between y, z and the new index.
// This rule stores the mapping to reconstruct the 2d element constraint.
@@ -1512,9 +1693,7 @@ bool Presolver::PresolveSimplifyElement(Constraint* ct, std::string* log) {
const int64 index = ct->arguments[0].Value() - 1;
const int64 value = ct->arguments[1].values[index];
// Rewrite as equality.
ct->type = "int_eq";
ct->arguments[0] = Argument::IntegerValue(value);
ct->RemoveArg(1);
SetConstraintAsIntEq(ct, ct->arguments[2].Var(), value);
return true;
}
@@ -1790,32 +1969,39 @@ bool Presolver::PresolveSimplifyExprElement(Constraint* ct, std::string* log) {
}
// Rule 4.
if (ct->arguments[0].IsVariable() && ct->arguments[2].HasOneValue()) {
bool changed = false;
const int64 value = ct->arguments[2].Value();
const std::vector<IntegerVariable*>& vars = ct->arguments[1].variables;
for (int index = 1; index <= vars.size(); ++index) {
if (!ct->arguments[0].Var()->domain.Contains(index)) continue;
const Domain& x = vars[index - 1]->domain; // 1-based in minizinc.
if (!x.Contains(value)) {
ct->arguments[0].Var()->domain.RemoveValue(index);
changed = true;
if (ct->arguments[0].IsVariable()) {
const Domain& domain = ct->arguments[0].Var()->domain;
std::vector<int64> to_keep;
const int array_size = ct->arguments[1].variables.size();
bool remove_some = false;
if (domain.is_interval) {
for (int64 v = std::max<int64>(1, domain.values[0]);
v <= std::min<int64>(array_size, domain.values[1]); ++v) {
if (OverlapsAt(ct->arguments[1], v - 1, ct->arguments[2])) {
to_keep.push_back(v);
} else {
remove_some = true;
}
}
} else {
for (int64 v : domain.values) {
if (v < 1 || v > array_size) {
remove_some = true;
} else {
if (OverlapsAt(ct->arguments[1], v - 1, ct->arguments[2])) {
to_keep.push_back(v);
} else {
remove_some = true;
}
}
}
}
return changed;
} else if (ct->arguments[0].IsVariable() && ct->arguments[2].IsVariable()) {
bool changed = false;
const std::vector<IntegerVariable*>& vars = ct->arguments[1].variables;
for (int index = 1; index <= vars.size(); ++index) {
if (!ct->arguments[0].Var()->domain.Contains(index)) continue;
const Domain& x = vars[index - 1]->domain; // 1-based in minizinc.
const Domain& y = ct->arguments[2].Var()->domain;
if (x.Min() > y.Max() || y.Min() > x.Max()) {
ct->arguments[0].Var()->domain.RemoveValue(index);
changed = true;
}
if (remove_some) {
ct->arguments[0].Var()->domain.IntersectWithListOfIntegers(to_keep);
log->append(StringPrintf("reduce index domain to %s",
ct->arguments[0].Var()->DebugString().c_str()));
return true;
}
return changed;
}
return false;
@@ -1915,6 +2101,7 @@ bool Presolver::PropagateReifiedComparisons(Constraint* ct, std::string* log) {
ct->arguments.push_back(Argument::IntVarRef(var));
ct->arguments.push_back(target);
ct->type = parity ? "bool_eq" : "bool_not";
return true;
} else {
// Rule 3.
int state = 2; // 0 force_false, 1 force true, 2 unknown.
@@ -1985,10 +2172,11 @@ bool Presolver::PropagateReifiedComparisons(Constraint* ct, std::string* log) {
const Domain& rd = ct->arguments[1].Var()->domain;
int state = 2; // 0 force_false, 1 force true, 2 unknown.
if (id == "int_eq_reif" || id == "bool_eq_reif") {
if (ld.Min() > rd.Max() || ld.Max() < rd.Min()) {
if (!ld.OverlapsDomain(rd)) {
state = 0;
}
} else if (id == "int_ne_reif" || id == "bool_ne_reif") {
// TODO(user): Test if the domain are disjoint.
if (ld.Min() > rd.Max() || ld.Max() < rd.Min()) {
state = 1;
}
@@ -2401,7 +2589,7 @@ bool Presolver::PresolveTableInt(Constraint* ct, std::string* log) {
const int num_tuples = ct->arguments[1].values.size() / num_vars;
int ignored_tuples = 0;
std::vector<int64> new_tuples;
std::vector<std::unordered_set<int64>> visited_values(num_vars);
std::vector<std::unordered_set<int64>> next_values(num_vars);
for (int t = 0; t < num_tuples; ++t) {
std::vector<int64> tuple(
ct->arguments[1].values.begin() + t * num_vars,
@@ -2415,7 +2603,7 @@ bool Presolver::PresolveTableInt(Constraint* ct, std::string* log) {
}
if (valid) {
for (int i = 0; i < num_vars; ++i) {
visited_values[i].insert(tuple[i]);
next_values[i].insert(tuple[i]);
}
new_tuples.insert(new_tuples.end(), tuple.begin(), tuple.end());
} else {
@@ -2437,8 +2625,8 @@ bool Presolver::PresolveTableInt(Constraint* ct, std::string* log) {
const int vmin =
var->domain.values.empty() ? 0 : var->domain.values.front();
const int vmax = var->domain.values.empty() ? 0 : var->domain.values.back();
std::vector<int64> values(visited_values[var_index].begin(),
visited_values[var_index].end());
std::vector<int64> values(next_values[var_index].begin(),
next_values[var_index].end());
// TODO(user): Add return value that indicates change to IntersectXXX().
var->domain.IntersectWithListOfIntegers(values);
variable_changed |= is_interval != var->domain.is_interval ||
@@ -2449,6 +2637,124 @@ bool Presolver::PresolveTableInt(Constraint* ct, std::string* log) {
return variable_changed || ignored_tuples > 0;
}
bool Presolver::PresolveRegular(Constraint* ct, std::string* log) {
const std::vector<IntegerVariable*> vars = ct->arguments[0].variables;
if (vars.empty()) {
// TODO(user): presolve when all variables are instantiated.
return false;
}
const int num_vars = vars.size();
const int64 num_states = ct->arguments[1].Value();
const int64 num_values = ct->arguments[2].Value();
// Read transitions.
const std::vector<int64>& array_transitions = ct->arguments[3].values;
std::vector<std::vector<int64>> automata;
int count = 0;
for (int i = 1; i <= num_states; ++i) {
for (int j = 1; j <= num_values; ++j) {
automata.push_back({i, j, array_transitions[count++]});
}
}
const int64 initial_state = ct->arguments[4].Value();
std::unordered_set<int64> final_states;
switch (ct->arguments[5].type) {
case fz::Argument::INT_VALUE: {
final_states.insert(ct->arguments[5].values[0]);
break;
}
case fz::Argument::INT_INTERVAL: {
for (int64 v = ct->arguments[5].values[0];
v <= ct->arguments[5].values[1]; ++v) {
final_states.insert(v);
}
break;
}
case fz::Argument::INT_LIST: {
for (int64 value : ct->arguments[5].values) {
final_states.insert(value);
}
break;
}
default: { LOG(FATAL) << "Wrong constraint " << ct->DebugString(); }
}
// Compute the set of reachable state at each time point.
std::vector<std::unordered_set<int64>> reachable_states(num_vars + 1);
reachable_states[0].insert(initial_state);
reachable_states[num_vars] = {final_states.begin(), final_states.end()};
// Forward.
for (int time = 0; time + 1 < num_vars; ++time) {
const Domain& domain = vars[time]->domain;
for (const std::vector<int64>& transition : automata) {
if (!ContainsKey(reachable_states[time], transition[0])) continue;
if (!domain.Contains(transition[1])) continue;
reachable_states[time + 1].insert(transition[2]);
}
}
// Backward.
for (int time = num_vars - 1; time > 0; --time) {
std::unordered_set<int64> new_set;
const Domain& domain = vars[time]->domain;
for (const std::vector<int64>& transition : automata) {
if (!ContainsKey(reachable_states[time], transition[0])) continue;
if (!domain.Contains(transition[1])) continue;
if (!ContainsKey(reachable_states[time + 1], transition[2])) continue;
new_set.insert(transition[0]);
}
reachable_states[time].swap(new_set);
}
// Prune the variables.
bool changed = false;
for (int time = 0; time < num_vars; ++time) {
Domain& domain = vars[time]->domain;
// Collect valid values.
std::unordered_set<int64> reached_values;
for (const std::vector<int64>& transition : automata) {
if (!ContainsKey(reachable_states[time], transition[0])) continue;
if (!domain.Contains(transition[1])) continue;
if (!ContainsKey(reachable_states[time + 1], transition[2])) continue;
reached_values.insert(transition[1]);
}
// Scan domain to check if we will remove values.
std::vector<int64> to_keep;
bool remove_some = false;
if (domain.is_interval) {
for (int64 v = domain.values[0]; v <= domain.values[1]; ++v) {
if (ContainsKey(reached_values, v)) {
to_keep.push_back(v);
} else {
remove_some = true;
}
}
} else {
for (int64 v : domain.values) {
if (ContainsKey(reached_values, v)) {
to_keep.push_back(v);
} else {
remove_some = true;
}
}
}
if (remove_some) {
const std::string& before = HASVLOG ? vars[time]->DebugString() : "";
domain.IntersectWithListOfIntegers(to_keep);
if (HASVLOG) {
StringAppendF(log, "reduce domain of variable %d from %s to %s; ", time,
before.c_str(), vars[time]->DebugString().c_str());
}
changed = true;
}
}
return changed;
}
// Tranforms diffn into all_different_int when sizes and y positions are all 1.
//
// Input : diffn([x1, .. xn], [1, .., 1], [1, .., 1], [1, .., 1])
@@ -2552,6 +2858,10 @@ bool Presolver::PresolveOneConstraint(Constraint* ct) {
CALL_TYPE(ct, "int_lin_eq", PropagatePositiveLinear);
CALL_TYPE(ct, "int_lin_le", PropagatePositiveLinear);
CALL_TYPE(ct, "int_lin_ge", PropagatePositiveLinear);
CALL_TYPE(ct, "int_lin_eq", SimplifyPositiveLinear);
CALL_TYPE(ct, "int_lin_ne", SimplifyPositiveLinear);
CALL_TYPE(ct, "int_lin_eq_reif", SimplifyPositiveLinear);
CALL_TYPE(ct, "int_lin_ne_reif", SimplifyPositiveLinear);
CALL_TYPE(ct, "int_lin_eq", CreateLinearTarget);
CALL_TYPE(ct, "int_lin_eq", PresolveStoreMapping);
CALL_TYPE(ct, "int_lin_eq_reif", CheckIntLinReifBounds);
@@ -2602,6 +2912,7 @@ bool Presolver::PresolveOneConstraint(Constraint* ct) {
}
CALL_TYPE(ct, "table_int", PresolveTableInt);
CALL_TYPE(ct, "diffn", PresolveDiffN);
CALL_TYPE(ct, "regular", PresolveRegular);
// Last rule: if the target variable of a constraint is fixed, removed it
// the target part.

View File

@@ -142,6 +142,7 @@ class Presolver {
bool PresolveLinear(Constraint* ct, std::string* log);
bool RegroupLinear(Constraint* ct, std::string* log);
bool PropagatePositiveLinear(Constraint* ct, std::string* log);
bool SimplifyPositiveLinear(Constraint* ct, std::string* log);
bool PresolveStoreMapping(Constraint* ct, std::string* log);
bool PresolveSimplifyElement(Constraint* ct, std::string* log);
bool PresolveSimplifyExprElement(Constraint* ct, std::string* log);
@@ -159,6 +160,7 @@ class Presolver {
bool StoreIntEqReif(Constraint* ct, std::string* log);
bool SimplifyIntNeReif(Constraint* ct, std::string* log);
bool PresolveTableInt(Constraint* ct, std::string* log);
bool PresolveRegular(Constraint* ct, std::string* log);
bool PresolveDiffN(Constraint* ct, std::string* log);
// Helpers.

View File

@@ -462,17 +462,15 @@ void ExtractIntLinNeReif(const fz::Constraint& ct, SatModel* m) {
m->model.Add(FixedWeightedSumReif(r.Negated(), vars, coeffs, rhs));
}
// r => (a == cte)
// r => (a == cte).
void ImpliesEqualityToConstant(bool reverse_implication, IntegerVariable a,
int64 cte, Literal r, SatModel* m) {
if (m->model.Get(IsFixed(a))) {
if (m->model.Get(Value(a)) == IntegerValue(cte)) {
if (reverse_implication) {
FZLOG << "Case could have been presolved." << FZENDL;
m->model.GetOrCreate<SatSolver>()->AddUnitClause(r);
}
} else {
FZLOG << "Case could have been presolved." << FZENDL;
m->model.GetOrCreate<SatSolver>()->AddUnitClause(r.Negated());
}
return;
@@ -511,7 +509,6 @@ void ImpliesEqualityToConstant(bool reverse_implication, IntegerVariable a,
}
// Value is not found, the literal must be false.
FZLOG << "Case could have been presolved." << FZENDL;
m->model.GetOrCreate<SatSolver>()->AddUnitClause(r.Negated());
}
@@ -936,7 +933,7 @@ bool ExtractConstraint(const fz::Constraint& ct, SatModel* m) {
ExtractArrayVarIntElement(ct, m);
} else if (ct.type == "all_different_int") {
ExtractAllDifferentInt(ct, m);
} else if (ct.type == "int_eq") {
} else if (ct.type == "int_eq" || ct.type == "bool2int") {
ExtractIntEq(ct, m);
} else if (ct.type == "int_ne") {
ExtractIntNe(ct, m);
@@ -990,6 +987,8 @@ bool ExtractConstraint(const fz::Constraint& ct, SatModel* m) {
ct.type == "variable_cumulative" ||
ct.type == "fixed_cumulative") {
ExtractCumulative(ct, m);
} else if (ct.type == "false_constraint") {
m->model.GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
} else {
return false;
}
@@ -1147,12 +1146,23 @@ void SolveWithSat(const fz::Model& fz_model, const fz::FlatzincParameters& p,
FZLOG << "Extracting " << fz_model.constraints().size() << " constraints. "
<< FZENDL;
std::set<std::string> unsupported_types;
Trail* trail = m.model.GetOrCreate<Trail>();
for (fz::Constraint* ct : fz_model.constraints()) {
if (ct != nullptr && ct->active) {
const int old_num_fixed = trail->Index();
FZVLOG << "Extracting '" << ct->type << "'." << FZENDL;
if (!ExtractConstraint(*ct, &m)) {
unsupported_types.insert(ct->type);
}
// We propagate after each new Boolean constraint but not the integer
// ones. So we call Propagate() manually here. TODO(user): Do that
// automatically?
m.model.GetOrCreate<SatSolver>()->Propagate();
if (trail->Index() > old_num_fixed) {
FZVLOG << "Constraint fixed " << trail->Index() - old_num_fixed
<< " Boolean variable(s): " << ct->DebugString() << FZENDL;
}
if (m.model.GetOrCreate<SatSolver>()->IsModelUnsat()) {
FZLOG << "UNSAT during extraction (after adding '" << ct->type << "')."
<< FZENDL;
@@ -1169,21 +1179,30 @@ void SolveWithSat(const fz::Model& fz_model, const fz::FlatzincParameters& p,
}
// Some stats.
int num_fully_encoded_variables = 0;
for (int i = 0; i < m.model.Get<IntegerTrail>()->NumIntegerVariables(); ++i) {
if (m.model.Get<IntegerEncoder>()->VariableIsFullyEncoded(
IntegerVariable(i))) {
++num_fully_encoded_variables;
{
int num_fully_encoded_variables = 0;
for (int i = 0; i < m.model.Get<IntegerTrail>()->NumIntegerVariables();
++i) {
if (m.model.Get<IntegerEncoder>()->VariableIsFullyEncoded(
IntegerVariable(i))) {
++num_fully_encoded_variables;
}
}
// We divide by two because of the automatically created NegationOf() var.
FZLOG << "Num integer variables = "
<< m.model.GetOrCreate<IntegerTrail>()->NumIntegerVariables() / 2
<< FZENDL;
FZLOG << "Num fully encoded variable = " << num_fully_encoded_variables / 2
<< FZENDL;
FZLOG << "Num initial SAT variables = "
<< m.model.Get<SatSolver>()->NumVariables() << " ("
<< m.model.Get<SatSolver>()->LiteralTrail().Index() << " fixed)."
<< FZENDL;
FZLOG << "Num constants = " << m.constant_map.size() << FZENDL;
FZLOG << "Num integer propagators = "
<< m.model.GetOrCreate<GenericLiteralWatcher>()->NumPropagators()
<< FZENDL;
}
// We divide by two because of the automatically created NegationOf() var.
FZLOG << "Num integer variables = "
<< m.model.Get<IntegerTrail>()->NumIntegerVariables() / 2 << FZENDL;
FZLOG << "Num fully encoded variable = " << num_fully_encoded_variables / 2
<< FZENDL;
FZLOG << "Num Boolean variables created = "
<< m.model.Get<SatSolver>()->NumVariables() << FZENDL;
FZLOG << "Num constants = " << m.constant_map.size() << FZENDL;
int num_solutions = 0;
int64 best_objective = 0;

View File

@@ -660,6 +660,9 @@ class GenericLiteralWatcher : public SatPropagator {
// is usually done in a CP solver at the cost of a sligthly more complex API.
void RegisterReversibleInt(int id, int* rev);
// Returns the number of registered propagators.
int NumPropagators() const { return in_queue_.size(); }
private:
// Updates queue_ and in_queue_ with the propagator ids that need to be
// called.

View File

@@ -27,6 +27,9 @@ IntegerSumLE::IntegerSumLE(LiteralIndex reified_literal,
coeffs_(coeffs),
trail_(trail),
integer_trail_(integer_trail) {
// TODO(user): deal with this corner case.
CHECK(!vars_.empty());
// Handle negative coefficients.
for (int i = 0; i < vars.size(); ++i) {
if (coeffs_[i] < 0) {
@@ -57,8 +60,6 @@ void IntegerSumLE::FillIntegerReason() {
}
bool IntegerSumLE::Propagate() {
CHECK(!vars_.empty()); // TODO(user): deal with this corner case.
// Reified case: If the reified literal is false, we ignore the constraint.
if (reified_literal_ != kNoLiteralIndex &&
trail_->Assignment().LiteralIsFalse(Literal(reified_literal_))) {
@@ -71,7 +72,8 @@ bool IntegerSumLE::Propagate() {
}
// Conflict?
if (new_lb > upper_bound_) {
const IntegerValue slack = upper_bound_ - new_lb;
if (slack < 0) {
FillIntegerReason();
// Reified case: If the reified literal is unassigned, we set it to false,
@@ -92,31 +94,34 @@ bool IntegerSumLE::Propagate() {
return true;
}
// Will only be filled on the first push.
integer_reason_.clear();
// The integer_reason_ will only be filled on the first push.
bool first_push = true;
// The lower bound of all the variables minus one can be used to update the
// upper bound of the last one.
for (int i = 0; i < vars_.size(); ++i) {
const IntegerValue new_lb_excluding_i =
new_lb - integer_trail_->LowerBound(vars_[i]) * coeffs_[i];
const IntegerValue new_term_ub = upper_bound_ - new_lb_excluding_i;
const IntegerValue new_ub = new_term_ub / coeffs_[i];
if (new_ub < integer_trail_->UpperBound(vars_[i])) {
if (integer_reason_.empty()) FillIntegerReason();
const IntegerVariable var = vars_[i];
const IntegerValue var_slack =
integer_trail_->UpperBound(var) - integer_trail_->LowerBound(var);
if (var_slack * coeffs_[i] > slack) {
if (first_push) {
first_push = false;
FillIntegerReason();
}
// We need to remove the entry index from the reason temporarily.
IntegerLiteral saved;
const int index = index_in_integer_reason_[i];
if (index >= 0) {
saved = integer_reason_[index];
std::swap(integer_reason_[index], integer_reason_.back());
integer_reason_[index] = integer_reason_.back();
integer_reason_.pop_back();
}
if (!integer_trail_->Enqueue(
IntegerLiteral::LowerOrEqual(vars_[i], new_ub), literal_reason_,
integer_reason_)) {
const IntegerValue new_ub =
integer_trail_->LowerBound(var) + slack / coeffs_[i];
if (!integer_trail_->Enqueue(IntegerLiteral::LowerOrEqual(var, new_ub),
literal_reason_, integer_reason_)) {
return false;
}

View File

@@ -296,6 +296,7 @@ inline std::function<void(Model*)> WeightedSumGreaterOrEqualReif(
}
// Weighted sum == constant reified.
// TODO(user): Simplify if the constant is at the edge of the possible values.
template <typename VectorInt>
inline std::function<void(Model*)> FixedWeightedSumReif(
Literal is_eq, const std::vector<IntegerVariable>& vars,
@@ -312,6 +313,7 @@ inline std::function<void(Model*)> FixedWeightedSumReif(
}
// Weighted sum != constant.
// TODO(user): Simplify if the constant is at the edge of the possible values.
template <typename VectorInt>
inline std::function<void(Model*)> WeightedSumNotEqual(
const std::vector<IntegerVariable>& vars, const VectorInt& coefficients,