20 #include "absl/strings/match.h" 21 #include "absl/strings/str_format.h" 22 #include "absl/strings/str_join.h" 23 #include "absl/strings/string_view.h" 30 ABSL_FLAG(
bool, fz_floats_are_ints,
false,
31 "Interpret floats as integers in all variables and constraints.");
40 for (
int i = 0; i < values.size(); ++i) {
41 if (values[i] != 0 && values[i] != 1) {
49 bool AtMostOne0OrAtMostOne1(
const std::vector<T>& values) {
53 for (T val : values) {
59 if (num_one > 1 && num_zero > 1) {
67 void AppendIfNotInSet(T*
value, absl::flat_hash_set<T*>* s,
68 std::vector<T*>* vec) {
69 if (s->insert(
value).second) {
70 vec->push_back(
value);
97 void Presolver::PresolveBool2Int(Constraint*
ct) {
99 if (
ct->arguments[0].HasOneValue() ||
ct->arguments[1].HasOneValue()) {
101 UpdateRuleStats(
"bool2int: rename to int_eq");
105 UpdateRuleStats(
"bool2int: merge boolean and integer variables.");
106 AddVariableSubstitution(
ct->arguments[1].Var(),
ct->arguments[0].Var());
107 ct->MarkAsInactive();
116 void Presolver::PresolveStoreAffineMapping(Constraint*
ct) {
117 CHECK_EQ(2,
ct->arguments[1].variables.size());
118 Variable*
const var0 =
ct->arguments[1].variables[0];
119 Variable*
const var1 =
ct->arguments[1].variables[1];
120 const int64_t coeff0 =
ct->arguments[0].values[0];
121 const int64_t coeff1 =
ct->arguments[0].values[1];
122 const int64_t rhs =
ct->arguments[2].Value();
124 affine_map_[var0] = AffineMapping(var1, coeff0, -rhs,
ct);
125 UpdateRuleStats(
"int_lin_eq: store affine mapping");
127 affine_map_[var1] = AffineMapping(var0, coeff0, -rhs,
ct);
128 UpdateRuleStats(
"int_lin_eq: store affine mapping");
132 void Presolver::PresolveStoreFlatteningMapping(Constraint*
ct) {
133 CHECK_EQ(3,
ct->arguments[1].variables.size());
134 Variable*
const var0 =
ct->arguments[1].variables[0];
135 Variable*
const var1 =
ct->arguments[1].variables[1];
136 Variable*
const var2 =
ct->arguments[1].variables[2];
137 const int64_t coeff0 =
ct->arguments[0].values[0];
138 const int64_t coeff1 =
ct->arguments[0].values[1];
139 const int64_t coeff2 =
ct->arguments[0].values[2];
140 const int64_t rhs =
ct->arguments[2].Value();
141 if (coeff0 == -1 && coeff2 == 1 &&
143 array2d_index_map_[var0] =
144 Array2DIndexMapping(var1, coeff1, var2, -rhs,
ct);
145 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
146 }
else if (coeff0 == -1 && coeff1 == 1 &&
148 array2d_index_map_[var0] =
149 Array2DIndexMapping(var2, coeff2, var1, -rhs,
ct);
150 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
151 }
else if (coeff2 == -1 && coeff1 == 1 &&
153 array2d_index_map_[var2] =
154 Array2DIndexMapping(var0, coeff0, var1, -rhs,
ct);
155 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
156 }
else if (coeff2 == -1 && coeff0 == 1 &&
158 array2d_index_map_[var2] =
159 Array2DIndexMapping(var1, coeff1, var0, -rhs,
ct);
160 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
165 bool IsIncreasingAndContiguous(
const std::vector<int64_t>& values) {
166 for (
int i = 0; i < values.size() - 1; ++i) {
167 if (values[i + 1] != values[i] + 1) {
174 bool AreOnesFollowedByMinusOne(
const std::vector<int64_t>& coeffs) {
175 CHECK(!coeffs.empty());
176 for (
int i = 0; i < coeffs.size() - 1; ++i) {
177 if (coeffs[i] != 1) {
181 return coeffs.back() == -1;
185 bool IsStrictPrefix(
const std::vector<T>& v1,
const std::vector<T>& v2) {
186 if (v1.size() >= v2.size()) {
189 for (
int i = 0; i < v1.size(); ++i) {
190 if (v1[i] != v2[i]) {
216 void Presolver::PresolveSimplifyElement(Constraint*
ct) {
217 if (
ct->arguments[0].variables.size() != 1)
return;
218 Variable*
const index_var =
ct->arguments[0].Var();
222 const AffineMapping& mapping = affine_map_[index_var];
223 const Domain& domain = mapping.variable->domain;
224 if (domain.is_interval && domain.values.empty()) {
228 if (domain.values[0] == 0 && mapping.coefficient == 1 &&
229 mapping.offset > 1 && index_var->domain.is_interval) {
231 const int offset = mapping.offset - 1;
232 const int size =
ct->arguments[1].values.size();
233 for (
int i = 0; i < size - offset; ++i) {
234 ct->arguments[1].values[i] =
ct->arguments[1].values[i + offset];
236 ct->arguments[1].values.resize(size - offset);
237 affine_map_[index_var].constraint->arguments[2].values[0] = -1;
238 affine_map_[index_var].offset = 1;
239 index_var->domain.values[0] -= offset;
240 index_var->domain.values[1] -= offset;
241 UpdateRuleStats(
"array_int_element: simplify using affine mapping.");
243 }
else if (mapping.offset + mapping.coefficient > 0 &&
244 domain.values[0] > 0) {
245 const std::vector<int64_t>& values =
ct->arguments[1].values;
246 std::vector<int64_t> new_values;
247 for (int64_t i = 1; i <= domain.values.back(); ++i) {
248 const int64_t
index = i * mapping.coefficient + mapping.offset - 1;
252 if (
index > values.size()) {
255 new_values.push_back(values[
index]);
258 UpdateRuleStats(
"array_int_element: simplify using affine mapping.");
259 ct->arguments[0].variables[0] = mapping.variable;
260 ct->arguments[0].variables[0]->domain.IntersectWithInterval(
261 1, new_values.size());
263 ct->arguments[1].values.swap(new_values);
264 if (
ct->arguments[1].values.size() == 1) {
268 ct->presolve_propagation_done =
false;
270 mapping.constraint->MarkAsInactive();
271 index_var->active =
false;
278 UpdateRuleStats(
"array_int_element: rewrite as a 2d element");
279 const Array2DIndexMapping& mapping = array2d_index_map_[index_var];
283 std::vector<int64_t> coefs;
284 coefs.push_back(mapping.coefficient);
288 index_var->active =
false;
289 mapping.constraint->MarkAsInactive();
294 if (index_var->domain.Max() <
ct->arguments[1].values.size()) {
296 ct->arguments[1].values.resize(index_var->domain.Max());
297 ct->presolve_propagation_done =
false;
298 UpdateRuleStats(
"array_int_element: reduce array");
303 if (IsIncreasingAndContiguous(
ct->arguments[1].values) &&
305 const int64_t start =
ct->arguments[1].values.front();
306 Variable*
const index =
ct->arguments[0].Var();
307 Variable*
const target =
ct->arguments[2].Var();
308 UpdateRuleStats(
"array_int_element: rewrite as a linear constraint");
315 ct->type =
"int_lin_eq";
327 void Presolver::PresolveSimplifyExprElement(Constraint*
ct) {
328 if (
ct->arguments[0].variables.size() != 1)
return;
330 Variable*
const index_var =
ct->arguments[0].Var();
332 const AffineMapping& mapping = affine_map_[index_var];
333 const Domain& domain = mapping.variable->domain;
334 if ((domain.is_interval && domain.values.empty()) ||
335 domain.values[0] != 1 || mapping.offset + mapping.coefficient <= 0) {
339 const std::vector<Variable*>& vars =
ct->arguments[1].variables;
340 std::vector<Variable*> new_vars;
341 for (int64_t i = domain.values.front(); i <= domain.values.back(); ++i) {
342 const int64_t
index = i * mapping.coefficient + mapping.offset - 1;
346 if (
index >= vars.size()) {
349 new_vars.push_back(vars[
index]);
352 UpdateRuleStats(
"array_var_int_element: simplify using affine mapping.");
353 ct->arguments[0].variables[0] = mapping.variable;
355 ct->arguments[1].variables.swap(new_vars);
357 mapping.constraint->MarkAsInactive();
358 index_var->active =
false;
359 }
else if (index_var->domain.is_interval &&
360 index_var->domain.values.size() == 2 &&
361 index_var->domain.Max() <
ct->arguments[1].variables.size()) {
363 ct->arguments[1].variables.resize(index_var->domain.Max());
364 UpdateRuleStats(
"array_var_int_element: reduce array");
370 if (absl::GetFlag(FLAGS_fz_floats_are_ints)) {
373 const std::string&
id =
ct->type;
374 if (
id ==
"int2float") {
376 }
else if (
id ==
"float_lin_le") {
377 ct->type =
"int_lin_le";
378 }
else if (
id ==
"float_lin_eq") {
379 ct->type =
"int_lin_eq";
386 std::vector<Variable*> current_variables;
387 Variable* target_variable =
nullptr;
390 if (target_variable ==
nullptr) {
391 if (
ct->type ==
"int_lin_eq" &&
ct->arguments[0].values.size() == 3 &&
392 AreOnesFollowedByMinusOne(
ct->arguments[0].values) &&
393 ct->arguments[1].values.empty() &&
ct->arguments[2].Value() == 0) {
394 current_variables =
ct->arguments[1].variables;
395 target_variable = current_variables.back();
396 current_variables.pop_back();
397 first_constraint =
ct;
400 if (
ct->type ==
"int_lin_eq" &&
401 AreOnesFollowedByMinusOne(
ct->arguments[0].values) &&
402 ct->arguments[0].values.size() == current_variables.size() + 2 &&
403 IsStrictPrefix(current_variables,
ct->arguments[1].variables)) {
404 current_variables =
ct->arguments[1].variables;
406 ct->type =
"int_plus";
407 ct->arguments.clear();
409 ct->arguments.push_back(
412 target_variable = current_variables.back();
413 current_variables.pop_back();
416 if (first_constraint !=
nullptr) {
417 first_constraint =
nullptr;
420 current_variables.clear();
421 target_variable =
nullptr;
428 if (
ct->active &&
ct->type ==
"bool2int") {
429 PresolveBool2Int(
ct);
430 }
else if (
ct->active &&
ct->type ==
"int_lin_eq" &&
431 ct->arguments[1].variables.size() == 2 &&
432 ct->strong_propagation) {
433 PresolveStoreAffineMapping(
ct);
434 }
else if (
ct->active &&
ct->type ==
"int_lin_eq" &&
435 ct->arguments[1].variables.size() == 3 &&
436 ct->strong_propagation) {
437 PresolveStoreFlatteningMapping(
ct);
440 if (!var_representative_map_.empty()) {
442 SubstituteEverywhere(
model);
443 var_representative_map_.clear();
444 var_representative_vector_.clear();
449 if (
ct->type ==
"array_int_element" ||
ct->type ==
"array_bool_element") {
450 PresolveSimplifyElement(
ct);
452 if (
ct->type ==
"array_var_int_element" ||
453 ct->type ==
"array_var_bool_element") {
454 PresolveSimplifyExprElement(
ct);
459 if (!successful_rules_.empty()) {
460 for (
const auto& rule : successful_rules_) {
461 if (rule.second == 1) {
462 SOLVER_LOG(logger_,
" - rule '", rule.first,
"' was applied 1 time");
464 SOLVER_LOG(logger_,
" - rule '", rule.first,
"' was applied ",
465 rule.second,
" times");
474 CHECK(from !=
nullptr);
475 CHECK(to !=
nullptr);
477 from = FindRepresentativeOfVar(from);
478 to = FindRepresentativeOfVar(to);
488 var_representative_map_[from] = to;
489 var_representative_vector_.push_back(from);
493 Variable* Presolver::FindRepresentativeOfVar(Variable*
var) {
494 if (
var ==
nullptr)
return nullptr;
495 Variable* start_var =
var;
499 if (parent ==
var)
break;
503 while (start_var !=
var) {
504 Variable*
const parent = var_representative_map_[start_var];
505 var_representative_map_[start_var] =
var;
511 void Presolver::SubstituteEverywhere(Model*
model) {
513 for (Constraint*
const ct :
model->constraints()) {
514 if (
ct !=
nullptr &&
ct->active) {
515 for (
int i = 0; i <
ct->arguments.size(); ++i) {
516 Argument& argument =
ct->arguments[i];
517 switch (argument.type) {
520 for (
int i = 0; i < argument.variables.size(); ++i) {
521 Variable*
const old_var = argument.variables[i];
522 Variable*
const new_var = FindRepresentativeOfVar(old_var);
523 if (new_var != old_var) {
524 argument.variables[i] = new_var;
536 for (Annotation*
const ann :
model->mutable_search_annotations()) {
537 SubstituteAnnotation(ann);
540 for (SolutionOutputSpecs*
const output :
model->mutable_output()) {
541 output->variable = FindRepresentativeOfVar(output->variable);
542 for (
int i = 0; i < output->flat_variables.size(); ++i) {
543 output->flat_variables[i] =
544 FindRepresentativeOfVar(output->flat_variables[i]);
549 for (
const auto& iter : var_representative_map_) {
550 iter.second->domain.IntersectWithDomain(iter.first->domain);
554 Variable*
const current_objective =
model->objective();
555 if (current_objective ==
nullptr)
return;
556 Variable*
const new_objective = FindRepresentativeOfVar(current_objective);
557 if (new_objective != current_objective) {
558 model->SetObjective(new_objective);
562 void Presolver::SubstituteAnnotation(Annotation* ann) {
567 for (
int i = 0; i < ann->annotations.size(); ++i) {
568 SubstituteAnnotation(&ann->annotations[i]);
574 for (
int i = 0; i < ann->variables.size(); ++i) {
575 ann->variables[i] = FindRepresentativeOfVar(ann->variables[i]);
#define SOLVER_LOG(logger,...)
bool IsArrayBoolean(const std::vector< T > &values)
ABSL_FLAG(bool, fz_floats_are_ints, false, "Interpret floats as integers in all variables and constraints.")
static Argument IntegerValue(int64_t value)
bool ContainsKey(const Collection &collection, const Key &key)
bool Merge(const std::string &other_name, const Domain &other_domain, bool other_temporary)
#define CHECK_EQ(val1, val2)
static Argument VarRefArray(std::vector< Variable * > vars)
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
#define DCHECK_EQ(val1, val2)
static Argument VarRef(Variable *const var)
Collection of objects used to extend the Constraint Solver library.
static Argument IntegerList(std::vector< int64_t > values)