Skip to content

Commit

Permalink
fix #4392
Browse files Browse the repository at this point in the history
  • Loading branch information
lperron committed Oct 4, 2024
1 parent d5d6520 commit eb3684f
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 169 deletions.
151 changes: 2 additions & 149 deletions ortools/flatzinc/presolve.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,6 @@ void Presolver::PresolveInt2Float(Constraint* ct) {
ct->MarkAsInactive();
}

// 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.
// This mapping can involve 1 or 2 variables depending if y or z in A[y][z]
// is a constant in the model).
void Presolver::PresolveStoreAffineMapping(Constraint* ct) {
CHECK_EQ(2, ct->arguments[1].variables.size());
Variable* const var0 = ct->arguments[1].variables[0];
Variable* const var1 = ct->arguments[1].variables[1];
const int64_t coeff0 = ct->arguments[0].values[0];
const int64_t coeff1 = ct->arguments[0].values[1];
const int64_t rhs = ct->arguments[2].Value();
if (coeff0 == -1 && !affine_map_.contains(var0)) {
affine_map_[var0] = AffineMapping(var1, coeff0, -rhs, ct);
UpdateRuleStats("int_lin_eq: store affine mapping");
} else if (coeff1 == -1 && !affine_map_.contains(var1)) {
affine_map_[var1] = AffineMapping(var0, coeff0, -rhs, ct);
UpdateRuleStats("int_lin_eq: store affine mapping");
}
}

void Presolver::PresolveStoreFlatteningMapping(Constraint* ct) {
CHECK_EQ(3, ct->arguments[1].variables.size());
Variable* const var0 = ct->arguments[1].variables[0];
Expand Down Expand Up @@ -210,82 +189,18 @@ bool IsStrictPrefix(const std::vector<T>& v1, const std::vector<T>& v2) {
// Rewrite array element: array_int_element:
//
// Rule 1:
// Input : array_int_element(x0, [c1, .., cn], y) with x0 = a * x + b
// Output: array_int_element(x, [c_a1, .., c_am], b) with a * i = b = ai
//
// Rule 2:
// Input : array_int_element(x, [c1, .., cn], y) with x = a * x1 + x2 + b
// Output: array_int_element([x1, x2], [c_a1, .., c_am], b, [a, b])
// to be interpreted by the extraction process.
//
// Rule 3:
// Input: array_int_element(x, [c1, .., cn], y)
// Output array_int_element(x, [c1, .., c{max(x)}], y)
//
// Rule 4:
// Rule 2:
// Input : array_int_element(x, [c1, .., cn], y) with x0 ci = c0 + i
// Output: int_lin_eq([-1, 1], [y, x], 1 - c) (e.g. y = x + c - 1)
void Presolver::PresolveSimplifyElement(Constraint* ct) {
if (ct->arguments[0].variables.size() != 1) return;
Variable* const index_var = ct->arguments[0].Var();

// Rule 1.
if (affine_map_.contains(index_var)) {
const AffineMapping& mapping = affine_map_[index_var];
const Domain& domain = mapping.variable->domain;
if (domain.is_interval && domain.values.empty()) {
// Invalid case. Ignore it.
return;
}
if (domain.values[0] == 0 && mapping.coefficient == 1 &&
mapping.offset > 1 && index_var->domain.is_interval) {
// Simple translation
const int offset = mapping.offset - 1;
const int size = ct->arguments[1].values.size();
for (int i = 0; i < size - offset; ++i) {
ct->arguments[1].values[i] = ct->arguments[1].values[i + offset];
}
ct->arguments[1].values.resize(size - offset);
affine_map_[index_var].constraint->arguments[2].values[0] = -1;
affine_map_[index_var].offset = 1;
index_var->domain.values[0] -= offset;
index_var->domain.values[1] -= offset;
UpdateRuleStats("array_int_element: simplify using affine mapping.");
return;
} else if (mapping.offset + mapping.coefficient > 0 &&
domain.values[0] > 0) {
const std::vector<int64_t>& values = ct->arguments[1].values;
std::vector<int64_t> new_values;
for (int64_t i = 1; i <= domain.values.back(); ++i) {
const int64_t index = i * mapping.coefficient + mapping.offset - 1;
if (index < 0) {
return;
}
if (index > values.size()) {
break;
}
new_values.push_back(values[index]);
}
// Rewrite constraint.
UpdateRuleStats("array_int_element: simplify using affine mapping.");
ct->arguments[0].variables[0] = mapping.variable;
ct->arguments[0].variables[0]->domain.IntersectWithInterval(
1, new_values.size());
// TODO(user): Encapsulate argument setters.
ct->arguments[1].values.swap(new_values);
if (ct->arguments[1].values.size() == 1) {
ct->arguments[1].type = Argument::INT_VALUE;
}
// Reset propagate flag.
ct->presolve_propagation_done = false;
// Mark old index var and affine constraint as presolved out.
mapping.constraint->MarkAsInactive();
index_var->active = false;
return;
}
}

// Rule 2.
if (array2d_index_map_.contains(index_var)) {
UpdateRuleStats("array_int_element: rewrite as a 2d element");
const Array2DIndexMapping& mapping = array2d_index_map_[index_var];
Expand All @@ -302,16 +217,7 @@ void Presolver::PresolveSimplifyElement(Constraint* ct) {
return;
}

// Rule 3.
if (index_var->domain.Max() < ct->arguments[1].values.size()) {
// Reduce array of values.
ct->arguments[1].values.resize(index_var->domain.Max());
ct->presolve_propagation_done = false;
UpdateRuleStats("array_int_element: reduce array");
return;
}

// Rule 4.
// Rule 2.
if (IsIncreasingAndContiguous(ct->arguments[1].values) &&
ct->arguments[2].type == Argument::VAR_REF) {
const int64_t start = ct->arguments[1].values.front();
Expand All @@ -332,51 +238,6 @@ void Presolver::PresolveSimplifyElement(Constraint* ct) {
}
}

// Simplifies array_var_int_element
//
// Input : array_var_int_element(x0, [x1, .., xn], y) with x0 = a * x + b
// Output: array_var_int_element(x, [x_a1, .., x_an], b) with a * i = b = ai
void Presolver::PresolveSimplifyExprElement(Constraint* ct) {
if (ct->arguments[0].variables.size() != 1) return;

Variable* const index_var = ct->arguments[0].Var();
if (affine_map_.contains(index_var)) {
const AffineMapping& mapping = affine_map_[index_var];
const Domain& domain = mapping.variable->domain;
if ((domain.is_interval && domain.values.empty()) ||
domain.values[0] != 1 || mapping.offset + mapping.coefficient <= 0) {
// Invalid case. Ignore it.
return;
}
const std::vector<Variable*>& vars = ct->arguments[1].variables;
std::vector<Variable*> new_vars;
for (int64_t i = domain.values.front(); i <= domain.values.back(); ++i) {
const int64_t index = i * mapping.coefficient + mapping.offset - 1;
if (index < 0) {
return;
}
if (index >= vars.size()) {
break;
}
new_vars.push_back(vars[index]);
}
// Rewrite constraint.
UpdateRuleStats("array_var_int_element: simplify using affine mapping.");
ct->arguments[0].variables[0] = mapping.variable;
// TODO(user): Encapsulate argument setters.
ct->arguments[1].variables.swap(new_vars);
// Mark old index var and affine constraint as presolved out.
mapping.constraint->MarkAsInactive();
index_var->active = false;
} else if (index_var->domain.is_interval &&
index_var->domain.values.size() == 2 &&
index_var->domain.Max() < ct->arguments[1].variables.size()) {
// Reduce array of variables.
ct->arguments[1].variables.resize(index_var->domain.Max());
UpdateRuleStats("array_var_int_element: reduce array");
}
}

void Presolver::Run(Model* model) {
// Should rewrite float constraints.
if (absl::GetFlag(FLAGS_fz_floats_are_ints)) {
Expand Down Expand Up @@ -441,10 +302,6 @@ void Presolver::Run(Model* model) {
PresolveBool2Int(ct);
} else if (ct->active && ct->type == "int2float") {
PresolveInt2Float(ct);
} else if (ct->active && ct->type == "int_lin_eq" &&
ct->arguments[1].variables.size() == 2 &&
ct->strong_propagation) {
PresolveStoreAffineMapping(ct);
} else if (ct->active && ct->type == "int_lin_eq" &&
ct->arguments[1].variables.size() == 3 &&
ct->strong_propagation) {
Expand All @@ -463,10 +320,6 @@ void Presolver::Run(Model* model) {
if (ct->type == "array_int_element" || ct->type == "array_bool_element") {
PresolveSimplifyElement(ct);
}
if (ct->type == "array_var_int_element" ||
ct->type == "array_var_bool_element") {
PresolveSimplifyExprElement(ct);
}
}

// Third pass: process objective with floating point coefficients.
Expand Down
20 changes: 0 additions & 20 deletions ortools/flatzinc/presolve.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,6 @@ class Presolver {
void Run(Model* model);

private:
// This struct stores the affine mapping of one variable:
// it represents new_var = var * coefficient + offset. It also stores the
// constraint that defines this mapping.
struct AffineMapping {
Variable* variable;
int64_t coefficient;
int64_t offset;
Constraint* constraint;

AffineMapping()
: variable(nullptr), coefficient(0), offset(0), constraint(nullptr) {}
AffineMapping(Variable* v, int64_t c, int64_t o, Constraint* ct)
: variable(v), coefficient(c), offset(o), constraint(ct) {}
};

// This struct stores the mapping of two index variables (of a 2D array; not
// included here) onto a single index variable (of the flattened 1D array).
// The original 2D array could be trimmed in the process; so we also need an
Expand Down Expand Up @@ -96,10 +81,8 @@ class Presolver {
// Presolve rules.
void PresolveBool2Int(Constraint* ct);
void PresolveInt2Float(Constraint* ct);
void PresolveStoreAffineMapping(Constraint* ct);
void PresolveStoreFlatteningMapping(Constraint* ct);
void PresolveSimplifyElement(Constraint* ct);
void PresolveSimplifyExprElement(Constraint* ct);

// Helpers.
void UpdateRuleStats(const std::string& rule_name) {
Expand All @@ -117,9 +100,6 @@ class Presolver {
absl::flat_hash_map<const Variable*, Variable*> var_representative_map_;
std::vector<Variable*> var_representative_vector_;

// Stores affine_map_[x] = a * y + b.
absl::flat_hash_map<const Variable*, AffineMapping> affine_map_;

// Stores array2d_index_map_[z] = a * x + y + b.
absl::flat_hash_map<const Variable*, Array2DIndexMapping> array2d_index_map_;

Expand Down

0 comments on commit eb3684f

Please sign in to comment.