22 #include "absl/strings/match.h"
23 #include "absl/strings/str_format.h"
24 #include "absl/strings/str_join.h"
25 #include "absl/strings/string_view.h"
32 "Interpret floats as integers in all variables and constraints.");
37 enum PresolveState { ALWAYS_FALSE, ALWAYS_TRUE, UNDECIDED };
41 for (
int i = 0; i < values.size(); ++i) {
42 if (values[i] != 0 && values[i] != 1) {
50 bool AtMostOne0OrAtMostOne1(
const std::vector<T>& values) {
54 for (T val : values) {
68 void AppendIfNotInSet(T*
value, absl::flat_hash_set<T*>* s,
69 std::vector<T*>* vec) {
70 if (s->insert(
value).second) {
71 vec->push_back(
value);
73 DCHECK_EQ(s->size(), vec->size());
98 void Presolver::PresolveBool2Int(Constraint*
ct) {
99 DCHECK_EQ(
ct->type,
"bool2int");
100 if (
ct->arguments[0].HasOneValue() ||
ct->arguments[1].HasOneValue()) {
102 UpdateRuleStats(
"bool2int: rename to int_eq");
106 UpdateRuleStats(
"bool2int: merge boolean and integer variables.");
107 AddVariableSubstitution(
ct->arguments[1].Var(),
ct->arguments[0].Var());
108 ct->MarkAsInactive();
117 void Presolver::PresolveStoreAffineMapping(Constraint*
ct) {
118 CHECK_EQ(2,
ct->arguments[1].variables.size());
119 Variable*
const var0 =
ct->arguments[1].variables[0];
120 Variable*
const var1 =
ct->arguments[1].variables[1];
121 const int64_t coeff0 =
ct->arguments[0].values[0];
122 const int64_t coeff1 =
ct->arguments[0].values[1];
123 const int64_t rhs =
ct->arguments[2].Value();
124 if (coeff0 == -1 && !affine_map_.contains(var0)) {
125 affine_map_[var0] = AffineMapping(var1, coeff0, -rhs,
ct);
126 UpdateRuleStats(
"int_lin_eq: store affine mapping");
127 }
else if (coeff1 == -1 && !affine_map_.contains(var1)) {
128 affine_map_[var1] = AffineMapping(var0, coeff0, -rhs,
ct);
129 UpdateRuleStats(
"int_lin_eq: store affine mapping");
133 void Presolver::PresolveStoreFlatteningMapping(Constraint*
ct) {
134 CHECK_EQ(3,
ct->arguments[1].variables.size());
135 Variable*
const var0 =
ct->arguments[1].variables[0];
136 Variable*
const var1 =
ct->arguments[1].variables[1];
137 Variable*
const var2 =
ct->arguments[1].variables[2];
138 const int64_t coeff0 =
ct->arguments[0].values[0];
139 const int64_t coeff1 =
ct->arguments[0].values[1];
140 const int64_t coeff2 =
ct->arguments[0].values[2];
141 const int64_t rhs =
ct->arguments[2].Value();
142 if (coeff0 == -1 && coeff2 == 1 && !array2d_index_map_.contains(var0)) {
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 &&
147 !array2d_index_map_.contains(var0)) {
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 &&
152 !array2d_index_map_.contains(var2)) {
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 &&
157 !array2d_index_map_.contains(var2)) {
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();
221 if (affine_map_.contains(index_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;
277 if (array2d_index_map_.contains(index_var)) {
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();
331 if (affine_map_.contains(index_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;
498 const auto& it = var_representative_map_.find(
var);
499 Variable* parent = it == var_representative_map_.end() ?
var : it->second;
500 if (parent ==
var)
break;
504 while (start_var !=
var) {
505 Variable*
const parent = var_representative_map_[start_var];
506 var_representative_map_[start_var] =
var;
509 const auto& iter = var_representative_map_.find(
var);
510 return iter == var_representative_map_.end() ?
var : iter->second;
513 void Presolver::SubstituteEverywhere(Model*
model) {
515 for (Constraint*
const ct :
model->constraints()) {
516 if (
ct !=
nullptr &&
ct->active) {
517 for (
int i = 0; i <
ct->arguments.size(); ++i) {
518 Argument& argument =
ct->arguments[i];
519 switch (argument.type) {
522 for (
int i = 0; i < argument.variables.size(); ++i) {
523 Variable*
const old_var = argument.variables[i];
524 Variable*
const new_var = FindRepresentativeOfVar(old_var);
525 if (new_var != old_var) {
526 argument.variables[i] = new_var;
538 for (Annotation*
const ann :
model->mutable_search_annotations()) {
539 SubstituteAnnotation(ann);
542 for (SolutionOutputSpecs*
const output :
model->mutable_output()) {
543 output->variable = FindRepresentativeOfVar(output->variable);
544 for (
int i = 0; i < output->flat_variables.size(); ++i) {
545 output->flat_variables[i] =
546 FindRepresentativeOfVar(output->flat_variables[i]);
551 for (
const auto& iter : var_representative_map_) {
552 iter.second->domain.IntersectWithDomain(iter.first->domain);
556 Variable*
const current_objective =
model->objective();
557 if (current_objective ==
nullptr)
return;
558 Variable*
const new_objective = FindRepresentativeOfVar(current_objective);
559 if (new_objective != current_objective) {
560 model->SetObjective(new_objective);
564 void Presolver::SubstituteAnnotation(Annotation* ann) {
569 for (
int i = 0; i < ann->annotations.size(); ++i) {
570 SubstituteAnnotation(&ann->annotations[i]);
576 for (
int i = 0; i < ann->variables.size(); ++i) {
577 ann->variables[i] = FindRepresentativeOfVar(ann->variables[i]);
Collection of objects used to extend the Constraint Solver library.
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 VarRef(Variable *const var)
static Argument IntegerList(std::vector< int64_t > values)
static Argument VarRefArray(std::vector< Variable * > vars)
static Argument IntegerValue(int64_t value)
bool Merge(absl::string_view other_name, const Domain &other_domain, bool other_temporary)
#define SOLVER_LOG(logger,...)