22#include "absl/container/flat_hash_set.h"
23#include "absl/flags/flag.h"
24#include "absl/log/check.h"
25#include "absl/strings/string_view.h"
26#include "absl/types/span.h"
31 "Interpret floats as integers in all variables and constraints.");
36enum PresolveState { ALWAYS_FALSE, ALWAYS_TRUE, UNDECIDED };
40 for (
int i = 0;
i < values.size(); ++
i) {
41 if (values[i] != 0 && values[i] != 1) {
49bool AtMostOne0OrAtMostOne1(
const std::vector<T>& values) {
53 for (T val : values) {
67void 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);
72 DCHECK_EQ(s->size(), vec->size());
97void Presolver::PresolveBool2Int(Constraint*
ct) {
98 DCHECK_EQ(
ct->type,
"bool2int");
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();
116void Presolver::PresolveInt2Float(Constraint*
ct) {
117 DCHECK_EQ(
ct->type,
"int2float");
119 UpdateRuleStats(
"int2float: merge integer and floating point variables.");
120 AddVariableSubstitution(
ct->arguments[1].Var(),
ct->arguments[0].Var());
121 ct->MarkAsInactive();
129void Presolver::PresolveStoreAffineMapping(Constraint*
ct) {
130 CHECK_EQ(2,
ct->arguments[1].variables.size());
131 Variable*
const var0 =
ct->arguments[1].variables[0];
132 Variable*
const var1 =
ct->arguments[1].variables[1];
133 const int64_t coeff0 =
ct->arguments[0].values[0];
134 const int64_t coeff1 =
ct->arguments[0].values[1];
135 const int64_t rhs =
ct->arguments[2].Value();
136 if (coeff0 == -1 && !affine_map_.contains(var0)) {
137 affine_map_[var0] = AffineMapping(var1, coeff0, -rhs,
ct);
138 UpdateRuleStats(
"int_lin_eq: store affine mapping");
139 }
else if (coeff1 == -1 && !affine_map_.contains(var1)) {
140 affine_map_[var1] = AffineMapping(var0, coeff0, -rhs,
ct);
141 UpdateRuleStats(
"int_lin_eq: store affine mapping");
145void Presolver::PresolveStoreFlatteningMapping(Constraint*
ct) {
146 CHECK_EQ(3,
ct->arguments[1].variables.size());
147 Variable*
const var0 =
ct->arguments[1].variables[0];
148 Variable*
const var1 =
ct->arguments[1].variables[1];
149 Variable*
const var2 =
ct->arguments[1].variables[2];
150 const int64_t coeff0 =
ct->arguments[0].values[0];
151 const int64_t coeff1 =
ct->arguments[0].values[1];
152 const int64_t coeff2 =
ct->arguments[0].values[2];
153 const int64_t rhs =
ct->arguments[2].Value();
154 if (coeff0 == -1 && coeff2 == 1 && !array2d_index_map_.contains(var0)) {
155 array2d_index_map_[var0] =
156 Array2DIndexMapping(var1, coeff1, var2, -rhs,
ct);
157 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
158 }
else if (coeff0 == -1 && coeff1 == 1 &&
159 !array2d_index_map_.contains(var0)) {
160 array2d_index_map_[var0] =
161 Array2DIndexMapping(var2, coeff2, var1, -rhs,
ct);
162 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
163 }
else if (coeff2 == -1 && coeff1 == 1 &&
164 !array2d_index_map_.contains(var2)) {
165 array2d_index_map_[var2] =
166 Array2DIndexMapping(var0, coeff0, var1, -rhs,
ct);
167 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
168 }
else if (coeff2 == -1 && coeff0 == 1 &&
169 !array2d_index_map_.contains(var2)) {
170 array2d_index_map_[var2] =
171 Array2DIndexMapping(var1, coeff1, var0, -rhs,
ct);
172 UpdateRuleStats(
"int_lin_eq: store 2d flattening mapping");
177bool IsIncreasingAndContiguous(absl::Span<const int64_t> values) {
178 for (
int i = 0;
i < values.size() - 1; ++
i) {
179 if (values[i + 1] != values[i] + 1) {
186bool AreOnesFollowedByMinusOne(
const std::vector<int64_t>& coeffs) {
187 CHECK(!coeffs.empty());
188 for (
int i = 0;
i < coeffs.size() - 1; ++
i) {
189 if (coeffs[i] != 1) {
193 return coeffs.back() == -1;
197bool IsStrictPrefix(
const std::vector<T>& v1,
const std::vector<T>& v2) {
198 if (v1.size() >= v2.size()) {
201 for (
int i = 0;
i < v1.size(); ++
i) {
202 if (v1[i] != v2[i]) {
228void Presolver::PresolveSimplifyElement(Constraint*
ct) {
229 if (
ct->arguments[0].variables.size() != 1)
return;
230 Variable*
const index_var =
ct->arguments[0].Var();
233 if (affine_map_.contains(index_var)) {
234 const AffineMapping& mapping = affine_map_[index_var];
235 const Domain& domain = mapping.variable->domain;
236 if (domain.is_interval && domain.values.empty()) {
240 if (domain.values[0] == 0 && mapping.coefficient == 1 &&
241 mapping.offset > 1 && index_var->domain.is_interval) {
243 const int offset = mapping.offset - 1;
244 const int size =
ct->arguments[1].values.size();
245 for (
int i = 0;
i <
size - offset; ++
i) {
246 ct->arguments[1].values[
i] =
ct->arguments[1].values[
i + offset];
248 ct->arguments[1].values.resize(
size - offset);
249 affine_map_[index_var].constraint->arguments[2].values[0] = -1;
250 affine_map_[index_var].offset = 1;
251 index_var->domain.values[0] -= offset;
252 index_var->domain.values[1] -= offset;
253 UpdateRuleStats(
"array_int_element: simplify using affine mapping.");
255 }
else if (mapping.offset + mapping.coefficient > 0 &&
256 domain.values[0] > 0) {
257 const std::vector<int64_t>& values =
ct->arguments[1].values;
258 std::vector<int64_t> new_values;
259 for (int64_t i = 1;
i <= domain.values.back(); ++
i) {
260 const int64_t
index =
i * mapping.coefficient + mapping.offset - 1;
264 if (
index > values.size()) {
267 new_values.push_back(values[
index]);
270 UpdateRuleStats(
"array_int_element: simplify using affine mapping.");
271 ct->arguments[0].variables[0] = mapping.variable;
272 ct->arguments[0].variables[0]->domain.IntersectWithInterval(
273 1, new_values.size());
275 ct->arguments[1].values.swap(new_values);
276 if (
ct->arguments[1].values.size() == 1) {
280 ct->presolve_propagation_done =
false;
282 mapping.constraint->MarkAsInactive();
283 index_var->active =
false;
289 if (array2d_index_map_.contains(index_var)) {
290 UpdateRuleStats(
"array_int_element: rewrite as a 2d element");
291 const Array2DIndexMapping& mapping = array2d_index_map_[index_var];
295 std::vector<int64_t> coefs;
296 coefs.push_back(mapping.coefficient);
300 index_var->active =
false;
301 mapping.constraint->MarkAsInactive();
306 if (index_var->domain.Max() <
ct->arguments[1].values.size()) {
308 ct->arguments[1].values.resize(index_var->domain.Max());
309 ct->presolve_propagation_done =
false;
310 UpdateRuleStats(
"array_int_element: reduce array");
315 if (IsIncreasingAndContiguous(
ct->arguments[1].values) &&
317 const int64_t
start =
ct->arguments[1].values.front();
318 Variable*
const index =
ct->arguments[0].Var();
319 Variable*
const target =
ct->arguments[2].Var();
320 UpdateRuleStats(
"array_int_element: rewrite as a linear constraint");
327 ct->type =
"int_lin_eq";
339void Presolver::PresolveSimplifyExprElement(Constraint*
ct) {
340 if (
ct->arguments[0].variables.size() != 1)
return;
342 Variable*
const index_var =
ct->arguments[0].Var();
343 if (affine_map_.contains(index_var)) {
344 const AffineMapping& mapping = affine_map_[index_var];
345 const Domain& domain = mapping.variable->domain;
346 if ((domain.is_interval && domain.values.empty()) ||
347 domain.values[0] != 1 || mapping.offset + mapping.coefficient <= 0) {
351 const std::vector<Variable*>& vars =
ct->arguments[1].variables;
352 std::vector<Variable*> new_vars;
353 for (int64_t i = domain.values.front(); i <= domain.values.back(); ++i) {
354 const int64_t
index =
i * mapping.coefficient + mapping.offset - 1;
358 if (
index >= vars.size()) {
361 new_vars.push_back(vars[
index]);
364 UpdateRuleStats(
"array_var_int_element: simplify using affine mapping.");
365 ct->arguments[0].variables[0] = mapping.variable;
367 ct->arguments[1].variables.swap(new_vars);
369 mapping.constraint->MarkAsInactive();
370 index_var->active =
false;
371 }
else if (index_var->domain.is_interval &&
372 index_var->domain.values.size() == 2 &&
373 index_var->domain.Max() <
ct->arguments[1].variables.size()) {
375 ct->arguments[1].variables.resize(index_var->domain.Max());
376 UpdateRuleStats(
"array_var_int_element: reduce array");
382 if (absl::GetFlag(FLAGS_fz_floats_are_ints)) {
385 const std::string&
id =
ct->type;
386 if (
id ==
"int2float") {
388 }
else if (
id ==
"float_lin_le") {
389 ct->type =
"int_lin_le";
390 }
else if (
id ==
"float_lin_eq") {
391 ct->type =
"int_lin_eq";
398 std::vector<Variable*> current_variables;
399 Variable* target_variable =
nullptr;
402 if (target_variable ==
nullptr) {
403 if (
ct->type ==
"int_lin_eq" &&
ct->arguments[0].values.size() == 3 &&
404 AreOnesFollowedByMinusOne(
ct->arguments[0].values) &&
405 ct->arguments[1].values.empty() &&
ct->arguments[2].Value() == 0) {
406 current_variables =
ct->arguments[1].variables;
407 target_variable = current_variables.back();
408 current_variables.pop_back();
409 first_constraint =
ct;
412 if (
ct->type ==
"int_lin_eq" &&
413 AreOnesFollowedByMinusOne(
ct->arguments[0].values) &&
414 ct->arguments[0].values.size() == current_variables.size() + 2 &&
415 IsStrictPrefix(current_variables,
ct->arguments[1].variables)) {
416 current_variables =
ct->arguments[1].variables;
418 ct->type =
"int_plus";
419 ct->arguments.clear();
421 ct->arguments.push_back(
424 target_variable = current_variables.back();
425 current_variables.pop_back();
428 if (first_constraint !=
nullptr) {
429 first_constraint =
nullptr;
432 current_variables.clear();
433 target_variable =
nullptr;
440 if (
ct->active &&
ct->type ==
"bool2int") {
441 PresolveBool2Int(
ct);
442 }
else if (
ct->active &&
ct->type ==
"int2float") {
443 PresolveInt2Float(
ct);
444 }
else if (
ct->active &&
ct->type ==
"int_lin_eq" &&
445 ct->arguments[1].variables.size() == 2 &&
446 ct->strong_propagation) {
447 PresolveStoreAffineMapping(
ct);
448 }
else if (
ct->active &&
ct->type ==
"int_lin_eq" &&
449 ct->arguments[1].variables.size() == 3 &&
450 ct->strong_propagation) {
451 PresolveStoreFlatteningMapping(
ct);
454 if (!var_representative_map_.empty()) {
456 SubstituteEverywhere(
model);
457 var_representative_map_.clear();
458 var_representative_vector_.clear();
463 if (
ct->type ==
"array_int_element" ||
ct->type ==
"array_bool_element") {
464 PresolveSimplifyElement(
ct);
466 if (
ct->type ==
"array_var_int_element" ||
467 ct->type ==
"array_var_bool_element") {
468 PresolveSimplifyExprElement(
ct);
473 Variable* float_objective_var =
nullptr;
475 if (!
var->active)
continue;
476 if (
var->domain.is_float) {
477 CHECK(float_objective_var ==
nullptr);
478 float_objective_var =
var;
483 if (float_objective_var !=
nullptr) {
485 if (!
ct->active)
continue;
486 if (
ct->type ==
"float_lin_eq") {
487 CHECK(float_objective_ct ==
nullptr);
488 float_objective_ct =
ct;
494 if (float_objective_ct !=
nullptr || float_objective_var !=
nullptr) {
495 CHECK(float_objective_ct !=
nullptr);
496 CHECK(float_objective_var !=
nullptr);
497 const int arity = float_objective_ct->
arguments[0].Size();
498 CHECK_EQ(float_objective_ct->
arguments[1].variables[arity - 1],
499 float_objective_var);
500 CHECK_EQ(float_objective_ct->
arguments[0].floats[arity - 1], -1.0);
501 for (
int i = 0; i + 1 < arity; ++i) {
502 model->AddFloatingPointObjectiveTerm(
503 float_objective_ct->
arguments[1].variables[i],
504 float_objective_ct->
arguments[0].floats[i]);
506 model->SetFloatingPointObjectiveOffset(
507 -float_objective_ct->
arguments[2].floats[0]);
508 model->ClearObjective();
509 float_objective_var->
active =
false;
510 float_objective_ct->
active =
false;
514 if (!successful_rules_.empty()) {
515 for (
const auto& rule : successful_rules_) {
516 if (rule.second == 1) {
517 SOLVER_LOG(logger_,
" - rule '", rule.first,
"' was applied 1 time");
519 SOLVER_LOG(logger_,
" - rule '", rule.first,
"' was applied ",
520 rule.second,
" times");
529 CHECK(from !=
nullptr);
530 CHECK(
to !=
nullptr);
532 from = FindRepresentativeOfVar(from);
533 to = FindRepresentativeOfVar(
to);
543 var_representative_map_[from] =
to;
544 var_representative_vector_.push_back(from);
548Variable* Presolver::FindRepresentativeOfVar(Variable*
var) {
549 if (
var ==
nullptr)
return nullptr;
550 Variable* start_var =
var;
553 const auto& it = var_representative_map_.find(
var);
554 Variable* parent = it == var_representative_map_.end() ?
var : it->second;
555 if (parent ==
var)
break;
559 while (start_var !=
var) {
560 Variable*
const parent = var_representative_map_[start_var];
561 var_representative_map_[start_var] =
var;
564 const auto& iter = var_representative_map_.find(
var);
565 return iter == var_representative_map_.end() ?
var : iter->second;
568void Presolver::SubstituteEverywhere(Model*
model) {
570 for (Constraint*
const ct :
model->constraints()) {
571 if (
ct !=
nullptr &&
ct->active) {
572 for (
int i = 0;
i <
ct->arguments.size(); ++
i) {
573 Argument& argument =
ct->arguments[
i];
574 switch (argument.type) {
577 for (
int i = 0;
i < argument.variables.size(); ++
i) {
578 Variable*
const old_var = argument.variables[
i];
579 Variable*
const new_var = FindRepresentativeOfVar(old_var);
580 if (new_var != old_var) {
581 argument.variables[
i] = new_var;
593 for (Annotation*
const ann :
model->mutable_search_annotations()) {
594 SubstituteAnnotation(ann);
597 for (SolutionOutputSpecs*
const output :
model->mutable_output()) {
598 output->variable = FindRepresentativeOfVar(output->variable);
599 for (
int i = 0;
i < output->flat_variables.size(); ++
i) {
600 output->flat_variables[
i] =
601 FindRepresentativeOfVar(output->flat_variables[i]);
606 for (
const auto& iter : var_representative_map_) {
607 iter.second->domain.IntersectWithDomain(iter.first->domain);
611 Variable*
const current_objective =
model->objective();
612 if (current_objective ==
nullptr)
return;
613 Variable*
const new_objective = FindRepresentativeOfVar(current_objective);
614 if (new_objective != current_objective) {
615 model->SetObjective(new_objective);
619void Presolver::SubstituteAnnotation(Annotation* ann) {
624 for (
int i = 0;
i < ann->annotations.size(); ++
i) {
625 SubstituteAnnotation(&ann->annotations[i]);
631 for (
int i = 0;
i < ann->variables.size(); ++
i) {
632 ann->variables[
i] = FindRepresentativeOfVar(ann->variables[i]);
In SWIG mode, we don't want anything besides these top-level includes.
bool IsArrayBoolean(const std::vector< T > &values)
trees with all degrees equal to
ABSL_FLAG(bool, fz_floats_are_ints, false, "Interpret floats as integers in all variables and constraints.")
static Argument IntegerValue(int64_t value)
--— Argument --—
static Argument IntegerList(std::vector< int64_t > values)
static Argument VarRef(Variable *var)
static Argument VarRefArray(std::vector< Variable * > vars)
std::vector< Argument > arguments
#define SOLVER_LOG(logger,...)