1 // Copyright 2010-2021 Google LLC 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 #include "ortools/sat/cp_model_presolve.h" 15 16 #include <sys/stat.h> 17 18 #include <algorithm> 19 #include <cstdint> 20 #include <cstdlib> 21 #include <deque> 22 #include <limits> 23 #include <map> 24 #include <memory> 25 #include <numeric> 26 #include <set> 27 #include <string> 28 #include <utility> 29 #include <vector> 30 31 #include "absl/base/attributes.h" 32 #include "absl/container/flat_hash_map.h" 33 #include "absl/container/flat_hash_set.h" 34 #include "absl/hash/hash.h" 35 #include "absl/random/random.h" 36 #include "absl/strings/str_join.h" 37 #include "ortools/base/integral_types.h" 38 #include "ortools/base/logging.h" 39 #include "ortools/base/map_util.h" 40 #include "ortools/base/mathutil.h" 41 #include "ortools/base/stl_util.h" 42 #include "ortools/port/proto_utils.h" 43 #include "ortools/sat/circuit.h" 44 #include "ortools/sat/cp_model.pb.h" 45 #include "ortools/sat/cp_model_checker.h" 46 #include "ortools/sat/cp_model_expand.h" 47 #include "ortools/sat/cp_model_loader.h" 48 #include "ortools/sat/cp_model_mapping.h" 49 #include "ortools/sat/cp_model_objective.h" 50 #include "ortools/sat/cp_model_symmetries.h" 51 #include "ortools/sat/cp_model_utils.h" 52 #include "ortools/sat/diffn_util.h" 53 #include "ortools/sat/presolve_util.h" 54 #include "ortools/sat/probing.h" 55 #include "ortools/sat/sat_base.h" 56 #include "ortools/sat/sat_parameters.pb.h" 57 #include "ortools/sat/simplification.h" 58 #include "ortools/sat/var_domination.h" 59 60 namespace operations_research { 61 namespace sat { 62 63 bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) { 64 ct->Clear(); 65 return true; 66 } 67 68 // Remove all empty constraints. Note that we need to remap the interval 69 // references. 70 // 71 // Now that they have served their purpose, we also remove dummy constraints, 72 // otherwise that causes issue because our model are invalid in tests. 73 void CpModelPresolver::RemoveEmptyConstraints() { 74 std::vector<int> interval_mapping(context_->working_model->constraints_size(), 75 -1); 76 int new_num_constraints = 0; 77 const int old_num_non_empty_constraints = 78 context_->working_model->constraints_size(); 79 for (int c = 0; c < old_num_non_empty_constraints; ++c) { 80 const auto type = context_->working_model->constraints(c).constraint_case(); 81 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue; 82 if (type == ConstraintProto::ConstraintCase::kDummyConstraint) continue; 83 if (type == ConstraintProto::ConstraintCase::kInterval) { 84 interval_mapping[c] = new_num_constraints; 85 } 86 context_->working_model->mutable_constraints(new_num_constraints++) 87 ->Swap(context_->working_model->mutable_constraints(c)); 88 } 89 context_->working_model->mutable_constraints()->DeleteSubrange( 90 new_num_constraints, old_num_non_empty_constraints - new_num_constraints); 91 for (ConstraintProto& ct_ref : 92 *context_->working_model->mutable_constraints()) { 93 ApplyToAllIntervalIndices( 94 [&interval_mapping](int* ref) { 95 *ref = interval_mapping[*ref]; 96 CHECK_NE(-1, *ref); 97 }, 98 &ct_ref); 99 } 100 } 101 102 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) { 103 if (context_->ModelIsUnsat()) return false; 104 if (!HasEnforcementLiteral(*ct)) return false; 105 106 int new_size = 0; 107 const int old_size = ct->enforcement_literal().size(); 108 for (const int literal : ct->enforcement_literal()) { 109 if (context_->LiteralIsTrue(literal)) { 110 // We can remove a literal at true. 111 context_->UpdateRuleStats("true enforcement literal"); 112 continue; 113 } 114 115 if (context_->LiteralIsFalse(literal)) { 116 context_->UpdateRuleStats("false enforcement literal"); 117 return RemoveConstraint(ct); 118 } 119 120 if (context_->VariableIsUniqueAndRemovable(literal)) { 121 // We can simply set it to false and ignore the constraint in this case. 122 context_->UpdateRuleStats("enforcement literal not used"); 123 CHECK(context_->SetLiteralToFalse(literal)); 124 return RemoveConstraint(ct); 125 } 126 127 // If the literal only appear in the objective, we might be able to fix it 128 // to false. TODO(user): generalize if the literal always appear with the 129 // same polarity. 130 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) { 131 const int64_t obj_coeff = 132 context_->ObjectiveMap().at(PositiveRef(literal)); 133 if (RefIsPositive(literal) == (obj_coeff > 0)) { 134 // It is just more advantageous to set it to false! 135 context_->UpdateRuleStats("enforcement literal with unique direction"); 136 CHECK(context_->SetLiteralToFalse(literal)); 137 return RemoveConstraint(ct); 138 } 139 } 140 141 ct->set_enforcement_literal(new_size++, literal); 142 } 143 ct->mutable_enforcement_literal()->Truncate(new_size); 144 return new_size != old_size; 145 } 146 147 bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) { 148 if (context_->ModelIsUnsat()) return false; 149 if (HasEnforcementLiteral(*ct)) return false; 150 151 int new_size = 0; 152 bool changed = false; 153 int num_true_literals = 0; 154 int true_literal = std::numeric_limits<int32_t>::min(); 155 for (const int literal : ct->bool_xor().literals()) { 156 // TODO(user): More generally, if a variable appear in only bool xor 157 // constraints, we can simply eliminate it using linear algebra on Z/2Z. 158 // This should solve in polynomial time the parity-learning*.fzn problems 159 // for instance. This seems low priority, but it is also easy to do. Even 160 // better would be to have a dedicated propagator with all bool_xor 161 // constraints that do the necessary linear algebra. 162 if (context_->VariableIsUniqueAndRemovable(literal)) { 163 context_->UpdateRuleStats("TODO bool_xor: remove constraint"); 164 } 165 166 if (context_->LiteralIsFalse(literal)) { 167 context_->UpdateRuleStats("bool_xor: remove false literal"); 168 changed = true; 169 continue; 170 } else if (context_->LiteralIsTrue(literal)) { 171 true_literal = literal; // Keep if we need to put one back. 172 num_true_literals++; 173 continue; 174 } 175 176 ct->mutable_bool_xor()->set_literals(new_size++, literal); 177 } 178 179 if (new_size == 0) { 180 if (num_true_literals % 2 == 0) { 181 return context_->NotifyThatModelIsUnsat("bool_xor: always false"); 182 } else { 183 context_->UpdateRuleStats("bool_xor: always true"); 184 return RemoveConstraint(ct); 185 } 186 } else if (new_size == 1) { // We can fix the only active literal. 187 if (num_true_literals % 2 == 0) { 188 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) { 189 return context_->NotifyThatModelIsUnsat( 190 "bool_xor: cannot fix last literal"); 191 } 192 } else { 193 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) { 194 return context_->NotifyThatModelIsUnsat( 195 "bool_xor: cannot fix last literal"); 196 } 197 } 198 context_->UpdateRuleStats("bool_xor: one active literal"); 199 return RemoveConstraint(ct); 200 } else if (new_size == 2) { // We can simplify the bool_xor. 201 const int a = ct->bool_xor().literals(0); 202 const int b = ct->bool_xor().literals(1); 203 if (a == b) { 204 if (num_true_literals % 2 == 0) { 205 return context_->NotifyThatModelIsUnsat("bool_xor: always false"); 206 } else { 207 context_->UpdateRuleStats("bool_xor: always true"); 208 return RemoveConstraint(ct); 209 } 210 } 211 if (a == NegatedRef(b)) { 212 if (num_true_literals % 2 == 1) { 213 return context_->NotifyThatModelIsUnsat("bool_xor: always false"); 214 } else { 215 context_->UpdateRuleStats("bool_xor: always true"); 216 return RemoveConstraint(ct); 217 } 218 } 219 if (num_true_literals % 2 == 0) { // a == not(b). 220 context_->StoreBooleanEqualityRelation(a, NegatedRef(b)); 221 } else { // a == b. 222 context_->StoreBooleanEqualityRelation(a, b); 223 } 224 context_->UpdateNewConstraintsVariableUsage(); 225 context_->UpdateRuleStats("bool_xor: two active literals"); 226 return RemoveConstraint(ct); 227 } 228 229 if (num_true_literals % 2 == 1) { 230 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min()); 231 ct->mutable_bool_xor()->set_literals(new_size++, true_literal); 232 } 233 if (num_true_literals > 1) { 234 context_->UpdateRuleStats("bool_xor: remove even number of true literals"); 235 changed = true; 236 } 237 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size); 238 return changed; 239 } 240 241 bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) { 242 if (context_->ModelIsUnsat()) return false; 243 244 // Move the enforcement literal inside the clause if any. Note that we do not 245 // mark this as a change since the literal in the constraint are the same. 246 if (HasEnforcementLiteral(*ct)) { 247 context_->UpdateRuleStats("bool_or: removed enforcement literal"); 248 for (const int literal : ct->enforcement_literal()) { 249 ct->mutable_bool_or()->add_literals(NegatedRef(literal)); 250 } 251 ct->clear_enforcement_literal(); 252 } 253 254 // Inspects the literals and deal with fixed ones. 255 bool changed = false; 256 context_->tmp_literals.clear(); 257 context_->tmp_literal_set.clear(); 258 for (const int literal : ct->bool_or().literals()) { 259 if (context_->LiteralIsFalse(literal)) { 260 changed = true; 261 continue; 262 } 263 if (context_->LiteralIsTrue(literal)) { 264 context_->UpdateRuleStats("bool_or: always true"); 265 return RemoveConstraint(ct); 266 } 267 // We can just set the variable to true in this case since it is not 268 // used in any other constraint (note that we artificially bump the 269 // objective var usage by 1). 270 if (context_->VariableIsUniqueAndRemovable(literal)) { 271 context_->UpdateRuleStats("bool_or: singleton"); 272 if (!context_->SetLiteralToTrue(literal)) return true; 273 return RemoveConstraint(ct); 274 } 275 if (context_->tmp_literal_set.contains(NegatedRef(literal))) { 276 context_->UpdateRuleStats("bool_or: always true"); 277 return RemoveConstraint(ct); 278 } 279 280 if (context_->tmp_literal_set.contains(literal)) { 281 changed = true; 282 } else { 283 context_->tmp_literal_set.insert(literal); 284 context_->tmp_literals.push_back(literal); 285 } 286 } 287 context_->tmp_literal_set.clear(); 288 289 if (context_->tmp_literals.empty()) { 290 context_->UpdateRuleStats("bool_or: empty"); 291 return context_->NotifyThatModelIsUnsat(); 292 } 293 if (context_->tmp_literals.size() == 1) { 294 context_->UpdateRuleStats("bool_or: only one literal"); 295 if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true; 296 return RemoveConstraint(ct); 297 } 298 if (context_->tmp_literals.size() == 2) { 299 // For consistency, we move all "implication" into half-reified bool_and. 300 // TODO(user): merge by enforcement literal and detect implication cycles. 301 context_->UpdateRuleStats("bool_or: implications"); 302 ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0])); 303 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]); 304 return changed; 305 } 306 307 if (changed) { 308 context_->UpdateRuleStats("bool_or: fixed literals"); 309 ct->mutable_bool_or()->mutable_literals()->Clear(); 310 for (const int lit : context_->tmp_literals) { 311 ct->mutable_bool_or()->add_literals(lit); 312 } 313 } 314 return changed; 315 } 316 317 // Note this constraint does not update the constraint graph. Therefore, it 318 // assumes that the constraint being marked as false is the constraint being 319 // presolved. 320 ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse( 321 ConstraintProto* ct) { 322 if (HasEnforcementLiteral(*ct)) { 323 // Change the constraint to a bool_or. 324 ct->mutable_bool_or()->clear_literals(); 325 for (const int lit : ct->enforcement_literal()) { 326 ct->mutable_bool_or()->add_literals(NegatedRef(lit)); 327 } 328 ct->clear_enforcement_literal(); 329 PresolveBoolOr(ct); 330 return true; 331 } else { 332 return context_->NotifyThatModelIsUnsat(); 333 } 334 } 335 336 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) { 337 if (context_->ModelIsUnsat()) return false; 338 339 if (!HasEnforcementLiteral(*ct)) { 340 context_->UpdateRuleStats("bool_and: non-reified."); 341 for (const int literal : ct->bool_and().literals()) { 342 if (!context_->SetLiteralToTrue(literal)) return true; 343 } 344 return RemoveConstraint(ct); 345 } 346 347 bool changed = false; 348 context_->tmp_literals.clear(); 349 for (const int literal : ct->bool_and().literals()) { 350 if (context_->LiteralIsFalse(literal)) { 351 context_->UpdateRuleStats("bool_and: always false"); 352 return MarkConstraintAsFalse(ct); 353 } 354 if (context_->LiteralIsTrue(literal)) { 355 changed = true; 356 continue; 357 } 358 if (context_->VariableIsUniqueAndRemovable(literal)) { 359 changed = true; 360 if (!context_->SetLiteralToTrue(literal)) return true; 361 continue; 362 } 363 context_->tmp_literals.push_back(literal); 364 } 365 366 // Note that this is not the same behavior as a bool_or: 367 // - bool_or means "at least one", so it is false if empty. 368 // - bool_and means "all literals inside true", so it is true if empty. 369 if (context_->tmp_literals.empty()) return RemoveConstraint(ct); 370 371 if (changed) { 372 ct->mutable_bool_and()->mutable_literals()->Clear(); 373 for (const int lit : context_->tmp_literals) { 374 ct->mutable_bool_and()->add_literals(lit); 375 } 376 context_->UpdateRuleStats("bool_and: fixed literals"); 377 } 378 379 // If a variable can move freely in one direction except for this constraint, 380 // we can make it an equality. 381 // 382 // TODO(user): also consider literal on the other side of the =>. 383 if (ct->enforcement_literal().size() == 1 && 384 ct->bool_and().literals().size() == 1) { 385 const int enforcement = ct->enforcement_literal(0); 386 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) { 387 int var = PositiveRef(enforcement); 388 int64_t obj_coeff = context_->ObjectiveMap().at(var); 389 if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff; 390 391 // The other case where the constraint is redundant is treated elsewhere. 392 if (obj_coeff < 0) { 393 context_->UpdateRuleStats("bool_and: dual equality."); 394 context_->StoreBooleanEqualityRelation(enforcement, 395 ct->bool_and().literals(0)); 396 } 397 } 398 } 399 400 return changed; 401 } 402 403 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) { 404 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne; 405 const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: "; 406 auto* literals = is_at_most_one 407 ? ct->mutable_at_most_one()->mutable_literals() 408 : ct->mutable_exactly_one()->mutable_literals(); 409 410 // Deal with duplicate variable reference. 411 context_->tmp_literal_set.clear(); 412 for (const int literal : *literals) { 413 if (context_->tmp_literal_set.contains(literal)) { 414 if (!context_->SetLiteralToFalse(literal)) return false; 415 context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals")); 416 } 417 if (context_->tmp_literal_set.contains(NegatedRef(literal))) { 418 int num_positive = 0; 419 int num_negative = 0; 420 for (const int other : *literals) { 421 if (PositiveRef(other) != PositiveRef(literal)) { 422 if (!context_->SetLiteralToFalse(other)) return false; 423 context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)")); 424 } else { 425 if (other == literal) { 426 ++num_positive; 427 } else { 428 ++num_negative; 429 } 430 } 431 } 432 433 // This is tricky for the case where the at most one reduce to (lit, 434 // not(lit), not(lit)) for instance. 435 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) { 436 return false; 437 } 438 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) { 439 return false; 440 } 441 return RemoveConstraint(ct); 442 } 443 context_->tmp_literal_set.insert(literal); 444 } 445 446 // Remove fixed variables. 447 bool changed = false; 448 bool transform_to_at_most_one = false; 449 context_->tmp_literals.clear(); 450 for (const int literal : *literals) { 451 if (context_->LiteralIsTrue(literal)) { 452 context_->UpdateRuleStats(absl::StrCat(name, "satisfied")); 453 for (const int other : *literals) { 454 if (other != literal) { 455 if (!context_->SetLiteralToFalse(other)) return false; 456 } 457 } 458 return RemoveConstraint(ct); 459 } 460 461 if (context_->LiteralIsFalse(literal)) { 462 changed = true; 463 continue; 464 } 465 466 // A singleton variable in an at most one can just be set to zero. 467 // 468 // In an exactly one, it can be left to the postsolve to decide, and the 469 // rest of the constraint can be transformed to an at most one. 470 bool is_removable = context_->VariableIsUniqueAndRemovable(literal); 471 if (is_at_most_one && !is_removable && 472 context_->VariableWithCostIsUniqueAndRemovable(literal)) { 473 const auto it = context_->ObjectiveMap().find(PositiveRef(literal)); 474 CHECK(it != context_->ObjectiveMap().end()); 475 const int64_t coeff = it->second; 476 477 // Fixing it to zero need to go in the correct direction. 478 is_removable = (coeff > 0) == RefIsPositive(literal); 479 } 480 481 if (is_removable) { 482 if (is_at_most_one) { 483 context_->UpdateRuleStats("at_most_one: singleton"); 484 if (!context_->SetLiteralToFalse(literal)) return false; 485 changed = true; 486 continue; 487 } else { 488 changed = true; 489 is_at_most_one = true; 490 transform_to_at_most_one = true; 491 *(context_->mapping_model->add_constraints()) = *ct; 492 context_->UpdateRuleStats("exactly_one: singleton"); 493 context_->MarkVariableAsRemoved(PositiveRef(literal)); 494 continue; 495 } 496 } 497 498 context_->tmp_literals.push_back(literal); 499 } 500 501 if (!is_at_most_one && !transform_to_at_most_one && 502 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) { 503 context_->UpdateRuleStats("exactly_one: simplified objective"); 504 } 505 506 if (transform_to_at_most_one) { 507 CHECK(changed); 508 ct->Clear(); 509 literals = ct->mutable_at_most_one()->mutable_literals(); 510 } 511 if (changed) { 512 literals->Clear(); 513 for (const int lit : context_->tmp_literals) { 514 literals->Add(lit); 515 } 516 context_->UpdateRuleStats(absl::StrCat(name, "removed literals")); 517 } 518 return changed; 519 } 520 521 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) { 522 if (context_->ModelIsUnsat()) return false; 523 524 CHECK(!HasEnforcementLiteral(*ct)); 525 const bool changed = PresolveAtMostOrExactlyOne(ct); 526 if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed; 527 528 // Size zero: ok. 529 const auto& literals = ct->at_most_one().literals(); 530 if (literals.empty()) { 531 context_->UpdateRuleStats("at_most_one: empty or all false"); 532 return RemoveConstraint(ct); 533 } 534 535 // Size one: always satisfied. 536 if (literals.size() == 1) { 537 context_->UpdateRuleStats("at_most_one: size one"); 538 return RemoveConstraint(ct); 539 } 540 541 return changed; 542 } 543 544 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) { 545 if (context_->ModelIsUnsat()) return false; 546 CHECK(!HasEnforcementLiteral(*ct)); 547 const bool changed = PresolveAtMostOrExactlyOne(ct); 548 if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed; 549 550 // Size zero: UNSAT. 551 const auto& literals = ct->exactly_one().literals(); 552 if (literals.empty()) { 553 return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false"); 554 } 555 556 // Size one: fix variable. 557 if (literals.size() == 1) { 558 context_->UpdateRuleStats("exactly_one: size one"); 559 if (!context_->SetLiteralToTrue(literals[0])) return false; 560 return RemoveConstraint(ct); 561 } 562 563 // Size two: Equivalence. 564 if (literals.size() == 2) { 565 context_->UpdateRuleStats("exactly_one: size two"); 566 context_->StoreBooleanEqualityRelation(literals[0], 567 NegatedRef(literals[1])); 568 return RemoveConstraint(ct); 569 } 570 571 return changed; 572 } 573 574 bool CpModelPresolver::CanonicalizeLinMax(ConstraintProto* ct) { 575 if (context_->ModelIsUnsat()) return false; 576 577 // Canonicalize all involved expression. 578 // 579 // TODO(user): If we start to have many constraints like this, we should 580 // use reflexion (see cp_model_util) to do that generically. 581 bool changed = CanonicalizeLinearExpression( 582 *ct, ct->mutable_lin_max()->mutable_target()); 583 for (LinearExpressionProto& exp : *(ct->mutable_lin_max()->mutable_exprs())) { 584 changed |= CanonicalizeLinearExpression(*ct, &exp); 585 } 586 return changed; 587 } 588 589 bool CpModelPresolver::PresolveLinMax(ConstraintProto* ct) { 590 if (context_->ModelIsUnsat()) return false; 591 592 // Compute the infered min/max of the target. 593 // Update target domain (if it is not a complex expression). 594 const LinearExpressionProto& target = ct->lin_max().target(); 595 { 596 int64_t infered_min = context_->MinOf(target); 597 int64_t infered_max = std::numeric_limits<int64_t>::min(); 598 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) { 599 infered_min = std::max(infered_min, context_->MinOf(expr)); 600 infered_max = std::max(infered_max, context_->MaxOf(expr)); 601 } 602 603 if (target.vars().empty()) { 604 if (!Domain(infered_min, infered_max).Contains(target.offset())) { 605 context_->UpdateRuleStats("lin_max: infeasible"); 606 return MarkConstraintAsFalse(ct); 607 } 608 } 609 if (!HasEnforcementLiteral(*ct) && target.vars().size() <= 1) { // Affine 610 Domain rhs_domain; 611 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) { 612 rhs_domain = rhs_domain.UnionWith( 613 context_->DomainSuperSetOf(expr).IntersectionWith( 614 {infered_min, infered_max})); 615 } 616 bool reduced = false; 617 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) { 618 return true; 619 } 620 if (reduced) { 621 context_->UpdateRuleStats("lin_max: target domain reduced"); 622 } 623 } 624 } 625 626 // Filter the expressions which are smaller than target_min. 627 const int64_t target_min = context_->MinOf(target); 628 const int64_t target_max = context_->MaxOf(target); 629 bool changed = false; 630 { 631 int new_size = 0; 632 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) { 633 const LinearExpressionProto& expr = ct->lin_max().exprs(i); 634 if (context_->MaxOf(expr) < target_min) continue; 635 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr; 636 new_size++; 637 } 638 if (new_size < ct->lin_max().exprs_size()) { 639 context_->UpdateRuleStats("lin_max: removed exprs"); 640 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange( 641 new_size, ct->lin_max().exprs_size() - new_size); 642 changed = true; 643 } 644 } 645 646 if (ct->lin_max().exprs().empty()) { 647 context_->UpdateRuleStats("lin_max: no exprs"); 648 return MarkConstraintAsFalse(ct); 649 } 650 651 if (ct->lin_max().exprs().size() == 1) { 652 // Convert to an equality. Note that we create a new constraint otherwise it 653 // might not be processed again. 654 context_->UpdateRuleStats("lin_max: converted to equality"); 655 ConstraintProto* new_ct = context_->working_model->add_constraints(); 656 *new_ct = *ct; // copy name and potential reification. 657 auto* arg = new_ct->mutable_linear(); 658 const LinearExpressionProto& a = ct->lin_max().target(); 659 const LinearExpressionProto& b = ct->lin_max().exprs(0); 660 for (int i = 0; i < a.vars().size(); ++i) { 661 arg->add_vars(a.vars(i)); 662 arg->add_coeffs(a.coeffs(i)); 663 } 664 for (int i = 0; i < b.vars().size(); ++i) { 665 arg->add_vars(b.vars(i)); 666 arg->add_coeffs(-b.coeffs(i)); 667 } 668 arg->add_domain(b.offset() - a.offset()); 669 arg->add_domain(b.offset() - a.offset()); 670 context_->UpdateNewConstraintsVariableUsage(); 671 return RemoveConstraint(ct); 672 } 673 674 // Cut everything above the max if possible. 675 // If one of the linear expression has many term and is above the max, we 676 // abort early since none of the other rule can be applied. 677 { 678 bool abort = false; 679 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) { 680 const int64_t value_min = context_->MinOf(expr); 681 bool modified = false; 682 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max), 683 &modified)) { 684 return true; 685 } 686 if (modified) { 687 context_->UpdateRuleStats("lin_max: reduced expression domain."); 688 } 689 const int64_t value_max = context_->MaxOf(expr); 690 if (value_max > target_max) { 691 context_->UpdateRuleStats("TODO lin_max: linear expression above max."); 692 abort = true; 693 } 694 } 695 if (abort) return changed; 696 } 697 698 // Deal with fixed target case. 699 if (target_min == target_max) { 700 bool all_booleans = true; 701 std::vector<int> literals; 702 const int64_t fixed_target = target_min; 703 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) { 704 const int64_t value_min = context_->MinOf(expr); 705 const int64_t value_max = context_->MaxOf(expr); 706 CHECK_LE(value_max, fixed_target) << "Presolved above"; 707 if (value_max < fixed_target) continue; 708 709 if (value_min == value_max && value_max == fixed_target) { 710 context_->UpdateRuleStats("lin_max: always satisfied"); 711 return RemoveConstraint(ct); 712 } 713 if (context_->ExpressionIsAffineBoolean(expr)) { 714 CHECK_EQ(value_max, fixed_target); 715 literals.push_back(context_->LiteralForExpressionMax(expr)); 716 } else { 717 all_booleans = false; 718 } 719 } 720 if (all_booleans) { 721 if (literals.empty()) { 722 return MarkConstraintAsFalse(ct); 723 } 724 725 // At least one true; 726 context_->UpdateRuleStats("lin_max: fixed target and all booleans"); 727 for (const int lit : literals) { 728 ct->mutable_bool_or()->add_literals(lit); 729 } 730 return true; 731 } 732 return changed; 733 } 734 735 // If everything is Boolean and affine, do not use a lin max! 736 if (context_->ExpressionIsAffineBoolean(target)) { 737 const int target_ref = context_->LiteralForExpressionMax(target); 738 739 bool abort = false; 740 741 bool min_is_reachable = false; 742 std::vector<int> min_literals; 743 std::vector<int> literals_above_min; 744 std::vector<int> max_literals; 745 746 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) { 747 const int64_t value_min = context_->MinOf(expr); 748 const int64_t value_max = context_->MaxOf(expr); 749 750 // This shouldn't happen, but it document the fact. 751 if (value_min > target_min) { 752 context_->UpdateRuleStats("lin_max: fix target"); 753 if (!context_->SetLiteralToTrue(target_ref)) return true; 754 abort = true; 755 break; 756 } 757 758 // expr is fixed. 759 if (value_min == value_max) { 760 if (value_min == target_min) min_is_reachable = true; 761 continue; 762 } 763 764 if (!context_->ExpressionIsAffineBoolean(expr)) { 765 abort = true; 766 break; 767 } 768 769 const int ref = context_->LiteralForExpressionMax(expr); 770 CHECK_LE(value_min, target_min); 771 if (value_min == target_min) { 772 min_literals.push_back(NegatedRef(ref)); 773 } 774 775 CHECK_LE(value_max, target_max); 776 if (value_max == target_max) { 777 max_literals.push_back(ref); 778 literals_above_min.push_back(ref); 779 } else if (value_max > target_min) { 780 literals_above_min.push_back(ref); 781 } else if (value_max == target_min) { 782 min_literals.push_back(ref); 783 } 784 } 785 if (!abort) { 786 context_->UpdateRuleStats("lin_max: all Booleans."); 787 788 // target_ref => at_least_one(max_literals); 789 ConstraintProto* clause = context_->working_model->add_constraints(); 790 clause->add_enforcement_literal(target_ref); 791 clause->mutable_bool_or(); 792 for (const int lit : max_literals) { 793 clause->mutable_bool_or()->add_literals(lit); 794 } 795 796 // not(target_ref) => not(lit) for lit in literals_above_min 797 for (const int lit : literals_above_min) { 798 context_->AddImplication(lit, target_ref); 799 } 800 801 if (!min_is_reachable) { 802 // not(target_ref) => at_least_one(min_literals). 803 ConstraintProto* clause = context_->working_model->add_constraints(); 804 clause->add_enforcement_literal(NegatedRef(target_ref)); 805 clause->mutable_bool_or(); 806 for (const int lit : min_literals) { 807 clause->mutable_bool_or()->add_literals(lit); 808 } 809 } 810 811 context_->UpdateNewConstraintsVariableUsage(); 812 return RemoveConstraint(ct); 813 } 814 } 815 816 return changed; 817 } 818 819 // This presolve expect that the constraint only contains affine expressions. 820 bool CpModelPresolver::PresolveIntAbs(ConstraintProto* ct) { 821 CHECK_EQ(ct->enforcement_literal_size(), 0); 822 if (context_->ModelIsUnsat()) return false; 823 const LinearExpressionProto& target_expr = ct->lin_max().target(); 824 const LinearExpressionProto& expr = ct->lin_max().exprs(0); 825 DCHECK_EQ(expr.vars_size(), 1); 826 827 // Propagate domain from the expression to the target. 828 { 829 const Domain expr_domain = context_->DomainSuperSetOf(expr); 830 const Domain new_target_domain = 831 expr_domain.UnionWith(expr_domain.Negation()) 832 .IntersectionWith({0, std::numeric_limits<int64_t>::max()}); 833 bool target_domain_modified = false; 834 if (!context_->IntersectDomainWith(target_expr, new_target_domain, 835 &target_domain_modified)) { 836 return false; 837 } 838 if (expr_domain.IsFixed()) { 839 context_->UpdateRuleStats("int_abs: fixed expression"); 840 return RemoveConstraint(ct); 841 } 842 if (target_domain_modified) { 843 context_->UpdateRuleStats("int_abs: propagate domain from x to abs(x)"); 844 } 845 } 846 847 // Propagate from target domain to variable. 848 { 849 const Domain target_domain = 850 context_->DomainSuperSetOf(target_expr) 851 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max())); 852 const Domain new_expr_domain = 853 target_domain.UnionWith(target_domain.Negation()); 854 bool expr_domain_modified = false; 855 if (!context_->IntersectDomainWith(expr, new_expr_domain, 856 &expr_domain_modified)) { 857 return true; 858 } 859 // This is the only reason why we don't support fully generic linear 860 // expression. 861 if (context_->IsFixed(target_expr)) { 862 context_->UpdateRuleStats("int_abs: fixed target"); 863 return RemoveConstraint(ct); 864 } 865 if (expr_domain_modified) { 866 context_->UpdateRuleStats("int_abs: propagate domain from abs(x) to x"); 867 } 868 } 869 870 // Convert to equality if the sign of expr is fixed. 871 if (context_->MinOf(expr) >= 0) { 872 context_->UpdateRuleStats("int_abs: converted to equality"); 873 ConstraintProto* new_ct = context_->working_model->add_constraints(); 874 new_ct->set_name(ct->name()); 875 auto* arg = new_ct->mutable_linear(); 876 arg->add_domain(0); 877 arg->add_domain(0); 878 AddLinearExpressionToLinearConstraint(target_expr, 1, arg); 879 AddLinearExpressionToLinearConstraint(expr, -1, arg); 880 if (!CanonicalizeLinear(new_ct)) return false; 881 context_->UpdateNewConstraintsVariableUsage(); 882 return RemoveConstraint(ct); 883 } 884 885 if (context_->MaxOf(expr) <= 0) { 886 context_->UpdateRuleStats("int_abs: converted to equality"); 887 ConstraintProto* new_ct = context_->working_model->add_constraints(); 888 new_ct->set_name(ct->name()); 889 auto* arg = new_ct->mutable_linear(); 890 arg->add_domain(0); 891 arg->add_domain(0); 892 AddLinearExpressionToLinearConstraint(target_expr, 1, arg); 893 AddLinearExpressionToLinearConstraint(expr, 1, arg); 894 if (!CanonicalizeLinear(new_ct)) return false; 895 context_->UpdateNewConstraintsVariableUsage(); 896 return RemoveConstraint(ct); 897 } 898 899 // Remove the abs constraint if the target is removable and if domains have 900 // been propagated without loss. 901 // For now, we known that there is no loss if the target is a single ref. 902 // Since all the expression are affine, in this case we are fine. 903 if (ExpressionContainsSingleRef(target_expr) && 904 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) { 905 context_->MarkVariableAsRemoved(target_expr.vars(0)); 906 *context_->mapping_model->add_constraints() = *ct; 907 context_->UpdateRuleStats("int_abs: unused target"); 908 return RemoveConstraint(ct); 909 } 910 911 // Store the x == abs(y) relation if expr and target_expr can be cast into a 912 // ref. 913 // TODO(user): Support general affine expression in for expr in the Store 914 // method call. 915 { 916 if (ExpressionContainsSingleRef(target_expr) && 917 ExpressionContainsSingleRef(expr)) { 918 const int target_ref = GetSingleRefFromExpression(target_expr); 919 const int expr_ref = GetSingleRefFromExpression(expr); 920 if (context_->StoreAbsRelation(target_ref, expr_ref)) { 921 context_->UpdateRuleStats("int_abs: store abs(x) == y"); 922 } 923 } 924 } 925 926 return false; 927 } 928 929 bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) { 930 if (context_->ModelIsUnsat()) return false; 931 if (HasEnforcementLiteral(*ct)) return false; 932 933 LinearArgumentProto* proto = ct->mutable_int_prod(); 934 935 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_target()); 936 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) { 937 changed |= CanonicalizeLinearExpression(*ct, &exp); 938 } 939 940 // Remove constant expressions. 941 int64_t constant_factor = 1; 942 int new_size = 0; 943 for (int i = 0; i < ct->int_prod().exprs().size(); ++i) { 944 LinearExpressionProto expr = ct->int_prod().exprs(i); 945 if (context_->IsFixed(expr)) { 946 context_->UpdateRuleStats("int_prod: removed constant expressions."); 947 changed = true; 948 constant_factor = CapProd(constant_factor, context_->FixedValue(expr)); 949 continue; 950 } else { 951 const int64_t coeff = expr.coeffs(0); 952 const int64_t offset = expr.offset(); 953 const int64_t gcd = 954 MathUtil::GCD64(static_cast<uint64_t>(std::abs(coeff)), 955 static_cast<uint64_t>(std::abs(offset))); 956 if (gcd != 1) { 957 constant_factor = CapProd(constant_factor, gcd); 958 expr.set_coeffs(0, coeff / gcd); 959 expr.set_offset(offset / gcd); 960 } 961 } 962 *proto->mutable_exprs(new_size++) = expr; 963 } 964 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size, 965 proto->mutable_exprs()->end()); 966 967 if (ct->int_prod().exprs().empty()) { 968 if (!context_->IntersectDomainWith(ct->int_prod().target(), 969 Domain(constant_factor))) { 970 return false; 971 } 972 context_->UpdateRuleStats("int_prod: constant product"); 973 return RemoveConstraint(ct); 974 } 975 976 if (constant_factor == 0) { 977 context_->UpdateRuleStats("int_prod: multiplication by zero"); 978 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) { 979 return false; 980 } 981 return RemoveConstraint(ct); 982 } 983 984 // In this case, the only possible value that fit in the domains is zero. 985 // We will check for UNSAT if zero is not achievable by the rhs below. 986 if (constant_factor == std::numeric_limits<int64_t>::min() || 987 constant_factor == std::numeric_limits<int64_t>::max()) { 988 context_->UpdateRuleStats("int_prod: overflow if non zero"); 989 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) { 990 return false; 991 } 992 constant_factor = 1; 993 } 994 995 // Replace by linear! 996 if (ct->int_prod().exprs().size() == 1) { 997 LinearConstraintProto* const lin = 998 context_->working_model->add_constraints()->mutable_linear(); 999 lin->add_domain(0); 1000 lin->add_domain(0); 1001 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), 1, lin); 1002 AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0), 1003 -constant_factor, lin); 1004 context_->UpdateNewConstraintsVariableUsage(); 1005 context_->UpdateRuleStats("int_prod: linearize product by constant."); 1006 return RemoveConstraint(ct); 1007 } 1008 1009 if (constant_factor != 1) { 1010 // Lets canonicalize the target by introducing a new variable if necessary. 1011 // 1012 // coeff * X + offset must be a multiple of constant_factor, so 1013 // we can rewrite X so that this property is clear. 1014 const LinearExpressionProto old_target = ct->int_prod().target(); 1015 if (!context_->IsFixed(old_target)) { 1016 const int ref = old_target.vars(0); 1017 const int64_t coeff = old_target.coeffs(0); 1018 const int64_t offset = old_target.offset(); 1019 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor, 1020 -offset)) { 1021 return false; 1022 } 1023 if (context_->IsFixed(ref)) { 1024 changed = true; 1025 } 1026 } 1027 1028 // This can happen during CanonicalizeAffineVariable(). 1029 if (context_->IsFixed(old_target)) { 1030 const int64_t target_value = context_->FixedValue(old_target); 1031 if (target_value % constant_factor != 0) { 1032 return context_->NotifyThatModelIsUnsat( 1033 "int_prod: constant factor does not divide constant target"); 1034 } 1035 proto->clear_target(); 1036 proto->mutable_target()->set_offset(target_value / constant_factor); 1037 context_->UpdateRuleStats( 1038 "int_prod: divide product and fixed target by constant factor"); 1039 } else { 1040 // We use absl::int128 to be resistant to overflow here. 1041 const AffineRelation::Relation r = 1042 context_->GetAffineRelation(old_target.vars(0)); 1043 const absl::int128 temp_coeff = 1044 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff); 1045 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0); 1046 const absl::int128 temp_offset = 1047 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) + 1048 absl::int128(old_target.offset()); 1049 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0); 1050 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor); 1051 const absl::int128 new_offset = 1052 temp_offset / absl::int128(constant_factor); 1053 1054 // TODO(user): We try to keep coeff/offset small, if this happens, it 1055 // probably means there is no feasible solution involving int64_t and that 1056 // do not causes overflow while evaluating it, but it is hard to be 1057 // exactly sure we are correct here since it depends on the evaluation 1058 // order. Similarly, by introducing intermediate variable we might loose 1059 // solution if this intermediate variable value do not fit on an int64_t. 1060 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) || 1061 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) || 1062 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) || 1063 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) { 1064 return context_->NotifyThatModelIsUnsat( 1065 "int_prod: overflow during simplification."); 1066 } 1067 1068 // Rewrite the target. 1069 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff)); 1070 proto->mutable_target()->set_vars(0, r.representative); 1071 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset)); 1072 context_->UpdateRuleStats("int_prod: divide product by constant factor"); 1073 changed = true; 1074 } 1075 } 1076 1077 // Restrict the target domain if possible. 1078 Domain implied(1); 1079 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) { 1080 implied = 1081 implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr)); 1082 } 1083 bool domain_modified = false; 1084 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied, 1085 &domain_modified)) { 1086 return false; 1087 } 1088 if (domain_modified) { 1089 context_->UpdateRuleStats("int_prod: reduced target domain."); 1090 } 1091 1092 if (ct->int_prod().exprs_size() == 2) { 1093 LinearExpressionProto a = ct->int_prod().exprs(0); 1094 LinearExpressionProto b = ct->int_prod().exprs(1); 1095 const LinearExpressionProto product = ct->int_prod().target(); 1096 if (LinearExpressionProtosAreEqual(a, b) && 1097 LinearExpressionProtosAreEqual( 1098 a, product)) { // x = x * x, only true for {0, 1}. 1099 if (!context_->IntersectDomainWith(product, Domain(0, 1))) { 1100 return false; 1101 } 1102 context_->UpdateRuleStats("int_prod: fix variable to zero or one."); 1103 return RemoveConstraint(ct); 1104 } 1105 } 1106 1107 // For now, we only presolve the case where all variables are Booleans. 1108 const LinearExpressionProto target_expr = ct->int_prod().target(); 1109 int target; 1110 if (!context_->ExpressionIsALiteral(target_expr, &target)) { 1111 return changed; 1112 } 1113 std::vector<int> literals; 1114 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) { 1115 int lit; 1116 if (!context_->ExpressionIsALiteral(expr, &lit)) { 1117 return changed; 1118 } 1119 literals.push_back(lit); 1120 } 1121 1122 // This is a bool constraint! 1123 context_->UpdateRuleStats("int_prod: all Boolean."); 1124 { 1125 ConstraintProto* new_ct = context_->working_model->add_constraints(); 1126 new_ct->add_enforcement_literal(target); 1127 auto* arg = new_ct->mutable_bool_and(); 1128 for (const int lit : literals) { 1129 arg->add_literals(lit); 1130 } 1131 } 1132 { 1133 ConstraintProto* new_ct = context_->working_model->add_constraints(); 1134 auto* arg = new_ct->mutable_bool_or(); 1135 arg->add_literals(target); 1136 for (const int lit : literals) { 1137 arg->add_literals(NegatedRef(lit)); 1138 } 1139 } 1140 context_->UpdateNewConstraintsVariableUsage(); 1141 return RemoveConstraint(ct); 1142 } 1143 1144 bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) { 1145 if (context_->ModelIsUnsat()) return false; 1146 1147 bool changed = CanonicalizeLinearExpression( 1148 *ct, ct->mutable_int_div()->mutable_target()); 1149 for (LinearExpressionProto& exp : *(ct->mutable_int_div()->mutable_exprs())) { 1150 changed |= CanonicalizeLinearExpression(*ct, &exp); 1151 } 1152 1153 const LinearExpressionProto target = ct->int_div().target(); 1154 const LinearExpressionProto expr = ct->int_div().exprs(0); 1155 const LinearExpressionProto div = ct->int_div().exprs(1); 1156 1157 if (LinearExpressionProtosAreEqual(expr, div)) { 1158 if (!context_->IntersectDomainWith(target, Domain(1))) { 1159 return false; 1160 } 1161 context_->UpdateRuleStats("int_div: y = x / x"); 1162 return RemoveConstraint(ct); 1163 } else if (LinearExpressionProtosAreEqual(expr, div, -1)) { 1164 if (!context_->IntersectDomainWith(target, Domain(-1))) { 1165 return false; 1166 } 1167 context_->UpdateRuleStats("int_div: y = - x / x"); 1168 return RemoveConstraint(ct); 1169 } 1170 1171 // For now, we only presolve the case where the divisor is constant. 1172 if (!context_->IsFixed(div)) return changed; 1173 1174 const int64_t divisor = context_->FixedValue(div); 1175 if (divisor == 1) { 1176 LinearConstraintProto* const lin = 1177 context_->working_model->add_constraints()->mutable_linear(); 1178 lin->add_domain(0); 1179 lin->add_domain(0); 1180 AddLinearExpressionToLinearConstraint(expr, 1, lin); 1181 AddLinearExpressionToLinearConstraint(target, -1, lin); 1182 context_->UpdateNewConstraintsVariableUsage(); 1183 context_->UpdateRuleStats("int_div: rewrite to equality"); 1184 return RemoveConstraint(ct); 1185 } 1186 bool domain_modified = false; 1187 if (!context_->IntersectDomainWith( 1188 target, context_->DomainSuperSetOf(expr).DivisionBy(divisor), 1189 &domain_modified)) { 1190 return false; 1191 } 1192 if (domain_modified) { 1193 context_->UpdateRuleStats( 1194 "int_div: updated domain of target in target = X / cte"); 1195 } 1196 1197 // Linearize if everything is positive. 1198 // TODO(user): Deal with other cases where there is no change of 1199 // sign. We can also deal with target = cte, div variable. 1200 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 && 1201 divisor > 1) { 1202 LinearConstraintProto* const lin = 1203 context_->working_model->add_constraints()->mutable_linear(); 1204 lin->add_domain(0); 1205 lin->add_domain(divisor - 1); 1206 AddLinearExpressionToLinearConstraint(expr, 1, lin); 1207 AddLinearExpressionToLinearConstraint(target, -divisor, lin); 1208 context_->UpdateNewConstraintsVariableUsage(); 1209 context_->UpdateRuleStats( 1210 "int_div: linearize positive division with a constant divisor"); 1211 return RemoveConstraint(ct); 1212 } 1213 1214 // TODO(user): reduce the domain of X by introducing an 1215 // InverseDivisionOfSortedDisjointIntervals(). 1216 return changed; 1217 } 1218 1219 bool CpModelPresolver::PresolveIntMod(ConstraintProto* ct) { 1220 if (context_->ModelIsUnsat()) return false; 1221 1222 bool changed = CanonicalizeLinearExpression( 1223 *ct, ct->mutable_int_mod()->mutable_target()); 1224 for (LinearExpressionProto& exp : *(ct->mutable_int_mod()->mutable_exprs())) { 1225 changed |= CanonicalizeLinearExpression(*ct, &exp); 1226 } 1227 1228 const LinearExpressionProto target = ct->int_mod().target(); 1229 const LinearExpressionProto expr = ct->int_mod().exprs(0); 1230 const LinearExpressionProto mod = ct->int_mod().exprs(1); 1231 1232 bool domain_changed = false; 1233 if (!context_->IntersectDomainWith( 1234 target, 1235 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset( 1236 context_->DomainSuperSetOf(mod)), 1237 &domain_changed)) { 1238 return changed; 1239 } 1240 1241 if (domain_changed) { 1242 context_->UpdateRuleStats("int_mod: reduce target domain"); 1243 } 1244 1245 return changed; 1246 } 1247 1248 bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) { 1249 bool changed = false; 1250 1251 // Optim: Special case for the linear constraint. We just remap the 1252 // enforcement literals, the normal variables will be replaced by their 1253 // representative in CanonicalizeLinear(). 1254 if (ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) { 1255 for (int& ref : *ct->mutable_enforcement_literal()) { 1256 const int rep = this->context_->GetLiteralRepresentative(ref); 1257 if (rep != ref) { 1258 changed = true; 1259 ref = rep; 1260 } 1261 } 1262 return changed; 1263 } 1264 1265 // Optim: This extra loop is a lot faster than reparsing the variable from the 1266 // proto when there is nothing to do, which is quite often. 1267 bool work_to_do = false; 1268 for (const int var : context_->ConstraintToVars(c)) { 1269 const AffineRelation::Relation r = context_->GetAffineRelation(var); 1270 if (r.representative != var) { 1271 work_to_do = true; 1272 break; 1273 } 1274 } 1275 if (!work_to_do) return false; 1276 1277 // Remap equal and negated variables to their representative. 1278 ApplyToAllVariableIndices( 1279 [&changed, this](int* ref) { 1280 const int rep = context_->GetVariableRepresentative(*ref); 1281 if (rep != *ref) { 1282 changed = true; 1283 *ref = rep; 1284 } 1285 }, 1286 ct); 1287 1288 // Remap literal and negated literal to their representative. 1289 ApplyToAllLiteralIndices( 1290 [&changed, this](int* ref) { 1291 const int rep = this->context_->GetLiteralRepresentative(*ref); 1292 if (rep != *ref) { 1293 changed = true; 1294 *ref = rep; 1295 } 1296 }, 1297 ct); 1298 return changed; 1299 } 1300 1301 bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) { 1302 if (context_->ModelIsUnsat()) return false; 1303 1304 // Compute the GCD of all coefficients. 1305 int64_t gcd = 0; 1306 const int num_vars = ct->linear().vars().size(); 1307 for (int i = 0; i < num_vars; ++i) { 1308 const int64_t magnitude = std::abs(ct->linear().coeffs(i)); 1309 gcd = MathUtil::GCD64(gcd, magnitude); 1310 if (gcd == 1) break; 1311 } 1312 if (gcd > 1) { 1313 context_->UpdateRuleStats("linear: divide by GCD"); 1314 for (int i = 0; i < num_vars; ++i) { 1315 ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd); 1316 } 1317 const Domain rhs = ReadDomainFromProto(ct->linear()); 1318 FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear()); 1319 if (ct->linear().domain_size() == 0) { 1320 return MarkConstraintAsFalse(ct); 1321 } 1322 } 1323 return false; 1324 } 1325 1326 template <typename ProtoWithVarsAndCoeffs> 1327 bool CpModelPresolver::CanonicalizeLinearExpressionInternal( 1328 const ConstraintProto& ct, ProtoWithVarsAndCoeffs* proto, int64_t* offset) { 1329 // First regroup the terms on the same variables and sum the fixed ones. 1330 // 1331 // TODO(user): Add a quick pass to skip most of the work below if the 1332 // constraint is already in canonical form? 1333 tmp_terms_.clear(); 1334 int64_t sum_of_fixed_terms = 0; 1335 bool remapped = false; 1336 const int old_size = proto->vars().size(); 1337 DCHECK_EQ(old_size, proto->coeffs().size()); 1338 for (int i = 0; i < old_size; ++i) { 1339 const int ref = proto->vars(i); 1340 const int var = PositiveRef(ref); 1341 const int64_t coeff = 1342 RefIsPositive(ref) ? proto->coeffs(i) : -proto->coeffs(i); 1343 if (coeff == 0) continue; 1344 1345 if (context_->IsFixed(var)) { 1346 sum_of_fixed_terms += coeff * context_->MinOf(var); 1347 continue; 1348 } 1349 1350 // TODO(user): Avoid the quadratic loop for the corner case of many 1351 // enforcement literal (this should be pretty rare though). 1352 bool removed = false; 1353 for (const int enf : ct.enforcement_literal()) { 1354 if (var == PositiveRef(enf)) { 1355 if (RefIsPositive(enf)) { 1356 // If the constraint is enforced, we can assume the variable is at 1. 1357 sum_of_fixed_terms += coeff; 1358 } else { 1359 // We can assume the variable is at zero. 1360 } 1361 removed = true; 1362 break; 1363 } 1364 } 1365 if (removed) { 1366 context_->UpdateRuleStats("linear: enforcement literal in expression"); 1367 continue; 1368 } 1369 1370 const AffineRelation::Relation r = context_->GetAffineRelation(var); 1371 if (r.representative != var) { 1372 remapped = true; 1373 sum_of_fixed_terms += coeff * r.offset; 1374 } 1375 tmp_terms_.push_back({r.representative, coeff * r.coeff}); 1376 } 1377 proto->clear_vars(); 1378 proto->clear_coeffs(); 1379 std::sort(tmp_terms_.begin(), tmp_terms_.end()); 1380 int current_var = 0; 1381 int64_t current_coeff = 0; 1382 for (const auto entry : tmp_terms_) { 1383 CHECK(RefIsPositive(entry.first)); 1384 if (entry.first == current_var) { 1385 current_coeff += entry.second; 1386 } else { 1387 if (current_coeff != 0) { 1388 proto->add_vars(current_var); 1389 proto->add_coeffs(current_coeff); 1390 } 1391 current_var = entry.first; 1392 current_coeff = entry.second; 1393 } 1394 } 1395 if (current_coeff != 0) { 1396 proto->add_vars(current_var); 1397 proto->add_coeffs(current_coeff); 1398 } 1399 if (remapped) { 1400 context_->UpdateRuleStats("linear: remapped using affine relations"); 1401 } 1402 if (proto->vars().size() < old_size) { 1403 context_->UpdateRuleStats("linear: fixed or dup variables"); 1404 } 1405 *offset = sum_of_fixed_terms; 1406 return remapped || proto->vars().size() < old_size; 1407 } 1408 1409 bool CpModelPresolver::CanonicalizeLinearExpression( 1410 const ConstraintProto& ct, LinearExpressionProto* exp) { 1411 int64_t offset = 0; 1412 const bool result = CanonicalizeLinearExpressionInternal(ct, exp, &offset); 1413 exp->set_offset(exp->offset() + offset); 1414 return result; 1415 } 1416 1417 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) { 1418 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear || 1419 context_->ModelIsUnsat()) { 1420 return false; 1421 } 1422 1423 if (ct->linear().domain().empty()) { 1424 context_->UpdateRuleStats("linear: no domain"); 1425 return MarkConstraintAsFalse(ct); 1426 } 1427 1428 int64_t offset = 0; 1429 bool changed = 1430 CanonicalizeLinearExpressionInternal(*ct, ct->mutable_linear(), &offset); 1431 if (offset != 0) { 1432 FillDomainInProto( 1433 ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)), 1434 ct->mutable_linear()); 1435 } 1436 changed |= DivideLinearByGcd(ct); 1437 return changed; 1438 } 1439 1440 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) { 1441 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear || 1442 context_->ModelIsUnsat()) { 1443 return false; 1444 } 1445 1446 std::set<int> index_to_erase; 1447 const int num_vars = ct->linear().vars().size(); 1448 Domain rhs = ReadDomainFromProto(ct->linear()); 1449 1450 // First pass. Process singleton column that are not in the objective. Note 1451 // that for postsolve, it is important that we process them in the same order 1452 // in which they will be removed. 1453 for (int i = 0; i < num_vars; ++i) { 1454 const int var = ct->linear().vars(i); 1455 const int64_t coeff = ct->linear().coeffs(i); 1456 CHECK(RefIsPositive(var)); 1457 if (context_->VariableIsUniqueAndRemovable(var)) { 1458 bool exact; 1459 const auto term_domain = 1460 context_->DomainOf(var).MultiplicationBy(-coeff, &exact); 1461 if (!exact) continue; 1462 1463 // We do not do that if the domain of rhs becomes too complex. 1464 const Domain new_rhs = rhs.AdditionWith(term_domain); 1465 if (new_rhs.NumIntervals() > 100) continue; 1466 1467 // Note that we can't do that if we loose information in the 1468 // multiplication above because the new domain might not be as strict 1469 // as the initial constraint otherwise. TODO(user): because of the 1470 // addition, it might be possible to cover more cases though. 1471 context_->UpdateRuleStats("linear: singleton column"); 1472 index_to_erase.insert(i); 1473 rhs = new_rhs; 1474 continue; 1475 } 1476 } 1477 1478 // If we didn't find any, look for the one appearing in the objective. 1479 if (index_to_erase.empty()) { 1480 // Note that we only do that if we have a non-reified equality. 1481 if (context_->params().presolve_substitution_level() <= 0) return false; 1482 if (!ct->enforcement_literal().empty()) return false; 1483 1484 // If it is possible to do so, note that we can transform constraint into 1485 // equalities in PropagateDomainsInLinear(). 1486 if (rhs.Min() != rhs.Max()) return false; 1487 1488 for (int i = 0; i < num_vars; ++i) { 1489 const int var = ct->linear().vars(i); 1490 const int64_t coeff = ct->linear().coeffs(i); 1491 CHECK(RefIsPositive(var)); 1492 1493 // If the variable appear only in the objective and we have an equality, 1494 // we can transfer the cost to the rest of the linear expression, and 1495 // remove that variable. Note that this do not remove any feasible 1496 // solution and is not a "dual" reduction. 1497 // 1498 // Note that is similar to the substitution code in PresolveLinear() but 1499 // it doesn't require the variable to be implied free since we do not 1500 // remove the constraints afterwards, just the variable. 1501 if (!context_->VariableWithCostIsUnique(var)) continue; 1502 DCHECK(context_->ObjectiveMap().contains(var)); 1503 1504 // We only support substitution that does not require to multiply the 1505 // objective by some factor. 1506 // 1507 // TODO(user): If the objective is a single variable, we can actually 1508 // "absorb" any factor into the objective scaling. 1509 const int64_t objective_coeff = context_->ObjectiveMap().at(var); 1510 CHECK_NE(coeff, 0); 1511 if (objective_coeff % coeff != 0) continue; 1512 1513 // We do not do that if the domain of rhs becomes too complex. 1514 bool exact; 1515 const auto term_domain = 1516 context_->DomainOf(var).MultiplicationBy(-coeff, &exact); 1517 if (!exact) continue; 1518 const Domain new_rhs = rhs.AdditionWith(term_domain); 1519 if (new_rhs.NumIntervals() > 100) continue; 1520 1521 // Special case: If the objective was a single variable, we can transfer 1522 // the domain of var to the objective, and just completely remove this 1523 // equality constraint like it is done in ExpandObjective(). 1524 if (context_->ObjectiveMap().size() == 1) { 1525 if (!context_->IntersectDomainWith( 1526 var, context_->ObjectiveDomain().InverseMultiplicationBy( 1527 objective_coeff))) { 1528 return true; 1529 } 1530 1531 // The intersection above might fix var, in which case, we just abort. 1532 if (context_->IsFixed(var)) continue; 1533 1534 // This makes sure the domain of var is propagated back to the 1535 // objective. 1536 // 1537 // Tricky: We cannot "simplify" the domain of the objective using the 1538 // implied domain from the linear expression since we will substitute 1539 // the variable. 1540 // 1541 // TODO(user): Maybe if var has a complex domain, we might not want to 1542 // substitute it? 1543 if (!context_->CanonicalizeObjective(/*simplify_domain=*/false)) { 1544 return context_->NotifyThatModelIsUnsat(); 1545 } 1546 1547 // Normally, CanonicalizeObjective() shouldn't remove var because 1548 // we work on a linear constraint that has been canonicalized. We keep 1549 // the test here in case this ever happen so we are notified. 1550 if (!context_->ObjectiveMap().contains(var)) { 1551 LOG(WARNING) << "This was not supposed to happen and the presolve " 1552 "could be improved."; 1553 continue; 1554 } 1555 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) { 1556 if (context_->ModelIsUnsat()) return true; 1557 continue; 1558 } 1559 1560 context_->UpdateRuleStats("linear: singleton column define objective."); 1561 context_->MarkVariableAsRemoved(var); 1562 *(context_->mapping_model->add_constraints()) = *ct; 1563 return RemoveConstraint(ct); 1564 } 1565 1566 // Update the objective and remove the variable from its equality 1567 // constraint by expanding its rhs. This might fail if the new linear 1568 // objective expression can lead to overflow. 1569 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) { 1570 if (context_->ModelIsUnsat()) return true; 1571 continue; 1572 } 1573 1574 context_->UpdateRuleStats( 1575 "linear: singleton column in equality and in objective."); 1576 rhs = new_rhs; 1577 index_to_erase.insert(i); 1578 break; 1579 } 1580 } 1581 if (index_to_erase.empty()) return false; 1582 1583 // Tricky: If we have a singleton variable in an enforced constraint, and at 1584 // postsolve the enforcement is false, we might just ignore the constraint. 1585 // This is fine, but we still need to assign any removed variable to a 1586 // feasible value, otherwise later postsolve rules might not work correctly. 1587 // Adding these linear1 achieve that. 1588 // 1589 // TODO(user): Alternatively, we could copy the constraint without the 1590 // enforcement to the mapping model, since singleton variable are supposed 1591 // to always have a feasible value anyway. 1592 if (!ct->enforcement_literal().empty()) { 1593 for (const int i : index_to_erase) { 1594 const int var = ct->linear().vars(i); 1595 auto* l = context_->mapping_model->add_constraints()->mutable_linear(); 1596 l->add_vars(var); 1597 l->add_coeffs(1); 1598 FillDomainInProto(context_->DomainOf(var), l); 1599 } 1600 } 1601 1602 // TODO(user): we could add the constraint to mapping_model only once 1603 // instead of adding a reduced version of it each time a new singleton 1604 // variable appear in the same constraint later. That would work but would 1605 // also force the postsolve to take search decisions... 1606 *context_->mapping_model->add_constraints() = *ct; 1607 1608 int new_size = 0; 1609 for (int i = 0; i < num_vars; ++i) { 1610 if (index_to_erase.count(i)) { 1611 context_->MarkVariableAsRemoved(ct->linear().vars(i)); 1612 continue; 1613 } 1614 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i)); 1615 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i)); 1616 ++new_size; 1617 } 1618 ct->mutable_linear()->mutable_vars()->Truncate(new_size); 1619 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size); 1620 FillDomainInProto(rhs, ct->mutable_linear()); 1621 DivideLinearByGcd(ct); 1622 return true; 1623 } 1624 1625 // If the gcd of all but one term (with index target_index) is not one, we can 1626 // rewrite the last term using an affine representative. 1627 bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality( 1628 int target_index, ConstraintProto* ct) { 1629 int64_t gcd = 0; 1630 const int num_variables = ct->linear().vars().size(); 1631 for (int i = 0; i < num_variables; ++i) { 1632 if (i == target_index) continue; 1633 const int64_t magnitude = std::abs(ct->linear().coeffs(i)); 1634 gcd = MathUtil::GCD64(gcd, magnitude); 1635 if (gcd == 1) return false; 1636 } 1637 1638 // If we take the constraint % gcd, we have 1639 // ref * coeff % gcd = rhs % gcd 1640 CHECK_GT(gcd, 1); 1641 const int ref = ct->linear().vars(target_index); 1642 const int64_t coeff = ct->linear().coeffs(target_index); 1643 const int64_t rhs = ct->linear().domain(0); 1644 1645 // This should have been processed before by just dividing the whole 1646 // constraint by the gcd. 1647 if (coeff % gcd == 0) return false; 1648 1649 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) { 1650 return false; 1651 } 1652 1653 // We use the new variable in the constraint. 1654 // Note that we will divide everything by the gcd too. 1655 return CanonicalizeLinear(ct); 1656 } 1657 1658 // Any equality must be true modulo n. 1659 // 1660 // If the gcd of all but one term is not one, we can rewrite the last term using 1661 // an affine representative by considering the equality modulo that gcd. 1662 // As an heuristic, we only test the smallest term or small primes 2, 3, and 5. 1663 // 1664 // We also handle the special case of having two non-zero literals modulo 2. 1665 bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) { 1666 if (context_->ModelIsUnsat()) return false; 1667 if (ct->constraint_case() != ConstraintProto::kLinear) return false; 1668 if (ct->linear().domain().size() != 2) return false; 1669 if (ct->linear().domain(0) != ct->linear().domain(1)) return false; 1670 if (!ct->enforcement_literal().empty()) return false; 1671 1672 const int num_variables = ct->linear().vars().size(); 1673 if (num_variables < 2) return false; 1674 1675 std::vector<int> mod2_indices; 1676 std::vector<int> mod3_indices; 1677 std::vector<int> mod5_indices; 1678 1679 int64_t min_magnitude; 1680 int num_smallest = 0; 1681 int smallest_index; 1682 for (int i = 0; i < num_variables; ++i) { 1683 const int64_t magnitude = std::abs(ct->linear().coeffs(i)); 1684 if (num_smallest == 0 || magnitude < min_magnitude) { 1685 min_magnitude = magnitude; 1686 num_smallest = 1; 1687 smallest_index = i; 1688 } else if (magnitude == min_magnitude) { 1689 ++num_smallest; 1690 } 1691 1692 if (magnitude % 2 != 0) mod2_indices.push_back(i); 1693 if (magnitude % 3 != 0) mod3_indices.push_back(i); 1694 if (magnitude % 5 != 0) mod5_indices.push_back(i); 1695 } 1696 1697 if (mod2_indices.size() == 2) { 1698 bool ok = true; 1699 std::vector<int> literals; 1700 for (const int i : mod2_indices) { 1701 const int ref = ct->linear().vars(i); 1702 if (!context_->CanBeUsedAsLiteral(ref)) { 1703 ok = false; 1704 break; 1705 } 1706 literals.push_back(ref); 1707 } 1708 if (ok) { 1709 const int64_t rhs = std::abs(ct->linear().domain(0)); 1710 context_->UpdateRuleStats("linear: only two odd Booleans in equality"); 1711 if (rhs % 2) { 1712 context_->StoreBooleanEqualityRelation(literals[0], 1713 NegatedRef(literals[1])); 1714 } else { 1715 context_->StoreBooleanEqualityRelation(literals[0], literals[1]); 1716 } 1717 } 1718 } 1719 1720 // TODO(user): More than one reduction might be possible, so we will need 1721 // to call this again if we apply any of these reduction. 1722 if (mod2_indices.size() == 1) { 1723 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct); 1724 } 1725 if (mod3_indices.size() == 1) { 1726 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct); 1727 } 1728 if (mod5_indices.size() == 1) { 1729 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct); 1730 } 1731 if (num_smallest == 1) { 1732 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct); 1733 } 1734 1735 return false; 1736 } 1737 1738 bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) { 1739 DCHECK_EQ(ct->linear().vars().size(), 1); 1740 1741 // Size one constraint with no enforcement? 1742 if (!HasEnforcementLiteral(*ct)) { 1743 const int64_t coeff = RefIsPositive(ct->linear().vars(0)) 1744 ? ct->linear().coeffs(0) 1745 : -ct->linear().coeffs(0); 1746 context_->UpdateRuleStats("linear1: without enforcement"); 1747 const int var = PositiveRef(ct->linear().vars(0)); 1748 const Domain rhs = ReadDomainFromProto(ct->linear()); 1749 if (!context_->IntersectDomainWith(var, 1750 rhs.InverseMultiplicationBy(coeff))) { 1751 return false; 1752 } 1753 return RemoveConstraint(ct); 1754 } 1755 1756 // This is just an implication, lets convert it right away. 1757 if (context_->CanBeUsedAsLiteral(ct->linear().vars(0))) { 1758 const Domain rhs = ReadDomainFromProto(ct->linear()); 1759 const bool zero_ok = rhs.Contains(0); 1760 const bool one_ok = rhs.Contains(ct->linear().coeffs(0)); 1761 context_->UpdateRuleStats("linear1: is boolean implication"); 1762 if (!zero_ok && !one_ok) { 1763 return MarkConstraintAsFalse(ct); 1764 } 1765 if (zero_ok && one_ok) { 1766 return RemoveConstraint(ct); 1767 } 1768 const int ref = ct->linear().vars(0); 1769 if (zero_ok) { 1770 ct->mutable_bool_and()->add_literals(NegatedRef(ref)); 1771 } else { 1772 ct->mutable_bool_and()->add_literals(ref); 1773 } 1774 1775 // No var <-> constraint graph changes. 1776 // But this is no longer a linear1. 1777 return true; 1778 } 1779 1780 // If the constraint is literal => x in domain and x = abs(abs_arg), we can 1781 // replace x by abs_arg and hopefully remove the variable x later. 1782 int abs_arg; 1783 if (ct->linear().coeffs(0) == 1 && 1784 context_->GetAbsRelation(ct->linear().vars(0), &abs_arg)) { 1785 // TODO(user): Deal with coeff = -1, here or during canonicalization. 1786 context_->UpdateRuleStats("linear1: remove abs from abs(x) in domain"); 1787 const Domain implied_abs_target_domain = 1788 ReadDomainFromProto(ct->linear()) 1789 .IntersectionWith({0, std::numeric_limits<int64_t>::max()}) 1790 .IntersectionWith(context_->DomainOf(ct->linear().vars(0))); 1791 1792 if (implied_abs_target_domain.IsEmpty()) { 1793 return MarkConstraintAsFalse(ct); 1794 } 1795 1796 const Domain new_abs_var_domain = 1797 implied_abs_target_domain 1798 .UnionWith(implied_abs_target_domain.Negation()) 1799 .IntersectionWith(context_->DomainOf(abs_arg)); 1800 1801 if (new_abs_var_domain.IsEmpty()) { 1802 return MarkConstraintAsFalse(ct); 1803 } 1804 1805 ConstraintProto* new_ct = context_->working_model->add_constraints(); 1806 new_ct->set_name(ct->name()); 1807 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal(); 1808 auto* arg = new_ct->mutable_linear(); 1809 arg->add_vars(abs_arg); 1810 arg->add_coeffs(1); 1811 FillDomainInProto(new_abs_var_domain, new_ct->mutable_linear()); 1812 context_->UpdateNewConstraintsVariableUsage(); 1813 return RemoveConstraint(ct); 1814 } 1815 1816 // Detect encoding. 1817 if (ct->enforcement_literal_size() != 1 || 1818 (ct->linear().coeffs(0) != 1 && ct->linear().coeffs(0) == -1)) { 1819 return false; 1820 } 1821 1822 // Currently, we only use encoding during expansion, so when it is done, 1823 // there is no need to updates the maps. 1824 if (context_->ModelIsExpanded()) return false; 1825 1826 const int literal = ct->enforcement_literal(0); 1827 const LinearConstraintProto& linear = ct->linear(); 1828 const int ref = linear.vars(0); 1829 const int var = PositiveRef(ref); 1830 const int64_t coeff = 1831 RefIsPositive(ref) ? ct->linear().coeffs(0) : -ct->linear().coeffs(0); 1832 1833 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) { 1834 const int64_t value = RefIsPositive(ref) ? linear.domain(0) * coeff 1835 : -linear.domain(0) * coeff; 1836 if (!context_->DomainOf(var).Contains(value)) { 1837 if (!context_->SetLiteralToFalse(literal)) return false; 1838 } else if (context_->StoreLiteralImpliesVarEqValue(literal, var, value)) { 1839 // The domain is not actually modified, but we want to rescan the 1840 // constraints linked to this variable. See TODO below. 1841 context_->modified_domains.Set(var); 1842 } 1843 } else { 1844 const Domain complement = context_->DomainOf(ref).IntersectionWith( 1845 ReadDomainFromProto(linear).Complement()); 1846 if (complement.Size() != 1) return false; 1847 const int64_t value = RefIsPositive(ref) ? complement.Min() * coeff 1848 : -complement.Min() * coeff; 1849 if (context_->StoreLiteralImpliesVarNEqValue(literal, var, value)) { 1850 // The domain is not actually modified, but we want to rescan the 1851 // constraints linked to this variable. See TODO below. 1852 context_->modified_domains.Set(var); 1853 } 1854 } 1855 1856 // TODO(user): if we have l1 <=> x == value && l2 => x == value, we 1857 // could rewrite the second constraint into l2 => l1. 1858 context_->UpdateNewConstraintsVariableUsage(); 1859 return false; 1860 } 1861 1862 bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) { 1863 DCHECK_EQ(ct->linear().vars().size(), 2); 1864 1865 const LinearConstraintProto& arg = ct->linear(); 1866 const int var1 = arg.vars(0); 1867 const int var2 = arg.vars(1); 1868 const int64_t coeff1 = arg.coeffs(0); 1869 const int64_t coeff2 = arg.coeffs(1); 1870 1871 // If it is not an equality, we only presolve the constraint if one of 1872 // the variable is Boolean. Note that if both are Boolean, then a similar 1873 // reduction is done by PresolveLinearOnBooleans(). If we have an equality, 1874 // then the code below will do something stronger than this. 1875 // 1876 // TODO(user): We should probably instead generalize the code of 1877 // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary 1878 // propagate domain of enforced linear constraints, to detect Boolean that 1879 // must be true or false. This way we can do the same for longer constraints. 1880 const bool is_equality = 1881 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1); 1882 if (!is_equality) { 1883 int lit, var; 1884 int64_t value_on_true, coeff; 1885 if (context_->CanBeUsedAsLiteral(var1)) { 1886 lit = var1; 1887 value_on_true = coeff1; 1888 var = var2; 1889 coeff = coeff2; 1890 } else if (context_->CanBeUsedAsLiteral(var2)) { 1891 lit = var2; 1892 value_on_true = coeff2; 1893 var = var1; 1894 coeff = coeff1; 1895 } else { 1896 return false; 1897 } 1898 1899 const Domain rhs = ReadDomainFromProto(ct->linear()); 1900 const Domain rhs_if_true = 1901 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff); 1902 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff); 1903 const bool implied_false = 1904 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty(); 1905 const bool implied_true = 1906 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty(); 1907 if (implied_true && implied_false) { 1908 context_->UpdateRuleStats("linear2: infeasible."); 1909 return MarkConstraintAsFalse(ct); 1910 } else if (implied_true) { 1911 context_->UpdateRuleStats("linear2: Boolean with one feasible value."); 1912 1913 // => true. 1914 ConstraintProto* new_ct = context_->working_model->add_constraints(); 1915 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal(); 1916 new_ct->mutable_bool_and()->add_literals(lit); 1917 context_->UpdateNewConstraintsVariableUsage(); 1918 1919 // Rewrite to => var in rhs_if_true. 1920 ct->mutable_linear()->Clear(); 1921 ct->mutable_linear()->add_vars(var); 1922 ct->mutable_linear()->add_coeffs(1); 1923 FillDomainInProto(rhs_if_true, ct->mutable_linear()); 1924 return PresolveLinearOfSizeOne(ct) || true; 1925 } else if (implied_false) { 1926 context_->UpdateRuleStats("linear2: Boolean with one feasible value."); 1927 1928 // => false. 1929 ConstraintProto* new_ct = context_->working_model->add_constraints(); 1930 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal(); 1931 new_ct->mutable_bool_and()->add_literals(NegatedRef(lit)); 1932 context_->UpdateNewConstraintsVariableUsage(); 1933 1934 // Rewrite to => var in rhs_if_false. 1935 ct->mutable_linear()->Clear(); 1936 ct->mutable_linear()->add_vars(var); 1937 ct->mutable_linear()->add_coeffs(1); 1938 FillDomainInProto(rhs_if_false, ct->mutable_linear()); 1939 return PresolveLinearOfSizeOne(ct) || true; 1940 } else { 1941 // TODO(user): We can expand this into two linear1 constraints, I am not 1942 // 100% sure it is always good, so for now we don't do it. Note that the 1943 // effect of doing it or not is not really visible on the bench. Some 1944 // problem are better with it some better without. 1945 context_->UpdateRuleStats("TODO linear2: contains a Boolean."); 1946 return false; 1947 } 1948 } 1949 1950 // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs). 1951 const int64_t rhs = arg.domain(0); 1952 if (ct->enforcement_literal().empty()) { 1953 // Detect affine relation. 1954 // 1955 // TODO(user): it might be better to first add only the affine relation with 1956 // a coefficient of magnitude 1, and later the one with larger coeffs. 1957 bool added = false; 1958 if (coeff1 == 1) { 1959 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs); 1960 } else if (coeff2 == 1) { 1961 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs); 1962 } else if (coeff1 == -1) { 1963 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs); 1964 } else if (coeff2 == -1) { 1965 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs); 1966 } else { 1967 // In this case, we can solve the diophantine equation, and write 1968 // both x and y in term of a new affine representative z. 1969 // 1970 // Note that PresolveLinearEqualityWithModularInverse() will have the 1971 // same effect. 1972 // 1973 // We can also decide to fully expand the equality if the variables 1974 // are fully encoded. 1975 context_->UpdateRuleStats("TODO linear2: ax + by = cte"); 1976 } 1977 if (added) return RemoveConstraint(ct); 1978 } else { 1979 // We look ahead to detect solutions to ax + by == cte. 1980 int64_t a = coeff1; 1981 int64_t b = coeff2; 1982 int64_t cte = rhs; 1983 int64_t x0 = 0; 1984 int64_t y0 = 0; 1985 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) { 1986 context_->UpdateRuleStats( 1987 "linear2: implied ax + by = cte has no solutions"); 1988 return MarkConstraintAsFalse(ct); 1989 } 1990 const Domain reduced_domain = 1991 context_->DomainOf(var1) 1992 .AdditionWith(Domain(-x0)) 1993 .InverseMultiplicationBy(b) 1994 .IntersectionWith(context_->DomainOf(var2) 1995 .AdditionWith(Domain(-y0)) 1996 .InverseMultiplicationBy(-a)); 1997 1998 if (reduced_domain.IsEmpty()) { // no solution 1999 context_->UpdateRuleStats( 2000 "linear2: implied ax + by = cte has no solutions"); 2001 return MarkConstraintAsFalse(ct); 2002 } 2003 2004 if (reduced_domain.Size() == 1) { 2005 const int64_t z = reduced_domain.FixedValue(); 2006 const int64_t value1 = x0 + b * z; 2007 const int64_t value2 = y0 - a * z; 2008 2009 DCHECK(context_->DomainContains(var1, value1)); 2010 DCHECK(context_->DomainContains(var2, value2)); 2011 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs); 2012 2013 ConstraintProto* imply1 = context_->working_model->add_constraints(); 2014 *imply1->mutable_enforcement_literal() = ct->enforcement_literal(); 2015 imply1->mutable_linear()->add_vars(var1); 2016 imply1->mutable_linear()->add_coeffs(1); 2017 imply1->mutable_linear()->add_domain(value1); 2018 imply1->mutable_linear()->add_domain(value1); 2019 2020 ConstraintProto* imply2 = context_->working_model->add_constraints(); 2021 *imply2->mutable_enforcement_literal() = ct->enforcement_literal(); 2022 imply2->mutable_linear()->add_vars(var2); 2023 imply2->mutable_linear()->add_coeffs(1); 2024 imply2->mutable_linear()->add_domain(value2); 2025 imply2->mutable_linear()->add_domain(value2); 2026 context_->UpdateRuleStats( 2027 "linear2: implied ax + by = cte has only one solution"); 2028 context_->UpdateNewConstraintsVariableUsage(); 2029 return RemoveConstraint(ct); 2030 } 2031 } 2032 2033 return false; 2034 } 2035 2036 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) { 2037 if (ct->constraint_case() != ConstraintProto::kLinear) return false; 2038 if (context_->ModelIsUnsat()) return false; 2039 2040 if (ct->linear().vars().empty()) { 2041 context_->UpdateRuleStats("linear: empty"); 2042 const Domain rhs = ReadDomainFromProto(ct->linear()); 2043 if (rhs.Contains(0)) { 2044 return RemoveConstraint(ct); 2045 } else { 2046 return MarkConstraintAsFalse(ct); 2047 } 2048 } else if (ct->linear().vars().size() == 1) { 2049 return PresolveLinearOfSizeOne(ct); 2050 } else if (ct->linear().vars().size() == 2) { 2051 return PresolveLinearOfSizeTwo(ct); 2052 } 2053 2054 return false; 2055 } 2056 2057 namespace { 2058 2059 // Return true if the given domain only restrict the values with an upper bound. 2060 bool IsLeConstraint(const Domain& domain, const Domain& all_values) { 2061 return all_values 2062 .IntersectionWith( 2063 Domain(std::numeric_limits<int64_t>::min(), domain.Max())) 2064 .IsIncludedIn(domain); 2065 } 2066 2067 // Same as IsLeConstraint() but in the other direction. 2068 bool IsGeConstraint(const Domain& domain, const Domain& all_values) { 2069 return all_values 2070 .IntersectionWith( 2071 Domain(domain.Min(), std::numeric_limits<int64_t>::max())) 2072 .IsIncludedIn(domain); 2073 } 2074 2075 // In the equation terms + coeff * var_domain \included rhs, returns true if can 2076 // we always fix rhs to its min value for any value in terms. It is okay to 2077 // not be as generic as possible here. 2078 bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain, 2079 const Domain& terms, const Domain& rhs) { 2080 if (var_domain.NumIntervals() != 1) return false; 2081 if (std::abs(coeff) != 1) return false; 2082 2083 // If for all values in terms, there is one value below rhs.Min(), then 2084 // because we add only one integer interval, if there is a feasible value, it 2085 // can be at rhs.Min(). 2086 // 2087 // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple 2088 // or if terms is a multiple. 2089 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) { 2090 return true; 2091 } 2092 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) { 2093 return true; 2094 } 2095 return false; 2096 } 2097 2098 bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain, 2099 const Domain& terms, const Domain& rhs) { 2100 if (var_domain.NumIntervals() != 1) return false; 2101 if (std::abs(coeff) != 1) return false; 2102 2103 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) { 2104 return true; 2105 } 2106 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) { 2107 return true; 2108 } 2109 return false; 2110 } 2111 2112 // Remove from to_clear any entry not in current. 2113 void TakeIntersectionWith(const absl::flat_hash_set<int>& current, 2114 absl::flat_hash_set<int>* to_clear) { 2115 std::vector<int> new_set; 2116 for (const int c : *to_clear) { 2117 if (current.contains(c)) new_set.push_back(c); 2118 } 2119 to_clear->clear(); 2120 for (const int c : new_set) to_clear->insert(c); 2121 } 2122 2123 } // namespace 2124 2125 bool CpModelPresolver::DetectAndProcessOneSidedLinearConstraint( 2126 int c, ConstraintProto* ct) { 2127 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) { 2128 return false; 2129 } 2130 if (context_->ModelIsUnsat()) return false; 2131 if (context_->keep_all_feasible_solutions) return false; 2132 2133 // TODO(user): There is a bit of code and effort duplication with 2134 // PropagateDomainsInLinear(). Try to remove that. 2135 Domain implied_rhs(0); 2136 const int num_vars = ct->linear().vars().size(); 2137 for (int i = 0; i < num_vars; ++i) { 2138 const int ref = ct->linear().vars(i); 2139 const int64_t coeff = ct->linear().coeffs(i); 2140 implied_rhs = 2141 implied_rhs 2142 .AdditionWith(context_->DomainOf(ref).MultiplicationBy(coeff)) 2143 .RelaxIfTooComplex(); 2144 } 2145 2146 // Abort if trivial. 2147 const Domain old_rhs = ReadDomainFromProto(ct->linear()); 2148 if (implied_rhs.IsIncludedIn(old_rhs)) { 2149 context_->UpdateRuleStats("linear: always true"); 2150 return RemoveConstraint(ct); 2151 } 2152 2153 // Incorporate the implied rhs information. 2154 const Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs); 2155 if (rhs.IsEmpty()) { 2156 context_->UpdateRuleStats("linear: infeasible"); 2157 return MarkConstraintAsFalse(ct); 2158 } 2159 if (rhs != old_rhs) { 2160 context_->UpdateRuleStats("linear: simplified rhs"); 2161 } 2162 FillDomainInProto(rhs, ct->mutable_linear()); 2163 2164 // Detect if it is always good for a term of this constraint to move towards 2165 // its lower (resp. upper) bound. This is the same as saying that this 2166 // constraint only bound in one direction. 2167 const bool is_le_constraint = IsLeConstraint(rhs, implied_rhs); 2168 const bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs); 2169 if (!is_le_constraint && !is_ge_constraint) return false; 2170 CHECK_NE(is_le_constraint, is_ge_constraint); 2171 2172 bool recanonicalize = false; 2173 for (int i = 0; i < num_vars; ++i) { 2174 const int var = ct->linear().vars(i); 2175 const int64_t var_coeff = ct->linear().coeffs(i); 2176 CHECK(RefIsPositive(var)); 2177 2178 if ((var_coeff > 0) == is_ge_constraint) { 2179 context_->var_to_lb_only_constraints[var].insert(c); 2180 } else { 2181 context_->var_to_ub_only_constraints[var].insert(c); 2182 } 2183 2184 // Simple dual fixing: If for any feasible solution, any solution with var 2185 // higher (resp. lower) is also valid, then we can fix that variable to 2186 // its bound if it also moves the objective in the good direction. 2187 const bool is_in_objective = context_->VarToConstraints(var).contains(-1); 2188 const int size = 2189 context_->VarToConstraints(var).size() - (is_in_objective ? 1 : 0); 2190 const int64_t obj_coeff = 2191 is_in_objective ? context_->ObjectiveMap().at(var) : 0; 2192 2193 // We cannot fix anything if the domain of the objective is excluding 2194 // some objective values. 2195 if (obj_coeff != 0 && context_->ObjectiveDomainIsConstraining()) { 2196 continue; 2197 } 2198 2199 if (obj_coeff <= 0 && 2200 context_->var_to_lb_only_constraints[var].size() >= size) { 2201 TakeIntersectionWith(context_->VarToConstraints(var), 2202 &(context_->var_to_lb_only_constraints[var])); 2203 if (context_->var_to_lb_only_constraints[var].size() >= size) { 2204 if (!context_->IntersectDomainWith(var, Domain(context_->MaxOf(var)))) { 2205 return false; 2206 } 2207 context_->UpdateRuleStats("linear: dual fixing"); 2208 recanonicalize = true; 2209 continue; 2210 } 2211 } 2212 2213 if (obj_coeff >= 0 && 2214 context_->var_to_ub_only_constraints[var].size() >= size) { 2215 TakeIntersectionWith(context_->VarToConstraints(var), 2216 &(context_->var_to_ub_only_constraints[var])); 2217 if (context_->var_to_ub_only_constraints[var].size() >= size) { 2218 if (!context_->IntersectDomainWith(var, Domain(context_->MinOf(var)))) { 2219 return false; 2220 } 2221 context_->UpdateRuleStats("linear: dual fixing"); 2222 recanonicalize = true; 2223 continue; 2224 } 2225 } 2226 } 2227 2228 if (recanonicalize) return CanonicalizeLinear(ct); 2229 return false; 2230 } 2231 2232 bool CpModelPresolver::PropagateDomainsInLinear(int ct_index, 2233 ConstraintProto* ct) { 2234 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear || 2235 context_->ModelIsUnsat()) { 2236 return false; 2237 } 2238 2239 // Compute the implied rhs bounds from the variable ones. 2240 auto& term_domains = context_->tmp_term_domains; 2241 auto& left_domains = context_->tmp_left_domains; 2242 const int num_vars = ct->linear().vars_size(); 2243 term_domains.resize(num_vars + 1); 2244 left_domains.resize(num_vars + 1); 2245 left_domains[0] = Domain(0); 2246 for (int i = 0; i < num_vars; ++i) { 2247 const int var = ct->linear().vars(i); 2248 const int64_t coeff = ct->linear().coeffs(i); 2249 CHECK(RefIsPositive(var)); 2250 term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff); 2251 left_domains[i + 1] = 2252 left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex(); 2253 } 2254 const Domain& implied_rhs = left_domains[num_vars]; 2255 2256 // Abort if trivial. 2257 const Domain old_rhs = ReadDomainFromProto(ct->linear()); 2258 if (implied_rhs.IsIncludedIn(old_rhs)) { 2259 context_->UpdateRuleStats("linear: always true"); 2260 return RemoveConstraint(ct); 2261 } 2262 2263 // Incorporate the implied rhs information. 2264 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs); 2265 if (rhs.IsEmpty()) { 2266 context_->UpdateRuleStats("linear: infeasible"); 2267 return MarkConstraintAsFalse(ct); 2268 } 2269 if (rhs != old_rhs) { 2270 context_->UpdateRuleStats("linear: simplified rhs"); 2271 } 2272 FillDomainInProto(rhs, ct->mutable_linear()); 2273 2274 // Propagate the variable bounds. 2275 if (ct->enforcement_literal().size() > 1) return false; 2276 2277 bool new_bounds = false; 2278 bool recanonicalize = false; 2279 Domain negated_rhs = rhs.Negation(); 2280 Domain right_domain(0); 2281 Domain new_domain; 2282 Domain implied_term_domain; 2283 term_domains[num_vars] = Domain(0); 2284 for (int i = num_vars - 1; i >= 0; --i) { 2285 const int var = ct->linear().vars(i); 2286 const int64_t var_coeff = ct->linear().coeffs(i); 2287 right_domain = 2288 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex(); 2289 implied_term_domain = left_domains[i].AdditionWith(right_domain); 2290 new_domain = implied_term_domain.AdditionWith(negated_rhs) 2291 .InverseMultiplicationBy(-var_coeff); 2292 2293 if (ct->enforcement_literal().empty()) { 2294 // Push the new domain. 2295 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) { 2296 return true; 2297 } 2298 } else if (ct->enforcement_literal().size() == 1) { 2299 // We cannot push the new domain, but we can add some deduction. 2300 CHECK(RefIsPositive(var)); 2301 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) { 2302 context_->deductions.AddDeduction(ct->enforcement_literal(0), var, 2303 new_domain); 2304 } 2305 } 2306 2307 if (context_->IsFixed(var)) { 2308 // This will make sure we remove that fixed variable from the constraint. 2309 recanonicalize = true; 2310 continue; 2311 } 2312 2313 // The other transformations below require a non-reified constraint. 2314 if (!ct->enforcement_literal().empty()) continue; 2315 2316 // Given a variable that only appear in one constraint and in the 2317 // objective, for any feasible solution, it will be always better to move 2318 // this singleton variable as much as possible towards its good objective 2319 // direction. Sometime_exprs, we can detect that we will always be able to 2320 // do this until the only constraint of this singleton variable is tight. 2321 // 2322 // When this happens, we can make the constraint an equality. Note that it 2323 // might not always be good to restrict constraint like this, but in this 2324 // case, the RemoveSingletonInLinear() code should be able to remove this 2325 // variable altogether. 2326 if (rhs.Min() != rhs.Max() && 2327 context_->VariableWithCostIsUniqueAndRemovable(var)) { 2328 const int64_t obj_coeff = context_->ObjectiveMap().at(var); 2329 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0); 2330 bool fixed = false; 2331 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var), 2332 implied_term_domain, rhs)) { 2333 rhs = Domain(rhs.Min()); 2334 fixed = true; 2335 } 2336 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var), 2337 implied_term_domain, rhs)) { 2338 rhs = Domain(rhs.Max()); 2339 fixed = true; 2340 } 2341 if (fixed) { 2342 context_->UpdateRuleStats("linear: tightened into equality"); 2343 FillDomainInProto(rhs, ct->mutable_linear()); 2344 negated_rhs = rhs.Negation(); 2345 2346 // Restart the loop. 2347 i = num_vars; 2348 right_domain = Domain(0); 2349 continue; 2350 } 2351 } 2352 2353 // Can we perform some substitution? 2354 // 2355 // TODO(user): there is no guarantee we will not miss some since we might 2356 // not reprocess a constraint once other have been deleted. 2357 2358 // Skip affine constraint. It is more efficient to substitute them lazily 2359 // when we process other constraints. Note that if we relax the fact that 2360 // we substitute only equalities, we can deal with inequality of size 2 2361 // here. 2362 if (ct->linear().vars().size() <= 2) continue; 2363 2364 // TODO(user): We actually do not need a strict equality when 2365 // keep_all_feasible_solutions is false, but that simplifies things as the 2366 // SubstituteVariable() function cannot fail this way. 2367 if (rhs.Min() != rhs.Max()) continue; 2368 2369 // Only consider "implied free" variables. Note that the coefficient of 2370 // magnitude 1 is important otherwise we can't easily remove the 2371 // constraint since the fact that the sum of the other terms must be a 2372 // multiple of coeff will not be enforced anymore. 2373 if (context_->DomainOf(var) != new_domain) continue; 2374 if (std::abs(var_coeff) != 1) continue; 2375 if (context_->params().presolve_substitution_level() <= 0) continue; 2376 2377 // NOTE: The mapping doesn't allow us to remove a variable if 2378 // 'keep_all_feasible_solutions' is true. 2379 if (context_->keep_all_feasible_solutions) continue; 2380 2381 bool is_in_objective = false; 2382 if (context_->VarToConstraints(var).contains(-1)) { 2383 is_in_objective = true; 2384 DCHECK(context_->ObjectiveMap().contains(var)); 2385 } 2386 2387 // Only consider low degree columns. 2388 int col_size = context_->VarToConstraints(var).size(); 2389 if (is_in_objective) col_size--; 2390 const int row_size = ct->linear().vars_size(); 2391 2392 // This is actually an upper bound on the number of entries added since 2393 // some of them might already be present. 2394 const int num_entries_added = (row_size - 1) * (col_size - 1); 2395 const int num_entries_removed = col_size + row_size - 1; 2396 2397 if (num_entries_added > num_entries_removed) { 2398 continue; 2399 } 2400 2401 // Check pre-conditions on all the constraints in which this variable 2402 // appear. Basically they must all be linear. 2403 std::vector<int> others; 2404 bool abort = false; 2405 for (const int c : context_->VarToConstraints(var)) { 2406 if (c == kObjectiveConstraint) continue; 2407 if (c == kAffineRelationConstraint) { 2408 abort = true; 2409 break; 2410 } 2411 if (c == ct_index) continue; 2412 if (context_->working_model->constraints(c).constraint_case() != 2413 ConstraintProto::ConstraintCase::kLinear) { 2414 abort = true; 2415 break; 2416 } 2417 for (const int ref : 2418 context_->working_model->constraints(c).enforcement_literal()) { 2419 if (PositiveRef(ref) == var) { 2420 abort = true; 2421 break; 2422 } 2423 } 2424 others.push_back(c); 2425 } 2426 if (abort) continue; 2427 2428 // Do the actual substitution. 2429 for (const int c : others) { 2430 // TODO(user): In some corner cases, this might create integer overflow 2431 // issues. The danger is limited since the range of the linear 2432 // expression used in the definition do not exceed the domain of the 2433 // variable we substitute. 2434 const bool ok = SubstituteVariable( 2435 var, var_coeff, *ct, context_->working_model->mutable_constraints(c)); 2436 if (!ok) { 2437 // This can happen if the constraint was not canonicalized and the 2438 // variable is actually not there (we have var - var for instance). 2439 CanonicalizeLinear(context_->working_model->mutable_constraints(c)); 2440 } 2441 2442 // TODO(user): We should re-enqueue these constraints for presolve. 2443 context_->UpdateConstraintVariableUsage(c); 2444 } 2445 2446 // Substitute in objective. 2447 // This can only fail in corner cases. 2448 if (is_in_objective && 2449 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) { 2450 continue; 2451 } 2452 2453 context_->UpdateRuleStats( 2454 absl::StrCat("linear: variable substitution ", others.size())); 2455 2456 // The variable now only appear in its definition and we can remove it 2457 // because it was implied free. 2458 // 2459 // Tricky: If the linear constraint contains other variables that are only 2460 // used here, then the postsolve needs more info. We do need to indicate 2461 // that whatever the value of those other variables, we will have a way to 2462 // assign var. We do that by putting it fist. 2463 CHECK_EQ(context_->VarToConstraints(var).size(), 1); 2464 context_->MarkVariableAsRemoved(var); 2465 const int ct_index = context_->mapping_model->constraints().size(); 2466 *context_->mapping_model->add_constraints() = *ct; 2467 LinearConstraintProto* mapping_linear_ct = 2468 context_->mapping_model->mutable_constraints(ct_index) 2469 ->mutable_linear(); 2470 std::swap(mapping_linear_ct->mutable_vars()->at(0), 2471 mapping_linear_ct->mutable_vars()->at(i)); 2472 std::swap(mapping_linear_ct->mutable_coeffs()->at(0), 2473 mapping_linear_ct->mutable_coeffs()->at(i)); 2474 return RemoveConstraint(ct); 2475 } 2476 if (new_bounds) { 2477 context_->UpdateRuleStats("linear: reduced variable domains"); 2478 } 2479 if (recanonicalize) return CanonicalizeLinear(ct); 2480 return false; 2481 } 2482 2483 // Identify Boolean variable that makes the constraint always true when set to 2484 // true or false. Moves such literal to the constraint enforcement literals 2485 // list. 2486 // 2487 // We also generalize this to integer variable at one of their bound. 2488 // 2489 // This operation is similar to coefficient strengthening in the MIP world. 2490 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint( 2491 int ct_index, ConstraintProto* ct) { 2492 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear || 2493 context_->ModelIsUnsat()) { 2494 return; 2495 } 2496 2497 const LinearConstraintProto& arg = ct->linear(); 2498 const int num_vars = arg.vars_size(); 2499 2500 // No need to process size one constraints, they will be presolved separately. 2501 // We also do not want to split them in two. 2502 if (num_vars <= 1) return; 2503 2504 int64_t min_sum = 0; 2505 int64_t max_sum = 0; 2506 int64_t max_coeff_magnitude = 0; 2507 for (int i = 0; i < num_vars; ++i) { 2508 const int ref = arg.vars(i); 2509 const int64_t coeff = arg.coeffs(i); 2510 const int64_t term_a = coeff * context_->MinOf(ref); 2511 const int64_t term_b = coeff * context_->MaxOf(ref); 2512 max_coeff_magnitude = std::max(max_coeff_magnitude, std::abs(coeff)); 2513 min_sum += std::min(term_a, term_b); 2514 max_sum += std::max(term_a, term_b); 2515 } 2516 2517 // We can only extract enforcement literals if the maximum coefficient 2518 // magnitude is large enough. Note that we handle complex domain. 2519 // 2520 // TODO(user): Depending on how we split below, the threshold are not the 2521 // same. This is maybe not too important, we just don't split as often as we 2522 // could, but it is still unclear if splitting is good. 2523 const auto& domain = ct->linear().domain(); 2524 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum; 2525 const int64_t lb_threshold = max_sum - domain[1]; 2526 const Domain rhs_domain = ReadDomainFromProto(ct->linear()); 2527 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return; 2528 2529 // We need the constraint to be only bounded on one side in order to extract 2530 // enforcement literal. 2531 // 2532 // If it is boxed and we know that some coefficient are big enough (see test 2533 // above), then we split the constraint in two. That might not seems always 2534 // good, but for the CP propagation engine, we don't loose anything by doing 2535 // so, and for the LP we will regroup the constraints if they still have the 2536 // exact same coeff after the presolve. 2537 // 2538 // TODO(user): Creating two new constraints and removing the current one might 2539 // not be the most efficient, but it simplify the presolve code by not having 2540 // to do anything special to trigger a new presolving of these constraints. 2541 // Try to improve if this becomes a problem. 2542 // 2543 // TODO(user): At the end of the presolve we should probably remerge any 2544 // identical linear constraints. That also cover the corner cases where 2545 // constraints are just redundant... 2546 const bool lower_bounded = min_sum < rhs_domain.Min(); 2547 const bool upper_bounded = max_sum > rhs_domain.Max(); 2548 if (!lower_bounded && !upper_bounded) return; 2549 if (lower_bounded && upper_bounded) { 2550 context_->UpdateRuleStats("linear: split boxed constraint"); 2551 ConstraintProto* new_ct1 = context_->working_model->add_constraints(); 2552 *new_ct1 = *ct; 2553 if (!ct->name().empty()) { 2554 new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)")); 2555 } 2556 FillDomainInProto(Domain(min_sum, rhs_domain.Max()), 2557 new_ct1->mutable_linear()); 2558 2559 ConstraintProto* new_ct2 = context_->working_model->add_constraints(); 2560 *new_ct2 = *ct; 2561 if (!ct->name().empty()) { 2562 new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)")); 2563 } 2564 FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)), 2565 new_ct2->mutable_linear()); 2566 2567 context_->UpdateNewConstraintsVariableUsage(); 2568 return (void)RemoveConstraint(ct); 2569 } 2570 2571 // Any coefficient greater than this will cause the constraint to be trivially 2572 // satisfied when the variable move away from its bound. Note that as we 2573 // remove coefficient, the threshold do not change! 2574 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold; 2575 2576 // Do we only extract Booleans? 2577 // 2578 // Note that for now the default is false, and also there are problem calling 2579 // GetOrCreateVarValueEncoding() after expansion because we might have removed 2580 // the variable used in the encoding. 2581 const bool only_booleans = 2582 !context_->params().presolve_extract_integer_enforcement() || 2583 context_->ModelIsExpanded(); 2584 2585 // To avoid a quadratic loop, we will rewrite the linear expression at the 2586 // same time as we extract enforcement literals. 2587 int new_size = 0; 2588 int64_t rhs_offset = 0; 2589 bool some_integer_encoding_were_extracted = false; 2590 LinearConstraintProto* mutable_arg = ct->mutable_linear(); 2591 for (int i = 0; i < arg.vars_size(); ++i) { 2592 int ref = arg.vars(i); 2593 int64_t coeff = arg.coeffs(i); 2594 if (coeff < 0) { 2595 ref = NegatedRef(ref); 2596 coeff = -coeff; 2597 } 2598 2599 const bool is_boolean = context_->CanBeUsedAsLiteral(ref); 2600 if (context_->IsFixed(ref) || coeff < threshold || 2601 (only_booleans && !is_boolean)) { 2602 // We keep this term. 2603 mutable_arg->set_vars(new_size, mutable_arg->vars(i)); 2604 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i)); 2605 ++new_size; 2606 continue; 2607 } 2608 2609 if (is_boolean) { 2610 context_->UpdateRuleStats("linear: extracted enforcement literal"); 2611 } else { 2612 some_integer_encoding_were_extracted = true; 2613 context_->UpdateRuleStats( 2614 "linear: extracted integer enforcement literal"); 2615 } 2616 if (lower_bounded) { 2617 ct->add_enforcement_literal(is_boolean 2618 ? NegatedRef(ref) 2619 : context_->GetOrCreateVarValueEncoding( 2620 ref, context_->MinOf(ref))); 2621 rhs_offset -= coeff * context_->MinOf(ref); 2622 } else { 2623 ct->add_enforcement_literal(is_boolean 2624 ? ref 2625 : context_->GetOrCreateVarValueEncoding( 2626 ref, context_->MaxOf(ref))); 2627 rhs_offset -= coeff * context_->MaxOf(ref); 2628 } 2629 } 2630 mutable_arg->mutable_vars()->Truncate(new_size); 2631 mutable_arg->mutable_coeffs()->Truncate(new_size); 2632 FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg); 2633 if (some_integer_encoding_were_extracted) { 2634 context_->UpdateNewConstraintsVariableUsage(); 2635 context_->UpdateConstraintVariableUsage(ct_index); 2636 } 2637 } 2638 2639 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) { 2640 if (context_->ModelIsUnsat()) return; 2641 if (HasEnforcementLiteral(*ct)) return; 2642 const Domain rhs = ReadDomainFromProto(ct->linear()); 2643 2644 const LinearConstraintProto& arg = ct->linear(); 2645 const int num_vars = arg.vars_size(); 2646 int64_t min_sum = 0; 2647 int64_t max_sum = 0; 2648 for (int i = 0; i < num_vars; ++i) { 2649 const int ref = arg.vars(i); 2650 const int64_t coeff = arg.coeffs(i); 2651 const int64_t term_a = coeff * context_->MinOf(ref); 2652 const int64_t term_b = coeff * context_->MaxOf(ref); 2653 min_sum += std::min(term_a, term_b); 2654 max_sum += std::max(term_a, term_b); 2655 } 2656 for (const int type : {0, 1}) { 2657 std::vector<int> at_most_one; 2658 for (int i = 0; i < num_vars; ++i) { 2659 const int ref = arg.vars(i); 2660 const int64_t coeff = arg.coeffs(i); 2661 if (context_->MinOf(ref) != 0) continue; 2662 if (context_->MaxOf(ref) != 1) continue; 2663 2664 if (type == 0) { 2665 // TODO(user): we could add one more Boolean with a lower coeff as long 2666 // as we have lower_coeff + min_of_other_coeff > rhs.Max(). 2667 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) { 2668 at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref)); 2669 } 2670 } else { 2671 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) { 2672 at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref); 2673 } 2674 } 2675 } 2676 if (at_most_one.size() > 1) { 2677 if (type == 0) { 2678 context_->UpdateRuleStats("linear: extracted at most one (max)."); 2679 } else { 2680 context_->UpdateRuleStats("linear: extracted at most one (min)."); 2681 } 2682 ConstraintProto* new_ct = context_->working_model->add_constraints(); 2683 new_ct->set_name(ct->name()); 2684 for (const int ref : at_most_one) { 2685 new_ct->mutable_at_most_one()->add_literals(ref); 2686 } 2687 context_->UpdateNewConstraintsVariableUsage(); 2688 } 2689 } 2690 } 2691 2692 // Convert some linear constraint involving only Booleans to their Boolean 2693 // form. 2694 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) { 2695 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear || 2696 context_->ModelIsUnsat()) { 2697 return false; 2698 } 2699 2700 const LinearConstraintProto& arg = ct->linear(); 2701 const int num_vars = arg.vars_size(); 2702 int64_t min_coeff = std::numeric_limits<int64_t>::max(); 2703 int64_t max_coeff = 0; 2704 int64_t min_sum = 0; 2705 int64_t max_sum = 0; 2706 for (int i = 0; i < num_vars; ++i) { 2707 // We assume we already ran PresolveLinear(). 2708 const int var = arg.vars(i); 2709 const int64_t coeff = arg.coeffs(i); 2710 CHECK(RefIsPositive(var)); 2711 CHECK_NE(coeff, 0); 2712 if (context_->MinOf(var) != 0) return false; 2713 if (context_->MaxOf(var) != 1) return false; 2714 2715 if (coeff > 0) { 2716 max_sum += coeff; 2717 min_coeff = std::min(min_coeff, coeff); 2718 max_coeff = std::max(max_coeff, coeff); 2719 } else { 2720 // We replace the Boolean ref, by a ref to its negation (1 - x). 2721 min_sum += coeff; 2722 min_coeff = std::min(min_coeff, -coeff); 2723 max_coeff = std::max(max_coeff, -coeff); 2724 } 2725 } 2726 CHECK_LE(min_coeff, max_coeff); 2727 2728 // Detect trivially true/false constraints. Note that this is not necessarily 2729 // detected by PresolveLinear(). We do that here because we assume below 2730 // that this cannot happen. 2731 // 2732 // TODO(user): this could be generalized to constraint not containing only 2733 // Booleans. 2734 const Domain rhs_domain = ReadDomainFromProto(arg); 2735 if ((!rhs_domain.Contains(min_sum) && 2736 min_sum + min_coeff > rhs_domain.Max()) || 2737 (!rhs_domain.Contains(max_sum) && 2738 max_sum - min_coeff < rhs_domain.Min())) { 2739 context_->UpdateRuleStats("linear: all booleans and trivially false"); 2740 return MarkConstraintAsFalse(ct); 2741 } 2742 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) { 2743 context_->UpdateRuleStats("linear: all booleans and trivially true"); 2744 return RemoveConstraint(ct); 2745 } 2746 2747 // Detect clauses, reified ands, at_most_one. 2748 // 2749 // TODO(user): split a == 1 constraint or similar into a clause and an at 2750 // most one constraint? 2751 DCHECK(!rhs_domain.IsEmpty()); 2752 if (min_sum + min_coeff > rhs_domain.Max()) { 2753 // All Boolean are false if the reified literal is true. 2754 context_->UpdateRuleStats("linear: negative reified and"); 2755 const auto copy = arg; 2756 ct->mutable_bool_and()->clear_literals(); 2757 for (int i = 0; i < num_vars; ++i) { 2758 ct->mutable_bool_and()->add_literals( 2759 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i)); 2760 } 2761 PresolveBoolAnd(ct); 2762 return true; 2763 } else if (max_sum - min_coeff < rhs_domain.Min()) { 2764 // All Boolean are true if the reified literal is true. 2765 context_->UpdateRuleStats("linear: positive reified and"); 2766 const auto copy = arg; 2767 ct->mutable_bool_and()->clear_literals(); 2768 for (int i = 0; i < num_vars; ++i) { 2769 ct->mutable_bool_and()->add_literals( 2770 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i))); 2771 } 2772 PresolveBoolAnd(ct); 2773 return true; 2774 } else if (min_sum + min_coeff >= rhs_domain.Min() && 2775 rhs_domain.front().end >= max_sum) { 2776 // At least one Boolean is true. 2777 context_->UpdateRuleStats("linear: positive clause"); 2778 const auto copy = arg; 2779 ct->mutable_bool_or()->clear_literals(); 2780 for (int i = 0; i < num_vars; ++i) { 2781 ct->mutable_bool_or()->add_literals( 2782 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i))); 2783 } 2784 PresolveBoolOr(ct); 2785 return true; 2786 } else if (max_sum - min_coeff <= rhs_domain.Max() && 2787 rhs_domain.back().start <= min_sum) { 2788 // At least one Boolean is false. 2789 context_->UpdateRuleStats("linear: negative clause"); 2790 const auto copy = arg; 2791 ct->mutable_bool_or()->clear_literals(); 2792 for (int i = 0; i < num_vars; ++i) { 2793 ct->mutable_bool_or()->add_literals( 2794 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i)); 2795 } 2796 PresolveBoolOr(ct); 2797 return true; 2798 } else if (!HasEnforcementLiteral(*ct) && 2799 min_sum + max_coeff <= rhs_domain.Max() && 2800 min_sum + 2 * min_coeff > rhs_domain.Max() && 2801 rhs_domain.back().start <= min_sum) { 2802 // At most one Boolean is true. 2803 context_->UpdateRuleStats("linear: positive at most one"); 2804 const auto copy = arg; 2805 ct->mutable_at_most_one()->clear_literals(); 2806 for (int i = 0; i < num_vars; ++i) { 2807 ct->mutable_at_most_one()->add_literals( 2808 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i))); 2809 } 2810 return true; 2811 } else if (!HasEnforcementLiteral(*ct) && 2812 max_sum - max_coeff >= rhs_domain.Min() && 2813 max_sum - 2 * min_coeff < rhs_domain.Min() && 2814 rhs_domain.front().end >= max_sum) { 2815 // At most one Boolean is false. 2816 context_->UpdateRuleStats("linear: negative at most one"); 2817 const auto copy = arg; 2818 ct->mutable_at_most_one()->clear_literals(); 2819 for (int i = 0; i < num_vars; ++i) { 2820 ct->mutable_at_most_one()->add_literals( 2821 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i)); 2822 } 2823 return true; 2824 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 && 2825 min_sum < rhs_domain.Min() && 2826 min_sum + min_coeff >= rhs_domain.Min() && 2827 min_sum + 2 * min_coeff > rhs_domain.Max() && 2828 min_sum + max_coeff <= rhs_domain.Max()) { 2829 context_->UpdateRuleStats("linear: positive equal one"); 2830 ConstraintProto* exactly_one = context_->working_model->add_constraints(); 2831 exactly_one->set_name(ct->name()); 2832 for (int i = 0; i < num_vars; ++i) { 2833 exactly_one->mutable_exactly_one()->add_literals( 2834 arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i))); 2835 } 2836 context_->UpdateNewConstraintsVariableUsage(); 2837 return RemoveConstraint(ct); 2838 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 && 2839 max_sum > rhs_domain.Max() && 2840 max_sum - min_coeff <= rhs_domain.Max() && 2841 max_sum - 2 * min_coeff < rhs_domain.Min() && 2842 max_sum - max_coeff >= rhs_domain.Min()) { 2843 context_->UpdateRuleStats("linear: negative equal one"); 2844 ConstraintProto* exactly_one = context_->working_model->add_constraints(); 2845 exactly_one->set_name(ct->name()); 2846 for (int i = 0; i < num_vars; ++i) { 2847 exactly_one->mutable_exactly_one()->add_literals( 2848 arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i)); 2849 } 2850 context_->UpdateNewConstraintsVariableUsage(); 2851 return RemoveConstraint(ct); 2852 } 2853 2854 // Expand small expression into clause. 2855 // 2856 // TODO(user): This is bad from a LP relaxation perspective. Do not do that 2857 // now? On another hand it is good for the SAT presolving. 2858 if (num_vars > 3) return false; 2859 context_->UpdateRuleStats("linear: small Boolean expression"); 2860 2861 // Enumerate all possible value of the Booleans and add a clause if constraint 2862 // is false. TODO(user): the encoding could be made better in some cases. 2863 const int max_mask = (1 << arg.vars_size()); 2864 for (int mask = 0; mask < max_mask; ++mask) { 2865 int64_t value = 0; 2866 for (int i = 0; i < num_vars; ++i) { 2867 if ((mask >> i) & 1) value += arg.coeffs(i); 2868 } 2869 if (rhs_domain.Contains(value)) continue; 2870 2871 // Add a new clause to exclude this bad assignment. 2872 ConstraintProto* new_ct = context_->working_model->add_constraints(); 2873 auto* new_arg = new_ct->mutable_bool_or(); 2874 if (HasEnforcementLiteral(*ct)) { 2875 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal(); 2876 } 2877 for (int i = 0; i < num_vars; ++i) { 2878 new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i)) 2879 : arg.vars(i)); 2880 } 2881 } 2882 2883 context_->UpdateNewConstraintsVariableUsage(); 2884 return RemoveConstraint(ct); 2885 } 2886 2887 bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) { 2888 if (context_->ModelIsUnsat()) return false; 2889 IntervalConstraintProto* interval = ct->mutable_interval(); 2890 2891 // If the size is < 0, then the interval cannot be performed. 2892 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) { 2893 context_->UpdateRuleStats("interval: negative size implies unperformed"); 2894 return MarkConstraintAsFalse(ct); 2895 } 2896 2897 bool changed = false; 2898 if (ct->enforcement_literal().empty()) { 2899 // Size can't be negative. 2900 if (!context_->IntersectDomainWith( 2901 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()), 2902 &changed)) { 2903 return false; 2904 } 2905 context_->UpdateRuleStats( 2906 "interval: performed intervals must have a positive size"); 2907 } 2908 2909 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start()); 2910 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size()); 2911 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end()); 2912 return changed; 2913 } 2914 2915 // TODO(user): avoid code duplication between expand and presolve. 2916 bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) { 2917 const int size = ct->inverse().f_direct().size(); 2918 bool changed = false; 2919 2920 // Make sure the domains are included in [0, size - 1). 2921 for (const int ref : ct->inverse().f_direct()) { 2922 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) { 2923 VLOG(1) << "Empty domain for a variable in ExpandInverse()"; 2924 return false; 2925 } 2926 } 2927 for (const int ref : ct->inverse().f_inverse()) { 2928 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) { 2929 VLOG(1) << "Empty domain for a variable in ExpandInverse()"; 2930 return false; 2931 } 2932 } 2933 2934 // Detect duplicated variable. 2935 // Even with negated variables, the reduced domain in [0..size - 1] 2936 // implies that the constraint is infeasible if ref and its negation 2937 // appear together. 2938 { 2939 absl::flat_hash_set<int> direct_vars; 2940 for (const int ref : ct->inverse().f_direct()) { 2941 const auto [it, inserted] = direct_vars.insert(PositiveRef(ref)); 2942 if (!inserted) { 2943 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable"); 2944 } 2945 } 2946 2947 absl::flat_hash_set<int> inverse_vars; 2948 for (const int ref : ct->inverse().f_inverse()) { 2949 const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref)); 2950 if (!inserted) { 2951 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable"); 2952 } 2953 } 2954 } 2955 2956 // Propagate from one vector to its counterpart. 2957 // Note this reaches the fixpoint as there is a one to one mapping between 2958 // (variable-value) pairs in each vector. 2959 const auto filter_inverse_domain = 2960 [this, size, &changed](const auto& direct, const auto& inverse) { 2961 // Build the set of values in the inverse vector. 2962 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size); 2963 for (int i = 0; i < size; ++i) { 2964 const Domain domain = context_->DomainOf(inverse[i]); 2965 for (const int64_t j : domain.Values()) { 2966 inverse_values[i].insert(j); 2967 } 2968 } 2969 2970 // Propagate from the inverse vector to the direct vector. Reduce the 2971 // domains of each variable in the direct vector by checking that the 2972 // inverse value exists. 2973 std::vector<int64_t> possible_values; 2974 for (int i = 0; i < size; ++i) { 2975 possible_values.clear(); 2976 const Domain domain = context_->DomainOf(direct[i]); 2977 bool removed_value = false; 2978 for (const int64_t j : domain.Values()) { 2979 if (inverse_values[j].contains(i)) { 2980 possible_values.push_back(j); 2981 } else { 2982 removed_value = true; 2983 } 2984 } 2985 if (removed_value) { 2986 changed = true; 2987 if (!context_->IntersectDomainWith( 2988 direct[i], Domain::FromValues(possible_values))) { 2989 VLOG(1) << "Empty domain for a variable in ExpandInverse()"; 2990 return false; 2991 } 2992 } 2993 } 2994 return true; 2995 }; 2996 2997 if (!filter_inverse_domain(ct->inverse().f_direct(), 2998 ct->inverse().f_inverse())) { 2999 return false; 3000 } 3001 3002 if (!filter_inverse_domain(ct->inverse().f_inverse(), 3003 ct->inverse().f_direct())) { 3004 return false; 3005 } 3006 3007 if (changed) { 3008 context_->UpdateRuleStats("inverse: reduce domains"); 3009 } 3010 3011 return false; 3012 } 3013 3014 bool CpModelPresolver::PresolveElement(ConstraintProto* ct) { 3015 if (context_->ModelIsUnsat()) return false; 3016 3017 if (ct->element().vars().empty()) { 3018 context_->UpdateRuleStats("element: empty array"); 3019 return context_->NotifyThatModelIsUnsat(); 3020 } 3021 3022 const int index_ref = ct->element().index(); 3023 const int target_ref = ct->element().target(); 3024 3025 // TODO(user): think about this once we do have such constraint. 3026 if (HasEnforcementLiteral(*ct)) return false; 3027 3028 bool all_constants = true; 3029 absl::flat_hash_set<int64_t> constant_set; 3030 bool all_included_in_target_domain = true; 3031 3032 { 3033 bool reduced_index_domain = false; 3034 if (!context_->IntersectDomainWith(index_ref, 3035 Domain(0, ct->element().vars_size() - 1), 3036 &reduced_index_domain)) { 3037 return false; 3038 } 3039 3040 // Filter impossible index values if index == +/- target 3041 // 3042 // Note that this must be done before the unique_index/target rule. 3043 if (PositiveRef(target_ref) == PositiveRef(index_ref)) { 3044 std::vector<int64_t> possible_indices; 3045 const Domain& index_domain = context_->DomainOf(index_ref); 3046 for (const int64_t index_value : index_domain.Values()) { 3047 const int ref = ct->element().vars(index_value); 3048 const int64_t target_value = 3049 target_ref == index_ref ? index_value : -index_value; 3050 if (context_->DomainContains(ref, target_value)) { 3051 possible_indices.push_back(target_value); 3052 } 3053 } 3054 if (possible_indices.size() < index_domain.Size()) { 3055 if (!context_->IntersectDomainWith( 3056 index_ref, Domain::FromValues(possible_indices))) { 3057 return true; 3058 } 3059 context_->UpdateRuleStats( 3060 "element: reduced index domain when target equals index"); 3061 } 3062 } 3063 3064 // Filter possible index values. Accumulate variable domains to build 3065 // a possible target domain. 3066 Domain infered_domain; 3067 const Domain& initial_index_domain = context_->DomainOf(index_ref); 3068 const Domain& target_domain = context_->DomainOf(target_ref); 3069 std::vector<int64_t> possible_indices; 3070 for (const int64_t value : initial_index_domain.Values()) { 3071 CHECK_GE(value, 0); 3072 CHECK_LT(value, ct->element().vars_size()); 3073 const int ref = ct->element().vars(value); 3074 const Domain& domain = context_->DomainOf(ref); 3075 if (domain.IntersectionWith(target_domain).IsEmpty()) continue; 3076 possible_indices.push_back(value); 3077 if (domain.IsFixed()) { 3078 constant_set.insert(domain.Min()); 3079 } else { 3080 all_constants = false; 3081 } 3082 if (!domain.IsIncludedIn(target_domain)) { 3083 all_included_in_target_domain = false; 3084 } 3085 infered_domain = infered_domain.UnionWith(domain); 3086 } 3087 if (possible_indices.size() < initial_index_domain.Size()) { 3088 if (!context_->IntersectDomainWith( 3089 index_ref, Domain::FromValues(possible_indices))) { 3090 return true; 3091 } 3092 context_->UpdateRuleStats("element: reduced index domain"); 3093 } 3094 bool domain_modified = false; 3095 if (!context_->IntersectDomainWith(target_ref, infered_domain, 3096 &domain_modified)) { 3097 return true; 3098 } 3099 if (domain_modified) { 3100 context_->UpdateRuleStats("element: reduced target domain"); 3101 } 3102 } 3103 3104 // If the index is fixed, this is a equality constraint. 3105 if (context_->IsFixed(index_ref)) { 3106 const int var = ct->element().vars(context_->MinOf(index_ref)); 3107 if (var != target_ref) { 3108 LinearConstraintProto* const lin = 3109 context_->working_model->add_constraints()->mutable_linear(); 3110 lin->add_vars(var); 3111 lin->add_coeffs(-1); 3112 lin->add_vars(target_ref); 3113 lin->add_coeffs(1); 3114 lin->add_domain(0); 3115 lin->add_domain(0); 3116 context_->UpdateNewConstraintsVariableUsage(); 3117 } 3118 context_->UpdateRuleStats("element: fixed index"); 3119 return RemoveConstraint(ct); 3120 } 3121 3122 // If the accessible part of the array is made of a single constant value, 3123 // then we do not care about the index. And, because of the previous target 3124 // domain reduction, the target is also fixed. 3125 if (all_constants && constant_set.size() == 1) { 3126 CHECK(context_->IsFixed(target_ref)); 3127 context_->UpdateRuleStats("element: one value array"); 3128 return RemoveConstraint(ct); 3129 } 3130 3131 // Special case when the index is boolean, and the array does not contain 3132 // variables. 3133 if (context_->MinOf(index_ref) == 0 && context_->MaxOf(index_ref) == 1 && 3134 all_constants) { 3135 const int64_t v0 = context_->MinOf(ct->element().vars(0)); 3136 const int64_t v1 = context_->MinOf(ct->element().vars(1)); 3137 3138 LinearConstraintProto* const lin = 3139 context_->working_model->add_constraints()->mutable_linear(); 3140 lin->add_vars(target_ref); 3141 lin->add_coeffs(1); 3142 lin->add_vars(index_ref); 3143 lin->add_coeffs(v0 - v1); 3144 lin->add_domain(v0); 3145 lin->add_domain(v0); 3146 context_->UpdateNewConstraintsVariableUsage(); 3147 context_->UpdateRuleStats("element: linearize constant element of size 2"); 3148 return RemoveConstraint(ct); 3149 } 3150 3151 // If the index has a canonical affine representative, rewrite the element. 3152 const AffineRelation::Relation r_index = 3153 context_->GetAffineRelation(index_ref); 3154 if (r_index.representative != index_ref) { 3155 // Checks the domains are synchronized. 3156 if (context_->DomainOf(r_index.representative).Size() > 3157 context_->DomainOf(index_ref).Size()) { 3158 // Postpone, we will come back later when domains are synchronized. 3159 return true; 3160 } 3161 const int r_ref = r_index.representative; 3162 const int64_t r_min = context_->MinOf(r_ref); 3163 const int64_t r_max = context_->MaxOf(r_ref); 3164 const int array_size = ct->element().vars_size(); 3165 if (r_min != 0) { 3166 context_->UpdateRuleStats("TODO element: representative has bad domain"); 3167 } else if (r_index.offset >= 0 && r_index.offset < array_size && 3168 r_index.offset + r_max * r_index.coeff >= 0 && 3169 r_index.offset + r_max * r_index.coeff < array_size) { 3170 // This will happen eventually when domains are synchronized. 3171 ElementConstraintProto* const element = 3172 context_->working_model->add_constraints()->mutable_element(); 3173 for (int64_t v = 0; v <= r_max; ++v) { 3174 const int64_t scaled_index = v * r_index.coeff + r_index.offset; 3175 CHECK_GE(scaled_index, 0); 3176 CHECK_LT(scaled_index, array_size); 3177 element->add_vars(ct->element().vars(scaled_index)); 3178 } 3179 element->set_index(r_ref); 3180 element->set_target(target_ref); 3181 3182 if (r_index.coeff == 1) { 3183 context_->UpdateRuleStats("element: shifed index "); 3184 } else { 3185 context_->UpdateRuleStats("element: scaled index"); 3186 } 3187 context_->UpdateNewConstraintsVariableUsage(); 3188 return RemoveConstraint(ct); 3189 } 3190 } 3191 3192 // Should have been taken care of ealier. 3193 DCHECK(!context_->IsFixed(index_ref)); 3194 3195 // If a variable (target or index) appears only in this constraint, it does 3196 // not necessarily mean that we can remove the constraint, as the variable 3197 // can be used multiple time_exprs in the element. So let's count the local 3198 // uses of each variable. 3199 absl::flat_hash_map<int, int> local_var_occurrence_counter; 3200 local_var_occurrence_counter[PositiveRef(index_ref)]++; 3201 local_var_occurrence_counter[PositiveRef(target_ref)]++; 3202 3203 for (const ClosedInterval interval : context_->DomainOf(index_ref)) { 3204 for (int64_t value = interval.start; value <= interval.end; ++value) { 3205 DCHECK_GE(value, 0); 3206 DCHECK_LT(value, ct->element().vars_size()); 3207 const int ref = ct->element().vars(value); 3208 local_var_occurrence_counter[PositiveRef(ref)]++; 3209 } 3210 } 3211 3212 if (context_->VariableIsUniqueAndRemovable(index_ref) && 3213 local_var_occurrence_counter.at(PositiveRef(index_ref)) == 1) { 3214 if (all_constants) { 3215 // This constraint is just here to reduce the domain of the target! We can 3216 // add it to the mapping_model to reconstruct the index value during 3217 // postsolve and get rid of it now. 3218 context_->UpdateRuleStats("element: trivial target domain reduction"); 3219 context_->MarkVariableAsRemoved(index_ref); 3220 *(context_->mapping_model->add_constraints()) = *ct; 3221 return RemoveConstraint(ct); 3222 } else { 3223 context_->UpdateRuleStats("TODO element: index not used elsewhere"); 3224 } 3225 } 3226 3227 if (!context_->IsFixed(target_ref) && 3228 context_->VariableIsUniqueAndRemovable(target_ref) && 3229 local_var_occurrence_counter.at(PositiveRef(target_ref)) == 1) { 3230 if (all_included_in_target_domain) { 3231 context_->UpdateRuleStats("element: trivial index domain reduction"); 3232 context_->MarkVariableAsRemoved(target_ref); 3233 *(context_->mapping_model->add_constraints()) = *ct; 3234 return RemoveConstraint(ct); 3235 } else { 3236 context_->UpdateRuleStats("TODO element: target not used elsewhere"); 3237 } 3238 } 3239 3240 return false; 3241 } 3242 3243 bool CpModelPresolver::PresolveTable(ConstraintProto* ct) { 3244 if (context_->ModelIsUnsat()) return false; 3245 if (HasEnforcementLiteral(*ct)) return false; 3246 if (ct->table().vars().empty()) { 3247 context_->UpdateRuleStats("table: empty constraint"); 3248 return RemoveConstraint(ct); 3249 } 3250 3251 const int initial_num_vars = ct->table().vars_size(); 3252 bool changed = true; 3253 3254 // Query existing affine relations. 3255 std::vector<AffineRelation::Relation> affine_relations; 3256 std::vector<int64_t> old_var_lb; 3257 std::vector<int64_t> old_var_ub; 3258 { 3259 for (int v = 0; v < initial_num_vars; ++v) { 3260 const int ref = ct->table().vars(v); 3261 AffineRelation::Relation r = context_->GetAffineRelation(ref); 3262 affine_relations.push_back(r); 3263 old_var_lb.push_back(context_->MinOf(ref)); 3264 old_var_ub.push_back(context_->MaxOf(ref)); 3265 if (r.representative != ref) { 3266 changed = true; 3267 ct->mutable_table()->set_vars(v, r.representative); 3268 context_->UpdateRuleStats( 3269 "table: replace variable by canonical affine one"); 3270 } 3271 } 3272 } 3273 3274 // Check for duplicate occurrences of variables. 3275 // If the ith index is -1, then the variable is not a duplicate of a smaller 3276 // index variable. It if is != from -1, then the values stored is the new 3277 // index of the first occurrence of the variable. 3278 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence( 3279 initial_num_vars, -1); 3280 // If == -1, then the variable is a duplicate of a smaller index variable. 3281 std::vector<int> old_index_to_new_index(initial_num_vars, -1); 3282 int num_vars = 0; 3283 { 3284 absl::flat_hash_map<int, int> first_visit; 3285 for (int p = 0; p < initial_num_vars; ++p) { 3286 const int ref = ct->table().vars(p); 3287 const int var = PositiveRef(ref); 3288 const auto& it = first_visit.find(var); 3289 if (it != first_visit.end()) { 3290 const int previous = it->second; 3291 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous; 3292 context_->UpdateRuleStats("table: duplicate variables"); 3293 changed = true; 3294 } else { 3295 ct->mutable_table()->set_vars(num_vars, ref); 3296 first_visit[var] = num_vars; 3297 old_index_to_new_index[p] = num_vars; 3298 num_vars++; 3299 } 3300 } 3301 3302 if (num_vars < initial_num_vars) { 3303 ct->mutable_table()->mutable_vars()->Truncate(num_vars); 3304 } 3305 } 3306 3307 // Check each tuple for validity w.r.t. affine relations, variable domains, 3308 // and consistency with duplicate variables. Reduce the size of the tuple in 3309 // case of duplicate variables. 3310 std::vector<std::vector<int64_t>> new_tuples; 3311 const int initial_num_tuples = ct->table().values_size() / initial_num_vars; 3312 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars); 3313 3314 { 3315 std::vector<int64_t> tuple(num_vars); 3316 new_tuples.reserve(initial_num_tuples); 3317 for (int i = 0; i < initial_num_tuples; ++i) { 3318 bool delete_row = false; 3319 std::string tmp; 3320 for (int j = 0; j < initial_num_vars; ++j) { 3321 const int64_t old_value = ct->table().values(i * initial_num_vars + j); 3322 3323 // Corner case to avoid overflow, assuming the domain where already 3324 // propagated between a variable and its affine representative. 3325 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) { 3326 delete_row = true; 3327 break; 3328 } 3329 3330 // Affine relations are defined on the initial variables. 3331 const AffineRelation::Relation& r = affine_relations[j]; 3332 const int64_t value = (old_value - r.offset) / r.coeff; 3333 if (value * r.coeff + r.offset != old_value) { 3334 // Value not reachable by affine relation. 3335 delete_row = true; 3336 break; 3337 } 3338 const int mapped_position = old_index_to_new_index[j]; 3339 if (mapped_position == -1) { // The current variable is duplicate. 3340 const int new_index_of_first_occurrence = 3341 old_index_of_duplicate_to_new_index_of_first_occurrence[j]; 3342 if (value != tuple[new_index_of_first_occurrence]) { 3343 delete_row = true; 3344 break; 3345 } 3346 } else { 3347 const int ref = ct->table().vars(mapped_position); 3348 if (!context_->DomainContains(ref, value)) { 3349 delete_row = true; 3350 break; 3351 } 3352 tuple[mapped_position] = value; 3353 } 3354 } 3355 if (delete_row) { 3356 changed = true; 3357 continue; 3358 } 3359 new_tuples.push_back(tuple); 3360 for (int j = 0; j < num_vars; ++j) { 3361 new_domains[j].insert(tuple[j]); 3362 } 3363 } 3364 gtl::STLSortAndRemoveDuplicates(&new_tuples); 3365 if (new_tuples.size() < initial_num_tuples) { 3366 context_->UpdateRuleStats("table: removed rows"); 3367 } 3368 } 3369 3370 // Update the list of tuples if needed. 3371 if (changed) { 3372 ct->mutable_table()->clear_values(); 3373 for (const std::vector<int64_t>& t : new_tuples) { 3374 for (const int64_t v : t) { 3375 ct->mutable_table()->add_values(v); 3376 } 3377 } 3378 } 3379 3380 // Nothing more to do for negated tables. 3381 if (ct->table().negated()) return changed; 3382 3383 // Filter the variable domains. 3384 for (int j = 0; j < num_vars; ++j) { 3385 const int ref = ct->table().vars(j); 3386 if (!context_->IntersectDomainWith( 3387 PositiveRef(ref), 3388 Domain::FromValues(std::vector<int64_t>(new_domains[j].begin(), 3389 new_domains[j].end())), 3390 &changed)) { 3391 return true; 3392 } 3393 } 3394 if (changed) { 3395 context_->UpdateRuleStats("table: reduced variable domains"); 3396 } 3397 if (num_vars == 1) { 3398 // Now that we properly update the domain, we can remove the constraint. 3399 context_->UpdateRuleStats("table: only one column!"); 3400 return RemoveConstraint(ct); 3401 } 3402 3403 // Check that the table is not complete or just here to exclude a few tuples. 3404 double prod = 1.0; 3405 for (int j = 0; j < num_vars; ++j) prod *= new_domains[j].size(); 3406 if (prod == new_tuples.size()) { 3407 context_->UpdateRuleStats("table: all tuples!"); 3408 return RemoveConstraint(ct); 3409 } 3410 3411 // Convert to the negated table if we gain a lot of entries by doing so. 3412 // Note however that currently the negated table do not propagate as much as 3413 // it could. 3414 if (new_tuples.size() > 0.7 * prod) { 3415 // Enumerate all tuples. 3416 std::vector<std::vector<int64_t>> var_to_values(num_vars); 3417 for (int j = 0; j < num_vars; ++j) { 3418 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end()); 3419 } 3420 std::vector<std::vector<int64_t>> all_tuples(prod); 3421 for (int i = 0; i < prod; ++i) { 3422 all_tuples[i].resize(num_vars); 3423 int index = i; 3424 for (int j = 0; j < num_vars; ++j) { 3425 all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()]; 3426 index /= var_to_values[j].size(); 3427 } 3428 } 3429 gtl::STLSortAndRemoveDuplicates(&all_tuples); 3430 3431 // Compute the complement of new_tuples. 3432 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size()); 3433 std::set_difference(all_tuples.begin(), all_tuples.end(), 3434 new_tuples.begin(), new_tuples.end(), diff.begin()); 3435 3436 // Negate the constraint. 3437 ct->mutable_table()->set_negated(!ct->table().negated()); 3438 ct->mutable_table()->clear_values(); 3439 for (const std::vector<int64_t>& t : diff) { 3440 for (const int64_t v : t) ct->mutable_table()->add_values(v); 3441 } 3442 context_->UpdateRuleStats("table: negated"); 3443 } 3444 return changed; 3445 } 3446 3447 bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) { 3448 if (context_->ModelIsUnsat()) return false; 3449 if (HasEnforcementLiteral(*ct)) return false; 3450 3451 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff(); 3452 3453 bool constraint_has_changed = false; 3454 for (LinearExpressionProto& exp : 3455 *(ct->mutable_all_diff()->mutable_exprs())) { 3456 constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp); 3457 } 3458 3459 for (;;) { 3460 const int size = all_diff.exprs_size(); 3461 if (size == 0) { 3462 context_->UpdateRuleStats("all_diff: empty constraint"); 3463 return RemoveConstraint(ct); 3464 } 3465 if (size == 1) { 3466 context_->UpdateRuleStats("all_diff: only one variable"); 3467 return RemoveConstraint(ct); 3468 } 3469 3470 bool something_was_propagated = false; 3471 std::vector<LinearExpressionProto> kept_expressions; 3472 for (int i = 0; i < size; ++i) { 3473 if (!context_->IsFixed(all_diff.exprs(i))) { 3474 kept_expressions.push_back(all_diff.exprs(i)); 3475 continue; 3476 } 3477 3478 const int64_t value = context_->MinOf(all_diff.exprs(i)); 3479 bool propagated = false; 3480 for (int j = 0; j < size; ++j) { 3481 if (i == j) continue; 3482 if (context_->DomainContains(all_diff.exprs(j), value)) { 3483 if (!context_->IntersectDomainWith(all_diff.exprs(j), 3484 Domain(value).Complement())) { 3485 return true; 3486 } 3487 propagated = true; 3488 } 3489 } 3490 if (propagated) { 3491 context_->UpdateRuleStats("all_diff: propagated fixed expressions"); 3492 something_was_propagated = true; 3493 } 3494 } 3495 3496 // CanonicalizeLinearExpression() made sure that only positive variable 3497 // appears here, so this order will put expr and -expr one after the other. 3498 std::sort( 3499 kept_expressions.begin(), kept_expressions.end(), 3500 [](const LinearExpressionProto& expr_a, 3501 const LinearExpressionProto& expr_b) { 3502 DCHECK_EQ(expr_a.vars_size(), 1); 3503 DCHECK_EQ(expr_b.vars_size(), 1); 3504 const int ref_a = expr_a.vars(0); 3505 const int ref_b = expr_b.vars(0); 3506 const int64_t coeff_a = expr_a.coeffs(0); 3507 const int64_t coeff_b = expr_b.coeffs(0); 3508 const int64_t abs_coeff_a = std::abs(coeff_a); 3509 const int64_t abs_coeff_b = std::abs(coeff_b); 3510 const int64_t offset_a = expr_a.offset(); 3511 const int64_t offset_b = expr_b.offset(); 3512 const int64_t abs_offset_a = std::abs(offset_a); 3513 const int64_t abs_offset_b = std::abs(offset_b); 3514 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) < 3515 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b); 3516 }); 3517 3518 // TODO(user): improve algorithm if of (a + offset) and (-a - offset) 3519 // might not be together if (a - offset) is present. 3520 3521 for (int i = 1; i < kept_expressions.size(); ++i) { 3522 if (LinearExpressionProtosAreEqual(kept_expressions[i], 3523 kept_expressions[i - 1], 1)) { 3524 return context_->NotifyThatModelIsUnsat( 3525 "Duplicate variable in all_diff"); 3526 } 3527 if (LinearExpressionProtosAreEqual(kept_expressions[i], 3528 kept_expressions[i - 1], -1)) { 3529 bool domain_modified = false; 3530 if (!context_->IntersectDomainWith(kept_expressions[i], 3531 Domain(0).Complement(), 3532 &domain_modified)) { 3533 return false; 3534 } 3535 if (domain_modified) { 3536 context_->UpdateRuleStats( 3537 "all_diff: remove 0 from expression appearing with its " 3538 "opposite."); 3539 } 3540 } 3541 } 3542 3543 if (kept_expressions.size() < all_diff.exprs_size()) { 3544 all_diff.clear_exprs(); 3545 for (const LinearExpressionProto& expr : kept_expressions) { 3546 *all_diff.add_exprs() = expr; 3547 } 3548 context_->UpdateRuleStats("all_diff: removed fixed variables"); 3549 something_was_propagated = true; 3550 constraint_has_changed = true; 3551 if (kept_expressions.size() <= 1) continue; 3552 } 3553 3554 // Propagate mandatory value if the all diff is actually a permutation. 3555 CHECK_GE(all_diff.exprs_size(), 2); 3556 Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0)); 3557 for (int i = 1; i < all_diff.exprs_size(); ++i) { 3558 domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(i))); 3559 } 3560 if (all_diff.exprs_size() == domain.Size()) { 3561 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>> 3562 value_to_exprs; 3563 for (const LinearExpressionProto& expr : all_diff.exprs()) { 3564 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) { 3565 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr); 3566 } 3567 } 3568 bool propagated = false; 3569 for (const auto& it : value_to_exprs) { 3570 if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) { 3571 const LinearExpressionProto& expr = it.second.front(); 3572 if (!context_->IntersectDomainWith(expr, Domain(it.first))) { 3573 return true; 3574 } 3575 propagated = true; 3576 } 3577 } 3578 if (propagated) { 3579 context_->UpdateRuleStats( 3580 "all_diff: propagated mandatory values in permutation"); 3581 something_was_propagated = true; 3582 } 3583 } 3584 if (!something_was_propagated) break; 3585 } 3586 3587 return constraint_has_changed; 3588 } 3589 3590 namespace { 3591 3592 // Returns the sorted list of literals for given bool_or or at_most_one 3593 // constraint. 3594 std::vector<int> GetLiteralsFromSetPPCConstraint(const ConstraintProto& ct) { 3595 std::vector<int> sorted_literals; 3596 if (ct.constraint_case() == ConstraintProto::kAtMostOne) { 3597 for (const int literal : ct.at_most_one().literals()) { 3598 sorted_literals.push_back(literal); 3599 } 3600 } else if (ct.constraint_case() == ConstraintProto::kBoolOr) { 3601 for (const int literal : ct.bool_or().literals()) { 3602 sorted_literals.push_back(literal); 3603 } 3604 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) { 3605 for (const int literal : ct.exactly_one().literals()) { 3606 sorted_literals.push_back(literal); 3607 } 3608 } 3609 std::sort(sorted_literals.begin(), sorted_literals.end()); 3610 return sorted_literals; 3611 } 3612 3613 // Add the constraint (lhs => rhs) to the given proto. The hash map lhs -> 3614 // bool_and constraint index is used to merge implications with the same lhs. 3615 void AddImplication(int lhs, int rhs, CpModelProto* proto, 3616 absl::flat_hash_map<int, int>* ref_to_bool_and) { 3617 if (ref_to_bool_and->contains(lhs)) { 3618 const int ct_index = (*ref_to_bool_and)[lhs]; 3619 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs); 3620 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) { 3621 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)]; 3622 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals( 3623 NegatedRef(lhs)); 3624 } else { 3625 (*ref_to_bool_and)[lhs] = proto->constraints_size(); 3626 ConstraintProto* ct = proto->add_constraints(); 3627 ct->add_enforcement_literal(lhs); 3628 ct->mutable_bool_and()->add_literals(rhs); 3629 } 3630 } 3631 3632 template <typename ClauseContainer> 3633 void ExtractClauses(bool use_bool_and, const ClauseContainer& container, 3634 CpModelProto* proto) { 3635 // We regroup the "implication" into bool_and to have a more consise proto and 3636 // also for nicer information about the number of binary clauses. 3637 // 3638 // Important: however, we do not do that for the model used during presolving 3639 // since the order of the constraints might be important there depending on 3640 // how we perform the postsolve. 3641 absl::flat_hash_map<int, int> ref_to_bool_and; 3642 for (int i = 0; i < container.NumClauses(); ++i) { 3643 const std::vector<Literal>& clause = container.Clause(i); 3644 if (clause.empty()) continue; 3645 3646 // bool_and. 3647 if (use_bool_and && clause.size() == 2) { 3648 const int a = clause[0].IsPositive() 3649 ? clause[0].Variable().value() 3650 : NegatedRef(clause[0].Variable().value()); 3651 const int b = clause[1].IsPositive() 3652 ? clause[1].Variable().value() 3653 : NegatedRef(clause[1].Variable().value()); 3654 AddImplication(NegatedRef(a), b, proto, &ref_to_bool_and); 3655 continue; 3656 } 3657 3658 // bool_or. 3659 ConstraintProto* ct = proto->add_constraints(); 3660 for (const Literal l : clause) { 3661 if (l.IsPositive()) { 3662 ct->mutable_bool_or()->add_literals(l.Variable().value()); 3663 } else { 3664 ct->mutable_bool_or()->add_literals(NegatedRef(l.Variable().value())); 3665 } 3666 } 3667 } 3668 } 3669 3670 } // namespace 3671 3672 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) { 3673 if (context_->ModelIsUnsat()) return false; 3674 NoOverlapConstraintProto* proto = ct->mutable_no_overlap(); 3675 bool changed = false; 3676 3677 // Filter absent intervals. 3678 { 3679 const int initial_num_intervals = proto->intervals_size(); 3680 int new_size = 0; 3681 3682 for (int i = 0; i < initial_num_intervals; ++i) { 3683 const int interval_index = proto->intervals(i); 3684 if (context_->ConstraintIsInactive(interval_index)) { 3685 continue; 3686 } 3687 3688 proto->set_intervals(new_size++, interval_index); 3689 } 3690 3691 if (new_size < initial_num_intervals) { 3692 proto->mutable_intervals()->Truncate(new_size); 3693 context_->UpdateRuleStats("no_overlap: removed absent intervals"); 3694 changed = true; 3695 } 3696 } 3697 3698 // Split constraints in disjoint sets. 3699 if (proto->intervals_size() > 1) { 3700 std::vector<IndexedInterval> indexed_intervals; 3701 for (int i = 0; i < proto->intervals().size(); ++i) { 3702 const int index = proto->intervals(i); 3703 indexed_intervals.push_back({index, 3704 IntegerValue(context_->StartMin(index)), 3705 IntegerValue(context_->EndMax(index))}); 3706 } 3707 std::vector<std::vector<int>> components; 3708 GetOverlappingIntervalComponents(&indexed_intervals, &components); 3709 3710 if (components.size() > 1) { 3711 for (const std::vector<int>& intervals : components) { 3712 if (intervals.size() <= 1) continue; 3713 3714 NoOverlapConstraintProto* new_no_overlap = 3715 context_->working_model->add_constraints()->mutable_no_overlap(); 3716 // Fill in the intervals. Unfortunately, the Assign() method does not 3717 // compile in or-tools. 3718 for (const int i : intervals) { 3719 new_no_overlap->add_intervals(i); 3720 } 3721 } 3722 context_->UpdateNewConstraintsVariableUsage(); 3723 context_->UpdateRuleStats("no_overlap: split into disjoint components"); 3724 return RemoveConstraint(ct); 3725 } 3726 } 3727 3728 std::vector<int> constant_intervals; 3729 int64_t size_min_of_non_constant_intervals = 3730 std::numeric_limits<int64_t>::max(); 3731 for (int i = 0; i < proto->intervals_size(); ++i) { 3732 const int interval_index = proto->intervals(i); 3733 if (context_->IntervalIsConstant(interval_index)) { 3734 constant_intervals.push_back(interval_index); 3735 } else { 3736 size_min_of_non_constant_intervals = 3737 std::min(size_min_of_non_constant_intervals, 3738 context_->SizeMin(interval_index)); 3739 } 3740 } 3741 3742 if (!constant_intervals.empty()) { 3743 // Sort constant_intervals by start min. 3744 std::sort(constant_intervals.begin(), constant_intervals.end(), 3745 [this](int i1, int i2) { 3746 return context_->StartMin(i1) < context_->StartMin(i2); 3747 }); 3748 3749 // Check for overlapping constant intervals. We need to check feasibility 3750 // before we simplify the constraint, as we might remove conflicting 3751 // overlapping constant intervals. 3752 for (int i = 0; i + 1 < constant_intervals.size(); ++i) { 3753 if (context_->EndMax(constant_intervals[i]) > 3754 context_->StartMin(constant_intervals[i + 1])) { 3755 context_->UpdateRuleStats("no_overlap: constant intervals overlap"); 3756 return context_->NotifyThatModelIsUnsat(); 3757 } 3758 } 3759 3760 if (constant_intervals.size() == proto->intervals_size()) { 3761 context_->UpdateRuleStats("no_overlap: no variable intervals"); 3762 return RemoveConstraint(ct); 3763 } 3764 3765 absl::flat_hash_set<int> intervals_to_remove; 3766 3767 // If two constant intervals are separated by a gap smaller that the min 3768 // size of all non-constant intervals, then we can merge them. 3769 for (int i = 0; i + 1 < constant_intervals.size(); ++i) { 3770 const int start = i; 3771 while (i + 1 < constant_intervals.size() && 3772 context_->StartMin(constant_intervals[i + 1]) - 3773 context_->EndMax(constant_intervals[i]) < 3774 size_min_of_non_constant_intervals) { 3775 i++; 3776 } 3777 if (i == start) continue; 3778 for (int j = start; j <= i; ++j) { 3779 intervals_to_remove.insert(constant_intervals[j]); 3780 } 3781 const int64_t new_start = context_->StartMin(constant_intervals[start]); 3782 const int64_t new_end = context_->EndMax(constant_intervals[i]); 3783 proto->add_intervals(context_->working_model->constraints_size()); 3784 IntervalConstraintProto* new_interval = 3785 context_->working_model->add_constraints()->mutable_interval(); 3786 new_interval->mutable_start()->set_offset(new_start); 3787 new_interval->mutable_size()->set_offset(new_end - new_start); 3788 new_interval->mutable_end()->set_offset(new_end); 3789 } 3790 3791 // Cleanup the original proto. 3792 if (!intervals_to_remove.empty()) { 3793 int new_size = 0; 3794 const int old_size = proto->intervals_size(); 3795 for (int i = 0; i < old_size; ++i) { 3796 const int interval_index = proto->intervals(i); 3797 if (intervals_to_remove.contains(interval_index)) { 3798 continue; 3799 } 3800 proto->set_intervals(new_size++, interval_index); 3801 } 3802 CHECK_LT(new_size, old_size); 3803 proto->mutable_intervals()->Truncate(new_size); 3804 context_->UpdateRuleStats( 3805 "no_overlap: merge constant contiguous intervals"); 3806 intervals_to_remove.clear(); 3807 constant_intervals.clear(); 3808 changed = true; 3809 context_->UpdateNewConstraintsVariableUsage(); 3810 } 3811 } 3812 3813 if (proto->intervals_size() == 1) { 3814 context_->UpdateRuleStats("no_overlap: only one interval"); 3815 return RemoveConstraint(ct); 3816 } 3817 if (proto->intervals().empty()) { 3818 context_->UpdateRuleStats("no_overlap: no intervals"); 3819 return RemoveConstraint(ct); 3820 } 3821 3822 return changed; 3823 } 3824 3825 bool CpModelPresolver::PresolveNoOverlap2D(int c, ConstraintProto* ct) { 3826 if (context_->ModelIsUnsat()) { 3827 return false; 3828 } 3829 3830 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d(); 3831 const int initial_num_boxes = proto.x_intervals_size(); 3832 3833 bool has_zero_sizes = false; 3834 bool x_constant = true; 3835 bool y_constant = true; 3836 3837 // Filter absent boxes. 3838 int new_size = 0; 3839 std::vector<Rectangle> bounding_boxes; 3840 std::vector<int> active_boxes; 3841 for (int i = 0; i < proto.x_intervals_size(); ++i) { 3842 const int x_interval_index = proto.x_intervals(i); 3843 const int y_interval_index = proto.y_intervals(i); 3844 3845 if (context_->ConstraintIsInactive(x_interval_index) || 3846 context_->ConstraintIsInactive(y_interval_index)) { 3847 continue; 3848 } 3849 3850 if (proto.boxes_with_null_area_can_overlap() && 3851 (context_->SizeMax(x_interval_index) == 0 || 3852 context_->SizeMax(y_interval_index) == 0)) { 3853 if (proto.boxes_with_null_area_can_overlap()) continue; 3854 has_zero_sizes = true; 3855 } 3856 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index); 3857 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index); 3858 bounding_boxes.push_back( 3859 {IntegerValue(context_->StartMin(x_interval_index)), 3860 IntegerValue(context_->EndMax(x_interval_index)), 3861 IntegerValue(context_->StartMin(y_interval_index)), 3862 IntegerValue(context_->EndMax(y_interval_index))}); 3863 active_boxes.push_back(new_size); 3864 new_size++; 3865 3866 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) { 3867 x_constant = false; 3868 } 3869 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) { 3870 y_constant = false; 3871 } 3872 } 3873 3874 std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents( 3875 bounding_boxes, absl::MakeSpan(active_boxes)); 3876 if (components.size() > 1) { 3877 for (const absl::Span<int> boxes : components) { 3878 if (boxes.size() <= 1) continue; 3879 3880 NoOverlap2DConstraintProto* new_no_overlap_2d = 3881 context_->working_model->add_constraints()->mutable_no_overlap_2d(); 3882 for (const int b : boxes) { 3883 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b)); 3884 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b)); 3885 } 3886 } 3887 context_->UpdateNewConstraintsVariableUsage(); 3888 context_->UpdateRuleStats("no_overlap_2d: split into disjoint components"); 3889 return RemoveConstraint(ct); 3890 } 3891 3892 if (!has_zero_sizes && (x_constant || y_constant)) { 3893 context_->UpdateRuleStats( 3894 "no_overlap_2d: a dimension is constant, splitting into many no " 3895 "overlaps"); 3896 std::vector<IndexedInterval> indexed_intervals; 3897 for (int i = 0; i < new_size; ++i) { 3898 int x = proto.x_intervals(i); 3899 int y = proto.y_intervals(i); 3900 if (x_constant) std::swap(x, y); 3901 indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)), 3902 IntegerValue(context_->EndMax(y))}); 3903 } 3904 std::vector<std::vector<int>> no_overlaps; 3905 ConstructOverlappingSets(/*already_sorted=*/false, &indexed_intervals, 3906 &no_overlaps); 3907 for (const std::vector<int>& no_overlap : no_overlaps) { 3908 ConstraintProto* new_ct = context_->working_model->add_constraints(); 3909 // Unfortunately, the Assign() method does not work in or-tools as the 3910 // protobuf int32_t type is not the int type. 3911 for (const int i : no_overlap) { 3912 new_ct->mutable_no_overlap()->add_intervals(i); 3913 } 3914 } 3915 context_->UpdateNewConstraintsVariableUsage(); 3916 return RemoveConstraint(ct); 3917 } 3918 3919 if (new_size < initial_num_boxes) { 3920 context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes"); 3921 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size); 3922 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size); 3923 } 3924 3925 if (new_size == 0) { 3926 context_->UpdateRuleStats("no_overlap_2d: no boxes"); 3927 return RemoveConstraint(ct); 3928 } 3929 3930 if (new_size == 1) { 3931 context_->UpdateRuleStats("no_overlap_2d: only one box"); 3932 return RemoveConstraint(ct); 3933 } 3934 3935 return new_size < initial_num_boxes; 3936 } 3937 3938 namespace { 3939 LinearExpressionProto ConstantExpressionProto(int64_t value) { 3940 LinearExpressionProto expr; 3941 expr.set_offset(value); 3942 return expr; 3943 } 3944 } // namespace 3945 3946 bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) { 3947 if (context_->ModelIsUnsat()) return false; 3948 3949 CumulativeConstraintProto* proto = ct->mutable_cumulative(); 3950 3951 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity()); 3952 for (LinearExpressionProto& exp : 3953 *(ct->mutable_cumulative()->mutable_demands())) { 3954 changed |= CanonicalizeLinearExpression(*ct, &exp); 3955 } 3956 3957 const int64_t capacity_max = context_->MaxOf(proto->capacity()); 3958 3959 // Checks the capacity of the constraint. 3960 { 3961 bool domain_changed = false; 3962 if (!context_->IntersectDomainWith( 3963 proto->capacity(), Domain(0, capacity_max), &domain_changed)) { 3964 return true; 3965 } 3966 if (domain_changed) { 3967 context_->UpdateRuleStats("cumulative: trimmed negative capacity"); 3968 } 3969 } 3970 3971 { 3972 // Filter absent intervals, or zero demands, or demand incompatible with the 3973 // capacity. 3974 int new_size = 0; 3975 int num_zero_demand_removed = 0; 3976 int num_zero_size_removed = 0; 3977 int num_incompatible_demands = 0; 3978 for (int i = 0; i < proto->intervals_size(); ++i) { 3979 if (context_->ConstraintIsInactive(proto->intervals(i))) continue; 3980 3981 const LinearExpressionProto& demand_expr = proto->demands(i); 3982 const int64_t demand_max = context_->MaxOf(demand_expr); 3983 if (demand_max == 0) { 3984 num_zero_demand_removed++; 3985 continue; 3986 } 3987 3988 if (context_->SizeMax(proto->intervals(i)) == 0) { 3989 // Size 0 intervals cannot contribute to a cumulative. 3990 num_zero_size_removed++; 3991 continue; 3992 } 3993 3994 if (context_->MinOf(demand_expr) > capacity_max) { 3995 if (context_->ConstraintIsOptional(proto->intervals(i))) { 3996 ConstraintProto* interval_ct = 3997 context_->working_model->mutable_constraints(proto->intervals(i)); 3998 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1); 3999 const int literal = interval_ct->enforcement_literal(0); 4000 if (!context_->SetLiteralToFalse(literal)) { 4001 return true; 4002 } 4003 num_incompatible_demands++; 4004 continue; 4005 } else { // Interval is performed. 4006 return context_->NotifyThatModelIsUnsat( 4007 "cumulative: performed demand exceeds capacity."); 4008 } 4009 } 4010 4011 proto->set_intervals(new_size, proto->intervals(i)); 4012 *proto->mutable_demands(new_size) = proto->demands(i); 4013 new_size++; 4014 } 4015 4016 if (new_size < proto->intervals_size()) { 4017 changed = true; 4018 proto->mutable_intervals()->Truncate(new_size); 4019 proto->mutable_demands()->erase( 4020 proto->mutable_demands()->begin() + new_size, 4021 proto->mutable_demands()->end()); 4022 } 4023 4024 if (num_zero_demand_removed > 0) { 4025 context_->UpdateRuleStats( 4026 "cumulative: removed intervals with no demands"); 4027 } 4028 if (num_zero_size_removed > 0) { 4029 context_->UpdateRuleStats( 4030 "cumulative: removed intervals with a size of zero"); 4031 } 4032 if (num_incompatible_demands > 0) { 4033 context_->UpdateRuleStats( 4034 "cumulative: removed intervals demands greater than the capacity"); 4035 } 4036 } 4037 4038 // Checks the compatibility of demands w.r.t. the capacity. 4039 { 4040 for (int i = 0; i < proto->demands_size(); ++i) { 4041 const int interval = proto->intervals(i); 4042 const LinearExpressionProto& demand_expr = proto->demands(i); 4043 if (context_->ConstraintIsOptional(interval)) continue; 4044 bool domain_changed = false; 4045 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max}, 4046 &domain_changed)) { 4047 return true; 4048 } 4049 if (domain_changed) { 4050 context_->UpdateRuleStats( 4051 "cumulative: fit demand in [0..capacity_max]"); 4052 } 4053 } 4054 } 4055 4056 // Split constraints in disjoint sets. 4057 // 4058 // TODO(user): This can be improved: 4059 // If we detect bridge nodes in the graph of overlapping components, we 4060 // can split the graph around the bridge and add the bridge node to both 4061 // side. Note that if it we take into account precedences between intervals, 4062 // we can detect more bridges. 4063 if (proto->intervals_size() > 1) { 4064 std::vector<IndexedInterval> indexed_intervals; 4065 for (int i = 0; i < proto->intervals().size(); ++i) { 4066 const int index = proto->intervals(i); 4067 indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)), 4068 IntegerValue(context_->EndMax(index))}); 4069 } 4070 std::vector<std::vector<int>> components; 4071 GetOverlappingIntervalComponents(&indexed_intervals, &components); 4072 4073 if (components.size() > 1) { 4074 for (const std::vector<int>& component : components) { 4075 CumulativeConstraintProto* new_cumulative = 4076 context_->working_model->add_constraints()->mutable_cumulative(); 4077 for (const int i : component) { 4078 new_cumulative->add_intervals(proto->intervals(i)); 4079 *new_cumulative->add_demands() = proto->demands(i); 4080 } 4081 *new_cumulative->mutable_capacity() = proto->capacity(); 4082 } 4083 context_->UpdateNewConstraintsVariableUsage(); 4084 context_->UpdateRuleStats("cumulative: split into disjoint components"); 4085 return RemoveConstraint(ct); 4086 } 4087 } 4088 4089 // TODO(user): move the algorithmic part of what we do below in a 4090 // separate function to unit test it more properly. 4091 { 4092 // Build max load profiles. 4093 std::map<int64_t, int64_t> time_to_demand_deltas; 4094 const int64_t capacity_min = context_->MinOf(proto->capacity()); 4095 for (int i = 0; i < proto->intervals_size(); ++i) { 4096 const int interval_index = proto->intervals(i); 4097 const int64_t demand_max = context_->MaxOf(proto->demands(i)); 4098 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max; 4099 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max; 4100 } 4101 4102 // We construct the profile which correspond to a set of [time, next_time) 4103 // to max_profile height. And for each time in our discrete set of 4104 // time_exprs (all the start_min and end_max) we count for how often the 4105 // height was above the capacity before this time. 4106 // 4107 // This rely on the iteration in sorted order. 4108 int num_possible_overloads = 0; 4109 int64_t current_load = 0; 4110 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before; 4111 for (const auto& it : time_to_demand_deltas) { 4112 num_possible_overloads_before[it.first] = num_possible_overloads; 4113 current_load += it.second; 4114 if (current_load > capacity_min) { 4115 ++num_possible_overloads; 4116 } 4117 } 4118 CHECK_EQ(current_load, 0); 4119 4120 // No possible overload with the min capacity. 4121 if (num_possible_overloads == 0) { 4122 context_->UpdateRuleStats( 4123 "cumulative: max profile is always under the min capacity"); 4124 return RemoveConstraint(ct); 4125 } 4126 4127 // An interval that does not intersect with the potential_overload_domains 4128 // cannot contribute to a conflict. We can safely remove them. 4129 // 4130 // This is an extension of the presolve rule from 4131 // "Presolving techniques and linear relaxations for cumulative 4132 // scheduling" PhD dissertation by Stefan Heinz, ZIB. 4133 int new_size = 0; 4134 for (int i = 0; i < proto->intervals_size(); ++i) { 4135 const int index = proto->intervals(i); 4136 const int64_t start_min = context_->StartMin(index); 4137 const int64_t end_max = context_->EndMax(index); 4138 4139 // In the cumulative, if start_min == end_max, the interval is of size 4140 // zero and we can just ignore it. If the model is unsat or the interval 4141 // must be absent (start_min > end_max), this should be dealt with at 4142 // the interval constraint level and we can just remove it from here. 4143 // 4144 // Note that currently, the interpretation for interval of length zero 4145 // is different for the no-overlap constraint. 4146 if (start_min >= end_max) continue; 4147 4148 // Note that by construction, both point are in the map. The formula 4149 // counts exactly for how many time_exprs in [start_min, end_max), we have 4150 // a point in our discrete set of time that exceeded the capacity. Because 4151 // we included all the relevant points, this works. 4152 const int num_diff = num_possible_overloads_before.at(end_max) - 4153 num_possible_overloads_before.at(start_min); 4154 if (num_diff == 0) continue; 4155 4156 proto->set_intervals(new_size, proto->intervals(i)); 4157 *proto->mutable_demands(new_size) = proto->demands(i); 4158 new_size++; 4159 } 4160 4161 if (new_size < proto->intervals_size()) { 4162 changed = true; 4163 proto->mutable_intervals()->Truncate(new_size); 4164 proto->mutable_demands()->erase( 4165 proto->mutable_demands()->begin() + new_size, 4166 proto->mutable_demands()->end()); 4167 context_->UpdateRuleStats( 4168 "cumulative: remove never conflicting intervals."); 4169 } 4170 } 4171 4172 if (proto->intervals().empty()) { 4173 context_->UpdateRuleStats("cumulative: no intervals"); 4174 return RemoveConstraint(ct); 4175 } 4176 4177 { 4178 int64_t max_of_performed_demand_mins = 0; 4179 int64_t sum_of_max_demands = 0; 4180 for (int i = 0; i < proto->intervals_size(); ++i) { 4181 const ConstraintProto& interval_ct = 4182 context_->working_model->constraints(proto->intervals(i)); 4183 4184 const LinearExpressionProto& demand_expr = proto->demands(i); 4185 sum_of_max_demands += context_->MaxOf(demand_expr); 4186 4187 if (interval_ct.enforcement_literal().empty()) { 4188 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins, 4189 context_->MinOf(demand_expr)); 4190 } 4191 } 4192 4193 const LinearExpressionProto& capacity_expr = proto->capacity(); 4194 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) { 4195 context_->UpdateRuleStats("cumulative: propagate min capacity."); 4196 if (!context_->IntersectDomainWith( 4197 capacity_expr, Domain(max_of_performed_demand_mins, 4198 std::numeric_limits<int64_t>::max()))) { 4199 return true; 4200 } 4201 } 4202 4203 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) { 4204 context_->UpdateRuleStats("cumulative: cannot fit performed demands"); 4205 return context_->NotifyThatModelIsUnsat(); 4206 } 4207 4208 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) { 4209 context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands"); 4210 return RemoveConstraint(ct); 4211 } 4212 } 4213 4214 if (context_->IsFixed(proto->capacity())) { 4215 int64_t gcd = 0; 4216 for (int i = 0; i < ct->cumulative().demands_size(); ++i) { 4217 const LinearExpressionProto& demand_expr = ct->cumulative().demands(i); 4218 if (!context_->IsFixed(demand_expr)) { 4219 // Abort if the demand is not fixed. 4220 gcd = 1; 4221 break; 4222 } 4223 gcd = MathUtil::GCD64(gcd, context_->MinOf(demand_expr)); 4224 if (gcd == 1) break; 4225 } 4226 if (gcd > 1) { 4227 changed = true; 4228 for (int i = 0; i < ct->cumulative().demands_size(); ++i) { 4229 const int64_t demand = context_->MinOf(ct->cumulative().demands(i)); 4230 *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd); 4231 } 4232 4233 const int64_t old_capacity = context_->MinOf(proto->capacity()); 4234 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd); 4235 context_->UpdateRuleStats( 4236 "cumulative: divide demands and capacity by gcd"); 4237 } 4238 } 4239 4240 const int num_intervals = proto->intervals_size(); 4241 const LinearExpressionProto& capacity_expr = proto->capacity(); 4242 4243 std::vector<LinearExpressionProto> start_exprs(num_intervals); 4244 4245 int num_duration_one = 0; 4246 int num_greater_half_capacity = 0; 4247 4248 bool has_optional_interval = false; 4249 bool all_starts_are_variables = true; 4250 for (int i = 0; i < num_intervals; ++i) { 4251 const int index = proto->intervals(i); 4252 // TODO(user): adapt in the presence of optional intervals. 4253 if (context_->ConstraintIsOptional(index)) has_optional_interval = true; 4254 const ConstraintProto& ct = 4255 context_->working_model->constraints(proto->intervals(i)); 4256 const IntervalConstraintProto& interval = ct.interval(); 4257 start_exprs[i] = interval.start(); 4258 4259 const LinearExpressionProto& demand_expr = proto->demands(i); 4260 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) { 4261 num_duration_one++; 4262 } 4263 if (context_->SizeMin(index) == 0) { 4264 // The behavior for zero-duration interval is currently not the same in 4265 // the no-overlap and the cumulative constraint. 4266 return changed; 4267 } 4268 const int64_t demand_min = context_->MinOf(demand_expr); 4269 const int64_t demand_max = context_->MaxOf(demand_expr); 4270 if (demand_min > capacity_max / 2) { 4271 num_greater_half_capacity++; 4272 } 4273 if (demand_min > capacity_max) { 4274 context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max"); 4275 if (!context_->ConstraintIsOptional(index)) { 4276 return context_->NotifyThatModelIsUnsat(); 4277 } else { 4278 CHECK_EQ(ct.enforcement_literal().size(), 1); 4279 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) { 4280 return true; 4281 } 4282 } 4283 return changed; 4284 } else if (demand_max > capacity_max) { 4285 if (ct.enforcement_literal().empty()) { 4286 context_->UpdateRuleStats( 4287 "cumulative: demand_max exceeds capacity max."); 4288 if (!context_->IntersectDomainWith( 4289 demand_expr, 4290 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) { 4291 return true; 4292 } 4293 } else { 4294 // TODO(user): we abort because we cannot convert this to a no_overlap 4295 // for instance. 4296 context_->UpdateRuleStats( 4297 "cumulative: demand_max of optional interval exceeds capacity."); 4298 return changed; 4299 } 4300 } 4301 } 4302 if (num_greater_half_capacity == num_intervals) { 4303 if (num_duration_one == num_intervals && !has_optional_interval && 4304 all_starts_are_variables) { 4305 context_->UpdateRuleStats("cumulative: convert to all_different"); 4306 ConstraintProto* new_ct = context_->working_model->add_constraints(); 4307 auto* arg = new_ct->mutable_all_diff(); 4308 for (const LinearExpressionProto& expr : start_exprs) { 4309 *arg->add_exprs() = expr; 4310 } 4311 context_->UpdateNewConstraintsVariableUsage(); 4312 return RemoveConstraint(ct); 4313 } else { 4314 context_->UpdateRuleStats("cumulative: convert to no_overlap"); 4315 // Before we remove the cumulative, add constraints to enforce that the 4316 // capacity is greater than the demand of any performed intervals. 4317 for (int i = 0; i < proto->demands_size(); ++i) { 4318 const LinearExpressionProto& demand_expr = proto->demands(i); 4319 const int64_t demand_max = context_->MaxOf(demand_expr); 4320 if (demand_max > context_->MinOf(capacity_expr)) { 4321 ConstraintProto* capacity_gt = 4322 context_->working_model->add_constraints(); 4323 *capacity_gt->mutable_enforcement_literal() = 4324 context_->working_model->constraints(proto->intervals(i)) 4325 .enforcement_literal(); 4326 capacity_gt->mutable_linear()->add_domain(0); 4327 capacity_gt->mutable_linear()->add_domain( 4328 std::numeric_limits<int64_t>::max()); 4329 AddLinearExpressionToLinearConstraint(capacity_expr, 1, 4330 capacity_gt->mutable_linear()); 4331 AddLinearExpressionToLinearConstraint(demand_expr, -1, 4332 capacity_gt->mutable_linear()); 4333 } 4334 } 4335 4336 ConstraintProto* new_ct = context_->working_model->add_constraints(); 4337 auto* arg = new_ct->mutable_no_overlap(); 4338 for (const int interval : proto->intervals()) { 4339 arg->add_intervals(interval); 4340 } 4341 context_->UpdateNewConstraintsVariableUsage(); 4342 return RemoveConstraint(ct); 4343 } 4344 } 4345 4346 return changed; 4347 } 4348 4349 bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) { 4350 if (context_->ModelIsUnsat()) return false; 4351 if (HasEnforcementLiteral(*ct)) return false; 4352 RoutesConstraintProto& proto = *ct->mutable_routes(); 4353 4354 const int old_size = proto.literals_size(); 4355 int new_size = 0; 4356 std::vector<bool> has_incoming_or_outgoing_arcs; 4357 const int num_arcs = proto.literals_size(); 4358 for (int i = 0; i < num_arcs; ++i) { 4359 const int ref = proto.literals(i); 4360 const int tail = proto.tails(i); 4361 const int head = proto.heads(i); 4362 if (context_->LiteralIsFalse(ref)) { 4363 context_->UpdateRuleStats("routes: removed false arcs"); 4364 continue; 4365 } 4366 proto.set_literals(new_size, ref); 4367 proto.set_tails(new_size, tail); 4368 proto.set_heads(new_size, head); 4369 ++new_size; 4370 if (tail >= has_incoming_or_outgoing_arcs.size()) { 4371 has_incoming_or_outgoing_arcs.resize(tail + 1, false); 4372 } 4373 if (head >= has_incoming_or_outgoing_arcs.size()) { 4374 has_incoming_or_outgoing_arcs.resize(head + 1, false); 4375 } 4376 has_incoming_or_outgoing_arcs[tail] = true; 4377 has_incoming_or_outgoing_arcs[head] = true; 4378 } 4379 4380 if (old_size > 0 && new_size == 0) { 4381 // A routes constraint cannot have a self loop on 0. Therefore, if there 4382 // were arcs, it means it contains non zero nodes. Without arc, the 4383 // constraint is unfeasible. 4384 return context_->NotifyThatModelIsUnsat( 4385 "routes: graph with nodes and no arcs"); 4386 } 4387 4388 if (new_size < num_arcs) { 4389 proto.mutable_literals()->Truncate(new_size); 4390 proto.mutable_tails()->Truncate(new_size); 4391 proto.mutable_heads()->Truncate(new_size); 4392 return true; 4393 } 4394 4395 // if a node misses an incomping or outgoing arc, the model is trivially 4396 // infeasible. 4397 for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) { 4398 if (!has_incoming_or_outgoing_arcs[n]) { 4399 return context_->NotifyThatModelIsUnsat(absl::StrCat( 4400 "routes: node ", n, " misses incoming or outgoing arcs")); 4401 } 4402 } 4403 4404 return false; 4405 } 4406 4407 bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) { 4408 if (context_->ModelIsUnsat()) return false; 4409 if (HasEnforcementLiteral(*ct)) return false; 4410 CircuitConstraintProto& proto = *ct->mutable_circuit(); 4411 4412 // The indexing might not be dense, so fix that first. 4413 ReindexArcs(ct->mutable_circuit()->mutable_tails(), 4414 ct->mutable_circuit()->mutable_heads()); 4415 4416 // Convert the flat structure to a graph, note that we includes all the arcs 4417 // here (even if they are at false). 4418 std::vector<std::vector<int>> incoming_arcs; 4419 std::vector<std::vector<int>> outgoing_arcs; 4420 int num_nodes = 0; 4421 const int num_arcs = proto.literals_size(); 4422 for (int i = 0; i < num_arcs; ++i) { 4423 const int ref = proto.literals(i); 4424 const int tail = proto.tails(i); 4425 const int head = proto.heads(i); 4426 num_nodes = std::max(num_nodes, std::max(tail, head) + 1); 4427 if (std::max(tail, head) >= incoming_arcs.size()) { 4428 incoming_arcs.resize(std::max(tail, head) + 1); 4429 outgoing_arcs.resize(std::max(tail, head) + 1); 4430 } 4431 incoming_arcs[head].push_back(ref); 4432 outgoing_arcs[tail].push_back(ref); 4433 } 4434 4435 // All the node must have some incoming and outgoing arcs. 4436 for (int i = 0; i < num_nodes; ++i) { 4437 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) { 4438 return MarkConstraintAsFalse(ct); 4439 } 4440 } 4441 4442 // Note that it is important to reach the fixed point here: 4443 // One arc at true, then all other arc at false. This is because we rely 4444 // on this in case the circuit is fully specified below. 4445 // 4446 // TODO(user): Use a better complexity if needed. 4447 bool loop_again = true; 4448 int num_fixed_at_true = 0; 4449 while (loop_again) { 4450 loop_again = false; 4451 for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) { 4452 for (const std::vector<int>& refs : *node_to_refs) { 4453 if (refs.size() == 1) { 4454 if (!context_->LiteralIsTrue(refs.front())) { 4455 ++num_fixed_at_true; 4456 if (!context_->SetLiteralToTrue(refs.front())) return true; 4457 } 4458 continue; 4459 } 4460 4461 // At most one true, so if there is one, mark all the other to false. 4462 int num_true = 0; 4463 int true_ref; 4464 for (const int ref : refs) { 4465 if (context_->LiteralIsTrue(ref)) { 4466 ++num_true; 4467 true_ref = ref; 4468 break; 4469 } 4470 } 4471 if (num_true > 1) { 4472 return context_->NotifyThatModelIsUnsat(); 4473 } 4474 if (num_true == 1) { 4475 for (const int ref : refs) { 4476 if (ref != true_ref) { 4477 if (!context_->IsFixed(ref)) { 4478 context_->UpdateRuleStats("circuit: set literal to false."); 4479 loop_again = true; 4480 } 4481 if (!context_->SetLiteralToFalse(ref)) return true; 4482 } 4483 } 4484 } 4485 } 4486 } 4487 } 4488 if (num_fixed_at_true > 0) { 4489 context_->UpdateRuleStats("circuit: fixed singleton arcs."); 4490 } 4491 4492 // Remove false arcs. 4493 int new_size = 0; 4494 int num_true = 0; 4495 int circuit_start = -1; 4496 std::vector<int> next(num_nodes, -1); 4497 std::vector<int> new_in_degree(num_nodes, 0); 4498 std::vector<int> new_out_degree(num_nodes, 0); 4499 for (int i = 0; i < num_arcs; ++i) { 4500 const int ref = proto.literals(i); 4501 if (context_->LiteralIsFalse(ref)) continue; 4502 if (context_->LiteralIsTrue(ref)) { 4503 if (next[proto.tails(i)] != -1) { 4504 return context_->NotifyThatModelIsUnsat(); 4505 } 4506 next[proto.tails(i)] = proto.heads(i); 4507 if (proto.tails(i) != proto.heads(i)) { 4508 circuit_start = proto.tails(i); 4509 } 4510 ++num_true; 4511 } 4512 ++new_out_degree[proto.tails(i)]; 4513 ++new_in_degree[proto.heads(i)]; 4514 proto.set_tails(new_size, proto.tails(i)); 4515 proto.set_heads(new_size, proto.heads(i)); 4516 proto.set_literals(new_size, proto.literals(i)); 4517 ++new_size; 4518 } 4519 4520 // Detect infeasibility due to a node having no more incoming or outgoing arc. 4521 // This is a bit tricky because for now the meaning of the constraint says 4522 // that all nodes that appear in at least one of the arcs must be in the 4523 // circuit or have a self-arc. So if any such node ends up with an incoming or 4524 // outgoing degree of zero once we remove false arcs then the constraint is 4525 // infeasible! 4526 for (int i = 0; i < num_nodes; ++i) { 4527 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) { 4528 return context_->NotifyThatModelIsUnsat(); 4529 } 4530 } 4531 4532 // Test if a subcircuit is already present. 4533 if (circuit_start != -1) { 4534 std::vector<bool> visited(num_nodes, false); 4535 int current = circuit_start; 4536 while (current != -1 && !visited[current]) { 4537 visited[current] = true; 4538 current = next[current]; 4539 } 4540 if (current == circuit_start) { 4541 // We have a sub-circuit! mark all other arc false except self-loop not in 4542 // circuit. 4543 std::vector<bool> has_self_arc(num_nodes, false); 4544 for (int i = 0; i < num_arcs; ++i) { 4545 if (visited[proto.tails(i)]) continue; 4546 if (proto.tails(i) == proto.heads(i)) { 4547 has_self_arc[proto.tails(i)] = true; 4548 if (!context_->SetLiteralToTrue(proto.literals(i))) return true; 4549 } else { 4550 if (!context_->SetLiteralToFalse(proto.literals(i))) return true; 4551 } 4552 } 4553 for (int n = 0; n < num_nodes; ++n) { 4554 if (!visited[n] && !has_self_arc[n]) { 4555 // We have a subircuit, but it doesn't cover all the mandatory nodes. 4556 return MarkConstraintAsFalse(ct); 4557 } 4558 } 4559 context_->UpdateRuleStats("circuit: fully specified."); 4560 return RemoveConstraint(ct); 4561 } 4562 } else { 4563 // All self loop? 4564 if (num_true == new_size) { 4565 context_->UpdateRuleStats("circuit: empty circuit."); 4566 return RemoveConstraint(ct); 4567 } 4568 } 4569 4570 // Look for in/out-degree of two, this will imply that one of the indicator 4571 // Boolean is equal to the negation of the other. 4572 for (int i = 0; i < num_nodes; ++i) { 4573 for (const std::vector<int>* arc_literals : 4574 {&incoming_arcs[i], &outgoing_arcs[i]}) { 4575 std::vector<int> literals; 4576 for (const int ref : *arc_literals) { 4577 if (context_->LiteralIsFalse(ref)) continue; 4578 if (context_->LiteralIsTrue(ref)) { 4579 literals.clear(); 4580 break; 4581 } 4582 literals.push_back(ref); 4583 } 4584 if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) { 4585 context_->UpdateRuleStats("circuit: degree 2"); 4586 context_->StoreBooleanEqualityRelation(literals[0], 4587 NegatedRef(literals[1])); 4588 } 4589 } 4590 } 4591 4592 // Truncate the circuit and return. 4593 if (new_size < num_arcs) { 4594 proto.mutable_tails()->Truncate(new_size); 4595 proto.mutable_heads()->Truncate(new_size); 4596 proto.mutable_literals()->Truncate(new_size); 4597 context_->UpdateRuleStats("circuit: removed false arcs."); 4598 return true; 4599 } 4600 return false; 4601 } 4602 4603 bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) { 4604 if (context_->ModelIsUnsat()) return false; 4605 if (HasEnforcementLiteral(*ct)) return false; 4606 AutomatonConstraintProto& proto = *ct->mutable_automaton(); 4607 if (proto.vars_size() == 0 || proto.transition_label_size() == 0) { 4608 return false; 4609 } 4610 4611 bool all_have_same_affine_relation = true; 4612 std::vector<AffineRelation::Relation> affine_relations; 4613 for (int v = 0; v < proto.vars_size(); ++v) { 4614 const int var = ct->automaton().vars(v); 4615 const AffineRelation::Relation r = context_->GetAffineRelation(var); 4616 affine_relations.push_back(r); 4617 if (r.representative == var) { 4618 all_have_same_affine_relation = false; 4619 break; 4620 } 4621 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff || 4622 r.offset != affine_relations[v - 1].offset)) { 4623 all_have_same_affine_relation = false; 4624 break; 4625 } 4626 } 4627 4628 if (all_have_same_affine_relation) { // Unscale labels. 4629 for (int v = 0; v < proto.vars_size(); ++v) { 4630 proto.set_vars(v, affine_relations[v].representative); 4631 } 4632 const AffineRelation::Relation rep = affine_relations.front(); 4633 int new_size = 0; 4634 for (int t = 0; t < proto.transition_tail_size(); ++t) { 4635 const int64_t label = proto.transition_label(t); 4636 int64_t inverse_label = (label - rep.offset) / rep.coeff; 4637 if (inverse_label * rep.coeff + rep.offset == label) { 4638 if (new_size != t) { 4639 proto.set_transition_tail(new_size, proto.transition_tail(t)); 4640 proto.set_transition_head(new_size, proto.transition_head(t)); 4641 } 4642 proto.set_transition_label(new_size, inverse_label); 4643 new_size++; 4644 } 4645 } 4646 if (new_size < proto.transition_tail_size()) { 4647 proto.mutable_transition_tail()->Truncate(new_size); 4648 proto.mutable_transition_label()->Truncate(new_size); 4649 proto.mutable_transition_head()->Truncate(new_size); 4650 context_->UpdateRuleStats("automaton: remove invalid transitions"); 4651 } 4652 context_->UpdateRuleStats("automaton: unscale all affine labels"); 4653 return true; 4654 } 4655 4656 Domain hull = context_->DomainOf(proto.vars(0)); 4657 for (int v = 1; v < proto.vars_size(); ++v) { 4658 hull = hull.UnionWith(context_->DomainOf(proto.vars(v))); 4659 } 4660 4661 int new_size = 0; 4662 for (int t = 0; t < proto.transition_tail_size(); ++t) { 4663 const int64_t label = proto.transition_label(t); 4664 if (hull.Contains(label)) { 4665 if (new_size != t) { 4666 proto.set_transition_tail(new_size, proto.transition_tail(t)); 4667 proto.set_transition_label(new_size, label); 4668 proto.set_transition_head(new_size, proto.transition_head(t)); 4669 } 4670 new_size++; 4671 } 4672 } 4673 if (new_size < proto.transition_tail_size()) { 4674 proto.mutable_transition_tail()->Truncate(new_size); 4675 proto.mutable_transition_label()->Truncate(new_size); 4676 proto.mutable_transition_head()->Truncate(new_size); 4677 context_->UpdateRuleStats("automaton: remove invalid transitions"); 4678 return false; 4679 } 4680 4681 const int n = proto.vars_size(); 4682 const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()}; 4683 4684 // Compute the set of reachable state at each time point. 4685 std::vector<std::set<int64_t>> reachable_states(n + 1); 4686 reachable_states[0].insert(proto.starting_state()); 4687 reachable_states[n] = {proto.final_states().begin(), 4688 proto.final_states().end()}; 4689 4690 // Forward. 4691 for (int time = 0; time + 1 < n; ++time) { 4692 for (int t = 0; t < proto.transition_tail_size(); ++t) { 4693 const int64_t tail = proto.transition_tail(t); 4694 const int64_t label = proto.transition_label(t); 4695 const int64_t head = proto.transition_head(t); 4696 if (!gtl::ContainsKey(reachable_states[time], tail)) continue; 4697 if (!context_->DomainContains(vars[time], label)) continue; 4698 reachable_states[time + 1].insert(head); 4699 } 4700 } 4701 4702 std::vector<std::set<int64_t>> reached_values(n); 4703 4704 // Backward. 4705 for (int time = n - 1; time >= 0; --time) { 4706 std::set<int64_t> new_set; 4707 for (int t = 0; t < proto.transition_tail_size(); ++t) { 4708 const int64_t tail = proto.transition_tail(t); 4709 const int64_t label = proto.transition_label(t); 4710 const int64_t head = proto.transition_head(t); 4711 4712 if (!gtl::ContainsKey(reachable_states[time], tail)) continue; 4713 if (!context_->DomainContains(vars[time], label)) continue; 4714 if (!gtl::ContainsKey(reachable_states[time + 1], head)) continue; 4715 new_set.insert(tail); 4716 reached_values[time].insert(label); 4717 } 4718 reachable_states[time].swap(new_set); 4719 } 4720 4721 bool removed_values = false; 4722 for (int time = 0; time < n; ++time) { 4723 if (!context_->IntersectDomainWith( 4724 vars[time], 4725 Domain::FromValues( 4726 {reached_values[time].begin(), reached_values[time].end()}), 4727 &removed_values)) { 4728 return false; 4729 } 4730 } 4731 if (removed_values) { 4732 context_->UpdateRuleStats("automaton: reduced variable domains"); 4733 } 4734 return false; 4735 } 4736 4737 bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) { 4738 if (context_->ModelIsUnsat()) return false; 4739 if (HasEnforcementLiteral(*ct)) return false; 4740 4741 ReservoirConstraintProto& proto = *ct->mutable_reservoir(); 4742 bool changed = false; 4743 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) { 4744 changed |= CanonicalizeLinearExpression(*ct, &exp); 4745 } 4746 4747 if (proto.active_literals().empty()) { 4748 const int true_literal = context_->GetOrCreateConstantVar(1); 4749 for (int i = 0; i < proto.time_exprs_size(); ++i) { 4750 proto.add_active_literals(true_literal); 4751 } 4752 changed = true; 4753 } 4754 4755 const auto& demand_is_null = [&](int i) { 4756 return proto.level_changes(i) == 0 || 4757 context_->LiteralIsFalse(proto.active_literals(i)); 4758 }; 4759 4760 // Remove zero level_changes, and inactive events. 4761 int num_zeros = 0; 4762 for (int i = 0; i < proto.level_changes_size(); ++i) { 4763 if (demand_is_null(i)) num_zeros++; 4764 } 4765 4766 if (num_zeros > 0) { // Remove null events 4767 changed = true; 4768 int new_size = 0; 4769 for (int i = 0; i < proto.level_changes_size(); ++i) { 4770 if (demand_is_null(i)) continue; 4771 proto.set_level_changes(new_size, proto.level_changes(i)); 4772 *proto.mutable_time_exprs(new_size) = proto.time_exprs(i); 4773 proto.set_active_literals(new_size, proto.active_literals(i)); 4774 new_size++; 4775 } 4776 4777 proto.mutable_level_changes()->Truncate(new_size); 4778 proto.mutable_time_exprs()->erase( 4779 proto.mutable_time_exprs()->begin() + new_size, 4780 proto.mutable_time_exprs()->end()); 4781 proto.mutable_active_literals()->Truncate(new_size); 4782 4783 context_->UpdateRuleStats( 4784 "reservoir: remove zero level_changes or inactive events."); 4785 } 4786 4787 const int num_events = proto.level_changes_size(); 4788 int64_t gcd = 4789 proto.level_changes().empty() ? 0 : std::abs(proto.level_changes(0)); 4790 int num_positives = 0; 4791 int num_negatives = 0; 4792 int64_t max_sum_of_positive_level_changes = 0; 4793 int64_t min_sum_of_negative_level_changes = 0; 4794 for (int i = 0; i < num_events; ++i) { 4795 const int64_t demand = proto.level_changes(i); 4796 gcd = MathUtil::GCD64(gcd, std::abs(demand)); 4797 if (demand > 0) { 4798 num_positives++; 4799 max_sum_of_positive_level_changes += demand; 4800 } else { 4801 DCHECK_LT(demand, 0); 4802 num_negatives++; 4803 min_sum_of_negative_level_changes += demand; 4804 } 4805 } 4806 4807 if (min_sum_of_negative_level_changes >= proto.min_level() && 4808 max_sum_of_positive_level_changes <= proto.max_level()) { 4809 context_->UpdateRuleStats("reservoir: always feasible"); 4810 return RemoveConstraint(ct); 4811 } 4812 4813 if (min_sum_of_negative_level_changes > proto.max_level() || 4814 max_sum_of_positive_level_changes < proto.min_level()) { 4815 context_->UpdateRuleStats("reservoir: trivially infeasible"); 4816 return context_->NotifyThatModelIsUnsat(); 4817 } 4818 4819 if (min_sum_of_negative_level_changes > proto.min_level()) { 4820 proto.set_min_level(min_sum_of_negative_level_changes); 4821 context_->UpdateRuleStats( 4822 "reservoir: increase min_level to reachable value"); 4823 } 4824 4825 if (max_sum_of_positive_level_changes < proto.max_level()) { 4826 proto.set_max_level(max_sum_of_positive_level_changes); 4827 context_->UpdateRuleStats("reservoir: reduce max_level to reachable value"); 4828 } 4829 4830 if (proto.min_level() <= 0 && proto.max_level() >= 0 && 4831 (num_positives == 0 || num_negatives == 0)) { 4832 // If all level_changes have the same sign, and if the initial state is 4833 // always feasible, we do not care about the order, just the sum. 4834 auto* const sum = 4835 context_->working_model->add_constraints()->mutable_linear(); 4836 int64_t fixed_contrib = 0; 4837 for (int i = 0; i < proto.level_changes_size(); ++i) { 4838 const int64_t demand = proto.level_changes(i); 4839 DCHECK_NE(demand, 0); 4840 4841 const int active = proto.active_literals(i); 4842 if (RefIsPositive(active)) { 4843 sum->add_vars(active); 4844 sum->add_coeffs(demand); 4845 } else { 4846 sum->add_vars(PositiveRef(active)); 4847 sum->add_coeffs(-demand); 4848 fixed_contrib += demand; 4849 } 4850 } 4851 sum->add_domain(proto.min_level() - fixed_contrib); 4852 sum->add_domain(proto.max_level() - fixed_contrib); 4853 context_->UpdateRuleStats("reservoir: converted to linear"); 4854 return RemoveConstraint(ct); 4855 } 4856 4857 if (gcd > 1) { 4858 for (int i = 0; i < proto.level_changes_size(); ++i) { 4859 proto.set_level_changes(i, proto.level_changes(i) / gcd); 4860 } 4861 4862 // Adjust min and max levels. 4863 // max level is always rounded down. 4864 // min level is always rounded up. 4865 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()}) 4866 .InverseMultiplicationBy(gcd); 4867 proto.set_min_level(reduced_domain.Min()); 4868 proto.set_max_level(reduced_domain.Max()); 4869 context_->UpdateRuleStats( 4870 "reservoir: simplify level_changes and levels by gcd."); 4871 } 4872 4873 if (num_positives == 1 && num_negatives > 0) { 4874 context_->UpdateRuleStats( 4875 "TODO reservoir: one producer, multiple consumers."); 4876 } 4877 4878 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set; 4879 for (int i = 0; i < proto.level_changes_size(); ++i) { 4880 const LinearExpressionProto& time = proto.time_exprs(i); 4881 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min() 4882 : time.vars(0); 4883 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0); 4884 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple( 4885 var, coeff, 4886 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(), 4887 proto.active_literals(i)); 4888 if (time_active_set.contains(key)) { 4889 context_->UpdateRuleStats("TODO reservoir: merge synchronized events."); 4890 break; 4891 } else { 4892 time_active_set.insert(key); 4893 } 4894 } 4895 4896 return changed; 4897 } 4898 4899 // TODO(user): It is probably more efficient to keep all the bool_and in a 4900 // global place during all the presolve, and just output them at the end 4901 // rather than modifying more than once the proto. 4902 void CpModelPresolver::ExtractBoolAnd() { 4903 absl::flat_hash_map<int, int> ref_to_bool_and; 4904 const int num_constraints = context_->working_model->constraints_size(); 4905 std::vector<int> to_remove; 4906 for (int c = 0; c < num_constraints; ++c) { 4907 const ConstraintProto& ct = context_->working_model->constraints(c); 4908 if (HasEnforcementLiteral(ct)) continue; 4909 4910 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr && 4911 ct.bool_or().literals().size() == 2) { 4912 AddImplication(NegatedRef(ct.bool_or().literals(0)), 4913 ct.bool_or().literals(1), context_->working_model, 4914 &ref_to_bool_and); 4915 to_remove.push_back(c); 4916 continue; 4917 } 4918 4919 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne && 4920 ct.at_most_one().literals().size() == 2) { 4921 AddImplication(ct.at_most_one().literals(0), 4922 NegatedRef(ct.at_most_one().literals(1)), 4923 context_->working_model, &ref_to_bool_and); 4924 to_remove.push_back(c); 4925 continue; 4926 } 4927 } 4928 4929 context_->UpdateNewConstraintsVariableUsage(); 4930 for (const int c : to_remove) { 4931 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 4932 CHECK(RemoveConstraint(ct)); 4933 context_->UpdateConstraintVariableUsage(c); 4934 } 4935 } 4936 4937 // TODO(user): It might make sense to run this in parallel. The same apply for 4938 // other expansive and self-contains steps like symmetry detection, etc... 4939 void CpModelPresolver::Probe() { 4940 Model model; 4941 if (!LoadModelForProbing(context_, &model)) return; 4942 4943 // Probe. 4944 // 4945 // TODO(user): Compute the transitive reduction instead of just the 4946 // equivalences, and use the newly learned binary clauses? 4947 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>(); 4948 auto* sat_solver = model.GetOrCreate<SatSolver>(); 4949 auto* mapping = model.GetOrCreate<CpModelMapping>(); 4950 auto* prober = model.GetOrCreate<Prober>(); 4951 prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0); 4952 context_->time_limit()->AdvanceDeterministicTime( 4953 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime()); 4954 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) { 4955 return (void)context_->NotifyThatModelIsUnsat("during probing"); 4956 } 4957 4958 // Update the presolve context with fixed Boolean variables. 4959 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0); 4960 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) { 4961 const Literal l = sat_solver->LiteralTrail()[i]; 4962 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable()); 4963 if (var >= 0) { 4964 const int ref = l.IsPositive() ? var : NegatedRef(var); 4965 if (!context_->SetLiteralToTrue(ref)) return; 4966 } 4967 } 4968 4969 const int num_variables = context_->working_model->variables().size(); 4970 auto* integer_trail = model.GetOrCreate<IntegerTrail>(); 4971 for (int var = 0; var < num_variables; ++var) { 4972 // Restrict IntegerVariable domain. 4973 // Note that Boolean are already dealt with above. 4974 if (!mapping->IsBoolean(var)) { 4975 if (!context_->IntersectDomainWith( 4976 var, 4977 integer_trail->InitialVariableDomain(mapping->Integer(var)))) { 4978 return; 4979 } 4980 continue; 4981 } 4982 4983 // Add Boolean equivalence relations. 4984 const Literal l = mapping->Literal(var); 4985 const Literal r = implication_graph->RepresentativeOf(l); 4986 if (r != l) { 4987 const int r_var = 4988 mapping->GetProtoVariableFromBooleanVariable(r.Variable()); 4989 CHECK_GE(r_var, 0); 4990 context_->StoreBooleanEqualityRelation( 4991 var, r.IsPositive() ? r_var : NegatedRef(r_var)); 4992 } 4993 } 4994 } 4995 4996 // TODO(user): What to do with the at_most_one/exactly_one constraints? 4997 // currently we do not take them into account here. 4998 void CpModelPresolver::PresolvePureSatPart() { 4999 // TODO(user): Reenable some SAT presolve with 5000 // keep_all_feasible_solutions set to true. 5001 if (context_->ModelIsUnsat() || context_->keep_all_feasible_solutions) return; 5002 5003 const int num_variables = context_->working_model->variables_size(); 5004 SatPostsolver sat_postsolver(num_variables); 5005 SatPresolver sat_presolver(&sat_postsolver, logger_); 5006 sat_presolver.SetNumVariables(num_variables); 5007 sat_presolver.SetTimeLimit(context_->time_limit()); 5008 5009 SatParameters params = context_->params(); 5010 5011 // The "full solver" postsolve does not support changing the value of a 5012 // variable from the solution of the presolved problem, and we do need this 5013 // for blocked clause. It should be possible to allow for this by adding extra 5014 // variable to the mapping model at presolve and some linking constraints, but 5015 // this is messy. 5016 if (params.debug_postsolve_with_full_solver()) { 5017 params.set_presolve_blocked_clause(false); 5018 } 5019 5020 // TODO(user): BVA takes time_exprs and do not seems to help on the minizinc 5021 // benchmarks. That said, it was useful on pure sat problems, so we may 5022 // want to enable it. 5023 params.set_presolve_use_bva(false); 5024 sat_presolver.SetParameters(params); 5025 5026 // Converts a cp_model literal ref to a sat::Literal used by SatPresolver. 5027 absl::flat_hash_set<int> used_variables; 5028 auto convert = [&used_variables](int ref) { 5029 used_variables.insert(PositiveRef(ref)); 5030 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true); 5031 return Literal(BooleanVariable(NegatedRef(ref)), false); 5032 }; 5033 5034 // We need all Boolean constraints to be presolved before loading them below. 5035 // Otherwise duplicate literals might result in a wrong outcome. 5036 // 5037 // TODO(user): Be a bit more efficient, and enforce this invariant before we 5038 // reach this point? 5039 for (int c = 0; c < context_->working_model->constraints_size(); ++c) { 5040 const ConstraintProto& ct = context_->working_model->constraints(c); 5041 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr || 5042 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) { 5043 if (PresolveOneConstraint(c)) { 5044 context_->UpdateConstraintVariableUsage(c); 5045 } 5046 if (context_->ModelIsUnsat()) return; 5047 } 5048 } 5049 5050 // Load all Clauses into the presolver and remove them from the current model. 5051 // 5052 // TODO(user): The removing and adding back of the same clause when nothing 5053 // happens in the presolve "seems" bad. That said, complexity wise, it is 5054 // a lot faster that what happens in the presolve though. 5055 // 5056 // TODO(user): Add the "small" at most one constraints to the SAT presolver by 5057 // expanding them to implications? that could remove a lot of clauses. Do that 5058 // when we are sure we don't load duplicates at_most_one/implications in the 5059 // solver. Ideally, the pure sat presolve could be improved to handle at most 5060 // one, and we could merge this with what the ProcessSetPPC() is doing. 5061 std::vector<Literal> clause; 5062 int num_removed_constraints = 0; 5063 for (int i = 0; i < context_->working_model->constraints_size(); ++i) { 5064 const ConstraintProto& ct = context_->working_model->constraints(i); 5065 5066 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) { 5067 ++num_removed_constraints; 5068 clause.clear(); 5069 for (const int ref : ct.bool_or().literals()) { 5070 clause.push_back(convert(ref)); 5071 } 5072 for (const int ref : ct.enforcement_literal()) { 5073 clause.push_back(convert(ref).Negated()); 5074 } 5075 sat_presolver.AddClause(clause); 5076 5077 context_->working_model->mutable_constraints(i)->Clear(); 5078 context_->UpdateConstraintVariableUsage(i); 5079 continue; 5080 } 5081 5082 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) { 5083 ++num_removed_constraints; 5084 std::vector<Literal> clause; 5085 for (const int ref : ct.enforcement_literal()) { 5086 clause.push_back(convert(ref).Negated()); 5087 } 5088 clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below. 5089 for (const int ref : ct.bool_and().literals()) { 5090 clause.back() = convert(ref); 5091 sat_presolver.AddClause(clause); 5092 } 5093 5094 context_->working_model->mutable_constraints(i)->Clear(); 5095 context_->UpdateConstraintVariableUsage(i); 5096 continue; 5097 } 5098 } 5099 5100 // Abort early if there was no Boolean constraints. 5101 if (num_removed_constraints == 0) return; 5102 5103 // Mark the variables appearing elsewhere or in the objective as non-removable 5104 // by the sat presolver. 5105 // 5106 // TODO(user): do not remove variable that appear in the decision heuristic? 5107 // TODO(user): We could go further for variable with only one polarity by 5108 // removing variable from the objective if they can be set to their "low" 5109 // objective value, and also removing enforcement literal that can be set to 5110 // false and don't appear elsewhere. 5111 std::vector<bool> can_be_removed(num_variables, false); 5112 for (int i = 0; i < num_variables; ++i) { 5113 if (context_->VarToConstraints(i).empty()) { 5114 can_be_removed[i] = true; 5115 } 5116 5117 // Because we might not have reached the presove "fixed point" above, some 5118 // variable in the added clauses might be fixed. We need to indicate this to 5119 // the SAT presolver. 5120 if (used_variables.contains(i) && context_->IsFixed(i)) { 5121 if (context_->LiteralIsTrue(i)) { 5122 sat_presolver.AddClause({convert(i)}); 5123 } else { 5124 sat_presolver.AddClause({convert(NegatedRef(i))}); 5125 } 5126 } 5127 } 5128 5129 // Run the presolve for a small number of passes. 5130 // TODO(user): Add probing like we do in the pure sat solver presolve loop? 5131 // TODO(user): Add a time limit, this can be slow on big SAT problem. 5132 const int num_passes = params.presolve_use_bva() ? 4 : 1; 5133 for (int i = 0; i < num_passes; ++i) { 5134 const int old_num_clause = sat_postsolver.NumClauses(); 5135 if (!sat_presolver.Presolve(can_be_removed)) { 5136 VLOG(1) << "UNSAT during SAT presolve."; 5137 return (void)context_->NotifyThatModelIsUnsat(); 5138 } 5139 if (old_num_clause == sat_postsolver.NumClauses()) break; 5140 } 5141 5142 // Add any new variables to our internal structure. 5143 const int new_num_variables = sat_presolver.NumVariables(); 5144 if (new_num_variables > context_->working_model->variables_size()) { 5145 VLOG(1) << "New variables added by the SAT presolver."; 5146 for (int i = context_->working_model->variables_size(); 5147 i < new_num_variables; ++i) { 5148 IntegerVariableProto* var_proto = 5149 context_->working_model->add_variables(); 5150 var_proto->add_domain(0); 5151 var_proto->add_domain(1); 5152 } 5153 context_->InitializeNewDomains(); 5154 } 5155 5156 // Add the presolver clauses back into the model. 5157 ExtractClauses(/*use_bool_and=*/true, sat_presolver, context_->working_model); 5158 5159 // Update the constraints <-> variables graph. 5160 context_->UpdateNewConstraintsVariableUsage(); 5161 5162 // Add the sat_postsolver clauses to mapping_model. 5163 // 5164 // TODO(user): Mark removed variable as removed to detect any potential bugs. 5165 ExtractClauses(/*use_bool_and=*/false, sat_postsolver, 5166 context_->mapping_model); 5167 } 5168 5169 // TODO(user): The idea behind this was that it is better to have an objective 5170 // as spreaded as possible. However on some problems this have the opposite 5171 // effect. Like on a triangular matrix where each expansion reduced the size 5172 // of the objective by one. Investigate and fix? 5173 void CpModelPresolver::ExpandObjective() { 5174 if (context_->ModelIsUnsat()) return; 5175 5176 // The objective is already loaded in the context, but we re-canonicalize 5177 // it with the latest information. 5178 if (!context_->CanonicalizeObjective()) { 5179 (void)context_->NotifyThatModelIsUnsat(); 5180 return; 5181 } 5182 5183 if (context_->time_limit()->LimitReached()) { 5184 context_->WriteObjectiveToProto(); 5185 return; 5186 } 5187 5188 // If the objective is a single variable, then we can usually remove this 5189 // variable if it is only used in one linear equality constraint and we do 5190 // just one expansion. This is because the domain of the variable will be 5191 // transferred to our objective_domain. 5192 int unique_expanded_constraint = -1; 5193 const bool objective_was_a_single_variable = 5194 context_->ObjectiveMap().size() == 1; 5195 5196 // To avoid a bad complexity, we need to compute the number of relevant 5197 // constraints for each variables. 5198 const int num_variables = context_->working_model->variables_size(); 5199 const int num_constraints = context_->working_model->constraints_size(); 5200 absl::flat_hash_set<int> relevant_constraints; 5201 std::vector<int> var_to_num_relevant_constraints(num_variables, 0); 5202 for (int ct_index = 0; ct_index < num_constraints; ++ct_index) { 5203 const ConstraintProto& ct = context_->working_model->constraints(ct_index); 5204 // Skip everything that is not a linear equality constraint. 5205 if (!ct.enforcement_literal().empty() || 5206 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear || 5207 ct.linear().domain().size() != 2 || 5208 ct.linear().domain(0) != ct.linear().domain(1)) { 5209 continue; 5210 } 5211 5212 relevant_constraints.insert(ct_index); 5213 const int num_terms = ct.linear().vars_size(); 5214 for (int i = 0; i < num_terms; ++i) { 5215 var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]++; 5216 } 5217 } 5218 5219 std::set<int> var_to_process; 5220 for (const auto entry : context_->ObjectiveMap()) { 5221 const int var = entry.first; 5222 CHECK(RefIsPositive(var)); 5223 if (var_to_num_relevant_constraints[var] != 0) { 5224 var_to_process.insert(var); 5225 } 5226 } 5227 5228 // We currently never expand a variable more than once. 5229 int num_expansions = 0; 5230 int last_expanded_objective_var; 5231 absl::flat_hash_set<int> processed_vars; 5232 std::vector<int> new_vars_in_objective; 5233 while (!relevant_constraints.empty()) { 5234 // Find a not yet expanded var. 5235 int objective_var = -1; 5236 while (!var_to_process.empty()) { 5237 const int var = *var_to_process.begin(); 5238 CHECK(!processed_vars.contains(var)); 5239 if (var_to_num_relevant_constraints[var] == 0) { 5240 processed_vars.insert(var); 5241 var_to_process.erase(var); 5242 continue; 5243 } 5244 if (!context_->ObjectiveMap().contains(var)) { 5245 // We do not mark it as processed in case it re-appear later. 5246 var_to_process.erase(var); 5247 continue; 5248 } 5249 objective_var = var; 5250 break; 5251 } 5252 5253 if (objective_var == -1) break; 5254 CHECK(RefIsPositive(objective_var)); 5255 processed_vars.insert(objective_var); 5256 var_to_process.erase(objective_var); 5257 5258 int expanded_linear_index = -1; 5259 int64_t objective_coeff_in_expanded_constraint; 5260 int64_t size_of_expanded_constraint = 0; 5261 const auto& non_deterministic_list = 5262 context_->VarToConstraints(objective_var); 5263 std::vector<int> constraints_with_objective(non_deterministic_list.begin(), 5264 non_deterministic_list.end()); 5265 std::sort(constraints_with_objective.begin(), 5266 constraints_with_objective.end()); 5267 for (const int ct_index : constraints_with_objective) { 5268 if (relevant_constraints.count(ct_index) == 0) continue; 5269 const ConstraintProto& ct = 5270 context_->working_model->constraints(ct_index); 5271 5272 // This constraint is relevant now, but it will never be later because 5273 // it will contain the objective_var which is already processed! 5274 relevant_constraints.erase(ct_index); 5275 const int num_terms = ct.linear().vars_size(); 5276 for (int i = 0; i < num_terms; ++i) { 5277 var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]--; 5278 } 5279 5280 // Find the coefficient of objective_var in this constraint, and perform 5281 // various checks. 5282 // 5283 // TODO(user): This can crash the program if for some reason the linear 5284 // constraint was not canonicalized and contains the objective variable 5285 // twice. Currently this can only happen if the time limit was reached 5286 // before all constraints where processed, but because we abort at the 5287 // beginning of the function when this is the case we should be safe. 5288 // However, it might be more robust to just handle this case properly. 5289 bool is_present = false; 5290 int64_t objective_coeff; 5291 for (int i = 0; i < num_terms; ++i) { 5292 const int ref = ct.linear().vars(i); 5293 const int64_t coeff = ct.linear().coeffs(i); 5294 if (PositiveRef(ref) == objective_var) { 5295 CHECK(!is_present) << "Duplicate variables not supported."; 5296 is_present = true; 5297 objective_coeff = (ref == objective_var) ? coeff : -coeff; 5298 } else { 5299 // This is not possible since we only consider relevant constraints. 5300 CHECK(!processed_vars.contains(PositiveRef(ref))); 5301 } 5302 } 5303 CHECK(is_present); 5304 5305 // We use the longest equality we can find. 5306 // 5307 // TODO(user): Deal with objective_coeff with a magnitude greater than 5308 // 1? This will only be possible if we change the objective coeff type 5309 // to double. 5310 if (std::abs(objective_coeff) == 1 && 5311 num_terms > size_of_expanded_constraint) { 5312 expanded_linear_index = ct_index; 5313 size_of_expanded_constraint = num_terms; 5314 objective_coeff_in_expanded_constraint = objective_coeff; 5315 } 5316 } 5317 5318 if (expanded_linear_index != -1) { 5319 // Update the objective map. Note that the division is possible because 5320 // currently we only expand with coeff with a magnitude of 1. 5321 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1); 5322 const ConstraintProto& ct = 5323 context_->working_model->constraints(expanded_linear_index); 5324 if (!context_->SubstituteVariableInObjective( 5325 objective_var, objective_coeff_in_expanded_constraint, ct, 5326 &new_vars_in_objective)) { 5327 if (context_->ModelIsUnsat()) return; 5328 continue; 5329 } 5330 5331 context_->UpdateRuleStats("objective: expanded objective constraint."); 5332 5333 // Add not yet processed new variables. 5334 for (const int var : new_vars_in_objective) { 5335 if (!processed_vars.contains(var)) var_to_process.insert(var); 5336 } 5337 5338 // If the objective variable wasn't used in other constraints and it can 5339 // be reconstructed whatever the value of the other variables, we can 5340 // remove the constraint. 5341 // 5342 // TODO(user): It should be possible to refactor the code so this is 5343 // automatically done by the linear constraint singleton presolve rule. 5344 if (context_->VarToConstraints(objective_var).size() == 1 && 5345 !context_->keep_all_feasible_solutions) { 5346 // Compute implied domain on objective_var. 5347 Domain implied_domain = ReadDomainFromProto(ct.linear()); 5348 for (int i = 0; i < size_of_expanded_constraint; ++i) { 5349 const int ref = ct.linear().vars(i); 5350 if (PositiveRef(ref) == objective_var) continue; 5351 implied_domain = 5352 implied_domain 5353 .AdditionWith(context_->DomainOf(ref).MultiplicationBy( 5354 -ct.linear().coeffs(i))) 5355 .RelaxIfTooComplex(); 5356 } 5357 implied_domain = implied_domain.InverseMultiplicationBy( 5358 objective_coeff_in_expanded_constraint); 5359 5360 // Remove the constraint if the implied domain is included in the 5361 // domain of the objective_var term. 5362 if (implied_domain.IsIncludedIn(context_->DomainOf(objective_var))) { 5363 context_->MarkVariableAsRemoved(objective_var); 5364 context_->UpdateRuleStats("objective: removed objective constraint."); 5365 *(context_->mapping_model->add_constraints()) = ct; 5366 context_->working_model->mutable_constraints(expanded_linear_index) 5367 ->Clear(); 5368 context_->UpdateConstraintVariableUsage(expanded_linear_index); 5369 } else { 5370 unique_expanded_constraint = expanded_linear_index; 5371 } 5372 } 5373 5374 ++num_expansions; 5375 last_expanded_objective_var = objective_var; 5376 } 5377 } 5378 5379 // Special case: If we just did one expansion of a single variable, then we 5380 // can remove the expanded constraints if the objective wasn't used elsewhere. 5381 if (num_expansions == 1 && objective_was_a_single_variable && 5382 unique_expanded_constraint != -1) { 5383 context_->UpdateRuleStats( 5384 "objective: removed unique objective constraint."); 5385 ConstraintProto* mutable_ct = context_->working_model->mutable_constraints( 5386 unique_expanded_constraint); 5387 *(context_->mapping_model->add_constraints()) = *mutable_ct; 5388 mutable_ct->Clear(); 5389 context_->MarkVariableAsRemoved(last_expanded_objective_var); 5390 context_->UpdateConstraintVariableUsage(unique_expanded_constraint); 5391 } 5392 5393 // We re-do a canonicalization with the final linear expression. 5394 if (!context_->CanonicalizeObjective()) { 5395 (void)context_->NotifyThatModelIsUnsat(); 5396 return; 5397 } 5398 context_->WriteObjectiveToProto(); 5399 } 5400 5401 void CpModelPresolver::MergeNoOverlapConstraints() { 5402 if (context_->ModelIsUnsat()) return; 5403 5404 const int num_constraints = context_->working_model->constraints_size(); 5405 int old_num_no_overlaps = 0; 5406 int old_num_intervals = 0; 5407 5408 // Extract the no-overlap constraints. 5409 std::vector<int> disjunctive_index; 5410 std::vector<std::vector<Literal>> cliques; 5411 for (int c = 0; c < num_constraints; ++c) { 5412 const ConstraintProto& ct = context_->working_model->constraints(c); 5413 if (ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) { 5414 continue; 5415 } 5416 std::vector<Literal> clique; 5417 for (const int i : ct.no_overlap().intervals()) { 5418 clique.push_back(Literal(BooleanVariable(i), true)); 5419 } 5420 cliques.push_back(clique); 5421 disjunctive_index.push_back(c); 5422 5423 old_num_no_overlaps++; 5424 old_num_intervals += clique.size(); 5425 } 5426 if (old_num_no_overlaps == 0) return; 5427 5428 // We reuse the max-clique code from sat. 5429 Model local_model; 5430 local_model.GetOrCreate<Trail>()->Resize(num_constraints); 5431 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>(); 5432 graph->Resize(num_constraints); 5433 for (const std::vector<Literal>& clique : cliques) { 5434 // All variables at false is always a valid solution of the local model, 5435 // so this should never return UNSAT. 5436 CHECK(graph->AddAtMostOne(clique)); 5437 } 5438 CHECK(graph->DetectEquivalences()); 5439 graph->TransformIntoMaxCliques( 5440 &cliques, context_->params().merge_no_overlap_work_limit()); 5441 5442 // Replace each no-overlap with an extended version, or remove if empty. 5443 int new_num_no_overlaps = 0; 5444 int new_num_intervals = 0; 5445 for (int i = 0; i < cliques.size(); ++i) { 5446 const int ct_index = disjunctive_index[i]; 5447 ConstraintProto* ct = 5448 context_->working_model->mutable_constraints(ct_index); 5449 ct->Clear(); 5450 if (cliques[i].empty()) continue; 5451 for (const Literal l : cliques[i]) { 5452 CHECK(l.IsPositive()); 5453 ct->mutable_no_overlap()->add_intervals(l.Variable().value()); 5454 } 5455 new_num_no_overlaps++; 5456 new_num_intervals += cliques[i].size(); 5457 } 5458 if (old_num_intervals != new_num_intervals || 5459 old_num_no_overlaps != new_num_no_overlaps) { 5460 VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (", 5461 old_num_intervals, " intervals) into ", 5462 new_num_no_overlaps, " no-overlaps (", 5463 new_num_intervals, " intervals)."); 5464 context_->UpdateRuleStats("no_overlap: merged constraints"); 5465 } 5466 } 5467 5468 // TODO(user): Should we take into account the exactly_one constraints? note 5469 // that such constraint cannot be extended. If if a literal implies two literals 5470 // at one inside an exactly one constraint then it must be false. Similarly if 5471 // it implies all literals at zero inside the exactly one. 5472 void CpModelPresolver::TransformIntoMaxCliques() { 5473 if (context_->ModelIsUnsat()) return; 5474 5475 auto convert = [](int ref) { 5476 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true); 5477 return Literal(BooleanVariable(NegatedRef(ref)), false); 5478 }; 5479 const int num_constraints = context_->working_model->constraints_size(); 5480 5481 // Extract the bool_and and at_most_one constraints. 5482 std::vector<std::vector<Literal>> cliques; 5483 5484 for (int c = 0; c < num_constraints; ++c) { 5485 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 5486 if (ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) { 5487 std::vector<Literal> clique; 5488 for (const int ref : ct->at_most_one().literals()) { 5489 clique.push_back(convert(ref)); 5490 } 5491 cliques.push_back(clique); 5492 if (RemoveConstraint(ct)) { 5493 context_->UpdateConstraintVariableUsage(c); 5494 } 5495 } else if (ct->constraint_case() == 5496 ConstraintProto::ConstraintCase::kBoolAnd) { 5497 if (ct->enforcement_literal().size() != 1) continue; 5498 const Literal enforcement = convert(ct->enforcement_literal(0)); 5499 for (const int ref : ct->bool_and().literals()) { 5500 if (ref == ct->enforcement_literal(0)) continue; 5501 cliques.push_back({enforcement, convert(ref).Negated()}); 5502 } 5503 if (RemoveConstraint(ct)) { 5504 context_->UpdateConstraintVariableUsage(c); 5505 } 5506 } 5507 } 5508 5509 int64_t num_literals_before = 0; 5510 const int num_old_cliques = cliques.size(); 5511 5512 // We reuse the max-clique code from sat. 5513 Model local_model; 5514 const int num_variables = context_->working_model->variables().size(); 5515 local_model.GetOrCreate<Trail>()->Resize(num_variables); 5516 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>(); 5517 graph->Resize(num_variables); 5518 for (const std::vector<Literal>& clique : cliques) { 5519 num_literals_before += clique.size(); 5520 if (!graph->AddAtMostOne(clique)) { 5521 return (void)context_->NotifyThatModelIsUnsat(); 5522 } 5523 } 5524 if (!graph->DetectEquivalences()) { 5525 return (void)context_->NotifyThatModelIsUnsat(); 5526 } 5527 graph->TransformIntoMaxCliques( 5528 &cliques, context_->params().merge_at_most_one_work_limit()); 5529 5530 // Add the Boolean variable equivalence detected by DetectEquivalences(). 5531 // Those are needed because TransformIntoMaxCliques() will replace all 5532 // variable by its representative. 5533 for (int var = 0; var < num_variables; ++var) { 5534 const Literal l = Literal(BooleanVariable(var), true); 5535 if (graph->RepresentativeOf(l) != l) { 5536 const Literal r = graph->RepresentativeOf(l); 5537 context_->StoreBooleanEqualityRelation( 5538 var, r.IsPositive() ? r.Variable().value() 5539 : NegatedRef(r.Variable().value())); 5540 } 5541 } 5542 5543 int num_new_cliques = 0; 5544 int64_t num_literals_after = 0; 5545 for (const std::vector<Literal>& clique : cliques) { 5546 if (clique.empty()) continue; 5547 num_new_cliques++; 5548 num_literals_after += clique.size(); 5549 ConstraintProto* ct = context_->working_model->add_constraints(); 5550 for (const Literal literal : clique) { 5551 if (literal.IsPositive()) { 5552 ct->mutable_at_most_one()->add_literals(literal.Variable().value()); 5553 } else { 5554 ct->mutable_at_most_one()->add_literals( 5555 NegatedRef(literal.Variable().value())); 5556 } 5557 } 5558 5559 // Make sure we do not have duplicate variable reference. 5560 PresolveAtMostOne(ct); 5561 } 5562 context_->UpdateNewConstraintsVariableUsage(); 5563 if (num_new_cliques != num_old_cliques) { 5564 context_->UpdateRuleStats("at_most_one: transformed into max clique."); 5565 } 5566 5567 if (num_old_cliques != num_new_cliques || 5568 num_literals_before != num_literals_after) { 5569 SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(", 5570 num_literals_before, " literals) into ", num_new_cliques, "(", 5571 num_literals_after, " literals) at_most_ones."); 5572 } 5573 } 5574 5575 namespace { 5576 5577 bool IsAffineIntAbs(ConstraintProto* ct) { 5578 if (ct->constraint_case() != ConstraintProto::kLinMax || 5579 ct->lin_max().exprs_size() != 2 || 5580 ct->lin_max().target().vars_size() > 1 || 5581 ct->lin_max().exprs(0).vars_size() != 1 || 5582 ct->lin_max().exprs(1).vars_size() != 1) { 5583 return false; 5584 } 5585 5586 const LinearArgumentProto& lin_max = ct->lin_max(); 5587 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false; 5588 if (PositiveRef(lin_max.exprs(0).vars(0)) != 5589 PositiveRef(lin_max.exprs(1).vars(0))) { 5590 return false; 5591 } 5592 5593 const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0)) 5594 ? lin_max.exprs(0).coeffs(0) 5595 : -lin_max.exprs(0).coeffs(0); 5596 const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0)) 5597 ? lin_max.exprs(1).coeffs(0) 5598 : -lin_max.exprs(1).coeffs(0); 5599 return left_coeff == -right_coeff; 5600 } 5601 5602 } // namespace 5603 5604 bool CpModelPresolver::PresolveOneConstraint(int c) { 5605 if (context_->ModelIsUnsat()) return false; 5606 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 5607 5608 // Generic presolve to exploit variable/literal equivalence. 5609 if (ExploitEquivalenceRelations(c, ct)) { 5610 context_->UpdateConstraintVariableUsage(c); 5611 } 5612 5613 // Generic presolve for reified constraint. 5614 if (PresolveEnforcementLiteral(ct)) { 5615 context_->UpdateConstraintVariableUsage(c); 5616 } 5617 5618 // Call the presolve function for this constraint if any. 5619 switch (ct->constraint_case()) { 5620 case ConstraintProto::ConstraintCase::kBoolOr: 5621 return PresolveBoolOr(ct); 5622 case ConstraintProto::ConstraintCase::kBoolAnd: 5623 return PresolveBoolAnd(ct); 5624 case ConstraintProto::ConstraintCase::kAtMostOne: 5625 return PresolveAtMostOne(ct); 5626 case ConstraintProto::ConstraintCase::kExactlyOne: 5627 return PresolveExactlyOne(ct); 5628 case ConstraintProto::ConstraintCase::kBoolXor: 5629 return PresolveBoolXor(ct); 5630 case ConstraintProto::ConstraintCase::kLinMax: 5631 if (CanonicalizeLinMax(ct)) { 5632 context_->UpdateConstraintVariableUsage(c); 5633 } 5634 if (IsAffineIntAbs(ct)) { 5635 return PresolveIntAbs(ct); 5636 } else { 5637 return PresolveLinMax(ct); 5638 } 5639 case ConstraintProto::ConstraintCase::kIntProd: 5640 return PresolveIntProd(ct); 5641 case ConstraintProto::ConstraintCase::kIntDiv: 5642 return PresolveIntDiv(ct); 5643 case ConstraintProto::ConstraintCase::kIntMod: 5644 return PresolveIntMod(ct); 5645 case ConstraintProto::ConstraintCase::kLinear: { 5646 // In the presence of affine relation, it is possible that the sign of a 5647 // variable change during canonicalization, and a variable that could 5648 // freely move in one direction can no longer do so. So we make sure we 5649 // always remove c from all the maps before re-inserting it in 5650 // PropagateDomainsInLinear(). 5651 // 5652 // TODO(user): The move in only one direction code is redundant with the 5653 // dual bound strengthening code. So maybe we don't need both. 5654 for (const int ref : ct->linear().vars()) { 5655 const int var = PositiveRef(ref); 5656 context_->var_to_lb_only_constraints[var].erase(c); 5657 context_->var_to_ub_only_constraints[var].erase(c); 5658 } 5659 5660 if (CanonicalizeLinear(ct)) { 5661 context_->UpdateConstraintVariableUsage(c); 5662 } 5663 if (PropagateDomainsInLinear(c, ct)) { 5664 context_->UpdateConstraintVariableUsage(c); 5665 } 5666 if (PresolveSmallLinear(ct)) { 5667 context_->UpdateConstraintVariableUsage(c); 5668 } 5669 if (PresolveLinearEqualityWithModulo(ct)) { 5670 context_->UpdateConstraintVariableUsage(c); 5671 } 5672 // We first propagate the domains before calling this presolve rule. 5673 if (RemoveSingletonInLinear(ct)) { 5674 context_->UpdateConstraintVariableUsage(c); 5675 5676 // There is no need to re-do a propagation here, but the constraint 5677 // size might have been reduced. 5678 if (PresolveSmallLinear(ct)) { 5679 context_->UpdateConstraintVariableUsage(c); 5680 } 5681 } 5682 if (PresolveSmallLinear(ct)) { 5683 context_->UpdateConstraintVariableUsage(c); 5684 } 5685 if (PresolveLinearOnBooleans(ct)) { 5686 context_->UpdateConstraintVariableUsage(c); 5687 } 5688 if (ct->constraint_case() == ConstraintProto::kLinear) { 5689 const int old_num_enforcement_literals = ct->enforcement_literal_size(); 5690 ExtractEnforcementLiteralFromLinearConstraint(c, ct); 5691 if (ct->constraint_case() == 5692 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) { 5693 context_->UpdateConstraintVariableUsage(c); 5694 return true; 5695 } 5696 if (ct->enforcement_literal_size() > old_num_enforcement_literals && 5697 PresolveSmallLinear(ct)) { 5698 context_->UpdateConstraintVariableUsage(c); 5699 } 5700 } 5701 5702 // Note that it is important for this to be last, so that if a constraint 5703 // is marked as being in one direction, no other presolve is applied until 5704 // it is processed again and unmarked at the beginning of this case. 5705 if (ct->constraint_case() == ConstraintProto::kLinear && 5706 DetectAndProcessOneSidedLinearConstraint(c, ct)) { 5707 context_->UpdateConstraintVariableUsage(c); 5708 } 5709 return false; 5710 } 5711 case ConstraintProto::ConstraintCase::kInterval: 5712 return PresolveInterval(c, ct); 5713 case ConstraintProto::ConstraintCase::kInverse: 5714 return PresolveInverse(ct); 5715 case ConstraintProto::ConstraintCase::kElement: 5716 return PresolveElement(ct); 5717 case ConstraintProto::ConstraintCase::kTable: 5718 return PresolveTable(ct); 5719 case ConstraintProto::ConstraintCase::kAllDiff: 5720 return PresolveAllDiff(ct); 5721 case ConstraintProto::ConstraintCase::kNoOverlap: 5722 return PresolveNoOverlap(ct); 5723 case ConstraintProto::ConstraintCase::kNoOverlap2D: 5724 return PresolveNoOverlap2D(c, ct); 5725 case ConstraintProto::ConstraintCase::kCumulative: 5726 return PresolveCumulative(ct); 5727 case ConstraintProto::ConstraintCase::kCircuit: 5728 return PresolveCircuit(ct); 5729 case ConstraintProto::ConstraintCase::kRoutes: 5730 return PresolveRoutes(ct); 5731 case ConstraintProto::ConstraintCase::kAutomaton: 5732 return PresolveAutomaton(ct); 5733 case ConstraintProto::ConstraintCase::kReservoir: 5734 return PresolveReservoir(ct); 5735 default: 5736 return false; 5737 } 5738 } 5739 5740 // This is called with constraint c1 whose literals are included in the literals 5741 // of c2. 5742 // 5743 // Returns false iff the model is UNSAT. 5744 bool CpModelPresolver::ProcessSetPPCSubset( 5745 int c1, int c2, const std::vector<int>& c2_minus_c1, 5746 const std::vector<int>& original_constraint_index, 5747 std::vector<bool>* marked_for_removal) { 5748 if (context_->ModelIsUnsat()) return false; 5749 5750 CHECK(!(*marked_for_removal)[c1]); 5751 CHECK(!(*marked_for_removal)[c2]); 5752 5753 ConstraintProto* ct1 = context_->working_model->mutable_constraints( 5754 original_constraint_index[c1]); 5755 ConstraintProto* ct2 = context_->working_model->mutable_constraints( 5756 original_constraint_index[c2]); 5757 5758 if ((ct1->constraint_case() == ConstraintProto::kBoolOr || 5759 ct1->constraint_case() == ConstraintProto::kExactlyOne) && 5760 (ct2->constraint_case() == ConstraintProto::kAtMostOne || 5761 ct2->constraint_case() == ConstraintProto::kExactlyOne)) { 5762 context_->UpdateRuleStats("setppc: bool_or in at_most_one."); 5763 5764 // Fix extras in c2 to 0, note that these will be removed from the 5765 // constraint later. 5766 for (const int literal : c2_minus_c1) { 5767 if (!context_->SetLiteralToFalse(literal)) return false; 5768 context_->UpdateRuleStats("setppc: fixed variables"); 5769 } 5770 5771 // Change c2 to exactly_one if not already. 5772 if (ct2->constraint_case() != ConstraintProto::kExactlyOne) { 5773 ConstraintProto copy = *ct2; 5774 (*ct2->mutable_exactly_one()->mutable_literals()) = 5775 copy.at_most_one().literals(); 5776 } 5777 5778 // Remove c1. 5779 (*marked_for_removal)[c1] = true; 5780 ct1->Clear(); 5781 context_->UpdateConstraintVariableUsage(original_constraint_index[c1]); 5782 return true; 5783 } 5784 5785 if ((ct1->constraint_case() == ConstraintProto::kBoolOr || 5786 ct1->constraint_case() == ConstraintProto::kExactlyOne) && 5787 ct2->constraint_case() == ConstraintProto::kBoolOr) { 5788 context_->UpdateRuleStats("setppc: removed dominated constraints"); 5789 5790 (*marked_for_removal)[c2] = true; 5791 ct2->Clear(); 5792 context_->UpdateConstraintVariableUsage(original_constraint_index[c2]); 5793 return true; 5794 } 5795 5796 if (ct1->constraint_case() == ConstraintProto::kAtMostOne && 5797 (ct2->constraint_case() == ConstraintProto::kAtMostOne || 5798 ct2->constraint_case() == ConstraintProto::kExactlyOne)) { 5799 context_->UpdateRuleStats("setppc: removed dominated constraints"); 5800 (*marked_for_removal)[c1] = true; 5801 ct1->Clear(); 5802 context_->UpdateConstraintVariableUsage(original_constraint_index[c1]); 5803 return true; 5804 } 5805 5806 if (ct1->constraint_case() == ConstraintProto::kExactlyOne && 5807 ct2->constraint_case() == ConstraintProto::kLinear) { 5808 // If we have an exactly one in a linear, we can shift the coefficients of 5809 // all these variables by any constant value. For now, since we only deal 5810 // with positive coefficient, we shift by the min. 5811 // 5812 // TODO(user): It might be more interesting to maximize the number of terms 5813 // that are shifted to zero to reduce the constraint size. 5814 absl::flat_hash_set<int> literals(ct1->exactly_one().literals().begin(), 5815 ct1->exactly_one().literals().end()); 5816 int64_t min_coeff = std::numeric_limits<int64_t>::max(); 5817 int num_matches = 0; 5818 for (int i = 0; i < ct2->linear().vars().size(); ++i) { 5819 const int var = ct2->linear().vars(i); 5820 if (literals.contains(var)) { 5821 ++num_matches; 5822 min_coeff = std::min(min_coeff, std::abs(ct2->linear().coeffs(i))); 5823 } 5824 } 5825 5826 // If a linear constraint contains more than one at most one, after 5827 // processing one, we might no longer have an inclusion. 5828 if (num_matches != literals.size()) return true; 5829 5830 // TODO(user): It would be cool to propagate other variable domains with 5831 // the knowledge that the partial sum in is [min_coeff, max_coeff]. I am 5832 // a bit relunctant to duplicate the code for that here. 5833 int new_size = 0; 5834 for (int i = 0; i < ct2->linear().vars().size(); ++i) { 5835 const int var = ct2->linear().vars(i); 5836 int64_t coeff = ct2->linear().coeffs(i); 5837 if (literals.contains(var)) { 5838 CHECK_GE(coeff, 0); 5839 if (coeff == min_coeff) continue; // delete term. 5840 coeff -= min_coeff; 5841 } 5842 ct2->mutable_linear()->set_vars(new_size, var); 5843 ct2->mutable_linear()->set_coeffs(new_size, coeff); 5844 ++new_size; 5845 } 5846 5847 ct2->mutable_linear()->mutable_vars()->Truncate(new_size); 5848 ct2->mutable_linear()->mutable_coeffs()->Truncate(new_size); 5849 FillDomainInProto( 5850 ReadDomainFromProto(ct2->linear()).AdditionWith(Domain(-min_coeff)), 5851 ct2->mutable_linear()); 5852 context_->UpdateConstraintVariableUsage(original_constraint_index[c2]); 5853 context_->UpdateRuleStats("setppc: reduced linear coefficients."); 5854 } 5855 5856 // We can't deduce anything in the last remaining case: 5857 // ct1->constraint_case() == ConstraintProto::kAtMostOne && 5858 // ct2->constraint_case() == ConstraintProto::kBoolOr 5859 5860 return true; 5861 } 5862 5863 // TODO(user): TransformIntoMaxCliques() convert the bool_and to 5864 // at_most_one, but maybe also duplicating them into bool_or would allow this 5865 // function to do more presolving. 5866 bool CpModelPresolver::ProcessSetPPC() { 5867 const int num_constraints = context_->working_model->constraints_size(); 5868 5869 // Signatures of all the constraints. In the signature the bit i is 1 if it 5870 // contains a literal l such that l%64 = i. 5871 std::vector<uint64_t> signatures; 5872 5873 // Graph of constraints to literals. constraint_literals[c] contains all the 5874 // literals in constraint indexed by 'c' in sorted order. 5875 std::vector<std::vector<int>> constraint_literals; 5876 5877 // Graph of literals to constraints. literals_to_constraints[l] contains the 5878 // vector of constraint indices in which literal 'l' or 'neg(l)' appears. 5879 std::vector<std::vector<int>> literals_to_constraints; 5880 5881 // vector of booleans indicating if the constraint was already removed. 5882 std::vector<bool> removed; 5883 5884 // The containers above use the local indices for setppc constraints. We store 5885 // the original constraint indices corresponding to those local indices here. 5886 std::vector<int> original_constraint_index; 5887 5888 // Fill the initial constraint <-> literal graph, compute signatures and 5889 // initialize other containers defined above. 5890 int num_setppc_constraints = 0; 5891 std::vector<int> temp_literals; 5892 if (context_->ModelIsUnsat()) return false; 5893 for (int c = 0; c < num_constraints; ++c) { 5894 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 5895 if (ct->constraint_case() == ConstraintProto::kBoolOr || 5896 ct->constraint_case() == ConstraintProto::kAtMostOne || 5897 ct->constraint_case() == ConstraintProto::kExactlyOne) { 5898 // Because TransformIntoMaxCliques() can detect literal equivalence 5899 // relation, we make sure the constraints are presolved before being 5900 // inspected. 5901 if (PresolveOneConstraint(c)) { 5902 context_->UpdateConstraintVariableUsage(c); 5903 } 5904 if (context_->ModelIsUnsat()) return false; 5905 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*ct)); 5906 } else if (ct->constraint_case() == ConstraintProto::kLinear) { 5907 // We also want to test inclusion with the pseudo-Boolean part of 5908 // linear constraints of size at least 3. Exactly one of size two are 5909 // equivalent literals, and we already deal with this case. 5910 // 5911 // TODO(user): This is not ideal as we currently only process exactly one 5912 // included into linear, and we add overhead by detecting all the other 5913 // cases that we ignore later. That said, we could just propagate a bit 5914 // more the domain if we know at_least_one or at_most_one between literals 5915 // in a linear constraint. 5916 const int size = ct->linear().vars().size(); 5917 if (size <= 2) continue; 5918 5919 // TODO(user): We only deal with every literal having a positive coeff 5920 // here. We should probably do the same with all negative. We can also 5921 // consider NegatedRef(var). 5922 temp_literals.clear(); 5923 for (int i = 0; i < size; ++i) { 5924 const int var = ct->linear().vars(i); 5925 const int64_t coeff = ct->linear().coeffs(i); 5926 if (!context_->CanBeUsedAsLiteral(var)) continue; 5927 if (!RefIsPositive(var)) continue; 5928 if (coeff < 0) continue; 5929 temp_literals.push_back(var); 5930 } 5931 if (temp_literals.size() <= 2) continue; 5932 constraint_literals.push_back(temp_literals); 5933 } else { 5934 continue; 5935 } 5936 5937 uint64_t signature = 0; 5938 for (const int literal : constraint_literals.back()) { 5939 const int positive_literal = PositiveRef(literal); 5940 signature |= (int64_t{1} << (positive_literal % 64)); 5941 DCHECK_GE(positive_literal, 0); 5942 if (positive_literal >= literals_to_constraints.size()) { 5943 literals_to_constraints.resize(positive_literal + 1); 5944 } 5945 literals_to_constraints[positive_literal].push_back( 5946 num_setppc_constraints); 5947 } 5948 signatures.push_back(signature); 5949 removed.push_back(false); 5950 original_constraint_index.push_back(c); 5951 num_setppc_constraints++; 5952 } 5953 VLOG(1) << "#setppc constraints: " << num_setppc_constraints; 5954 5955 // Set of constraint pairs which are already compared. 5956 absl::flat_hash_set<std::pair<int, int>> compared_constraints; 5957 for (const std::vector<int>& literal_to_constraints : 5958 literals_to_constraints) { 5959 for (int index1 = 0; index1 < literal_to_constraints.size(); ++index1) { 5960 if (context_->time_limit()->LimitReached()) return true; 5961 5962 const int c1 = literal_to_constraints[index1]; 5963 if (removed[c1]) continue; 5964 const std::vector<int>& c1_literals = constraint_literals[c1]; 5965 5966 for (int index2 = index1 + 1; index2 < literal_to_constraints.size(); 5967 ++index2) { 5968 const int c2 = literal_to_constraints[index2]; 5969 if (removed[c2]) continue; 5970 if (removed[c1]) break; 5971 5972 // TODO(user): This should not happen. Investigate. 5973 if (c1 == c2) continue; 5974 5975 CHECK_LT(c1, c2); 5976 if (compared_constraints.contains(std::pair<int, int>(c1, c2))) { 5977 continue; 5978 } 5979 compared_constraints.insert({c1, c2}); 5980 5981 // Hard limit on number of comparisons to avoid spending too much time 5982 // here. 5983 if (compared_constraints.size() >= 50000) return true; 5984 5985 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0; 5986 const bool larger = (signatures[c2] & ~signatures[c1]) == 0; 5987 if (!(smaller || larger)) continue; 5988 5989 // Check if literals in c1 is subset of literals in c2 or vice versa. 5990 const std::vector<int>& c2_literals = constraint_literals[c2]; 5991 5992 // TODO(user): Try avoiding computation of set differences if 5993 // possible. 5994 std::vector<int> c1_minus_c2; 5995 gtl::STLSetDifference(c1_literals, c2_literals, &c1_minus_c2); 5996 std::vector<int> c2_minus_c1; 5997 gtl::STLSetDifference(c2_literals, c1_literals, &c2_minus_c1); 5998 5999 if (c1_minus_c2.empty()) { // c1 included in c2. 6000 if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1, 6001 original_constraint_index, &removed)) { 6002 return false; 6003 } 6004 } else if (c2_minus_c1.empty()) { // c2 included in c1. 6005 if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2, 6006 original_constraint_index, &removed)) { 6007 return false; 6008 } 6009 } 6010 } 6011 } 6012 } 6013 6014 return true; 6015 } 6016 6017 // Note that because we remove the linear constraint, this will not be called 6018 // often, so it is okay to use "heavy" data structure here. 6019 // 6020 // TODO(user): in the at most one case, consider always creating an associated 6021 // literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)? 6022 // This constraint is implicit from what we create, however internally we will 6023 // not recover it easily, so we might not add the linear relaxation 6024 // corresponding to the constraint we just removed. 6025 bool CpModelPresolver::ProcessEncodingFromLinear( 6026 const int linear_encoding_ct_index, 6027 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms, 6028 int64_t* num_multiple_terms) { 6029 // Preprocess exactly or at most one. 6030 bool in_exactly_one = false; 6031 absl::flat_hash_map<int, int> var_to_ref; 6032 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) { 6033 for (const int ref : at_most_or_exactly_one.at_most_one().literals()) { 6034 CHECK(!var_to_ref.contains(PositiveRef(ref))); 6035 var_to_ref[PositiveRef(ref)] = ref; 6036 } 6037 } else { 6038 in_exactly_one = true; 6039 for (const int ref : at_most_or_exactly_one.exactly_one().literals()) { 6040 CHECK(!var_to_ref.contains(PositiveRef(ref))); 6041 var_to_ref[PositiveRef(ref)] = ref; 6042 } 6043 } 6044 6045 // Preprocess the linear constraints. 6046 const ConstraintProto& linear_encoding = 6047 context_->working_model->constraints(linear_encoding_ct_index); 6048 int64_t rhs = linear_encoding.linear().domain(0); 6049 int target_ref = std::numeric_limits<int>::min(); 6050 std::vector<std::pair<int, int64_t>> ref_to_coeffs; 6051 const int num_terms = linear_encoding.linear().vars().size(); 6052 for (int i = 0; i < num_terms; ++i) { 6053 const int ref = linear_encoding.linear().vars(i); 6054 const int64_t coeff = linear_encoding.linear().coeffs(i); 6055 const auto it = var_to_ref.find(PositiveRef(ref)); 6056 6057 if (it == var_to_ref.end()) { 6058 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness "; 6059 CHECK_EQ(std::abs(coeff), 1); 6060 target_ref = coeff == 1 ? ref : NegatedRef(ref); 6061 continue; 6062 } 6063 6064 // We transform the constraint so that the Boolean reference match exactly 6065 // what is in the at most one. 6066 if (it->second == ref) { 6067 // The term in the constraint is the same as in the at_most_one. 6068 ref_to_coeffs.push_back({ref, coeff}); 6069 } else { 6070 // We replace "coeff * ref" by "coeff - coeff * (1 - ref)" 6071 rhs -= coeff; 6072 ref_to_coeffs.push_back({NegatedRef(ref), -coeff}); 6073 } 6074 } 6075 if (target_ref == std::numeric_limits<int>::min() || 6076 context_->CanBeUsedAsLiteral(target_ref)) { 6077 // We didn't find the unique integer variable. This might have happenned 6078 // because by processing other encoding we might end up with a fully boolean 6079 // constraint. Just abort, it will be presolved later. 6080 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now."); 6081 return true; 6082 } 6083 6084 // Extract the encoding. 6085 std::vector<int64_t> all_values; 6086 std::map<int64_t, std::vector<int>> value_to_refs; 6087 for (const auto& [ref, coeff] : ref_to_coeffs) { 6088 const int64_t value = rhs - coeff; 6089 all_values.push_back(value); 6090 value_to_refs[value].push_back(ref); 6091 var_to_ref.erase(PositiveRef(ref)); 6092 } 6093 // The one not used "encodes" the rhs value. 6094 for (const auto& [var, ref] : var_to_ref) { 6095 all_values.push_back(rhs); 6096 value_to_refs[rhs].push_back(ref); 6097 } 6098 if (!in_exactly_one) { 6099 // To cover the corner case when the inclusion is an equality. For an at 6100 // most one, the rhs should be always reachable when all Boolean are false. 6101 all_values.push_back(rhs); 6102 } 6103 6104 // Make sure the target domain is up to date. 6105 const Domain new_domain = Domain::FromValues(all_values); 6106 bool domain_reduced = false; 6107 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) { 6108 return false; 6109 } 6110 if (domain_reduced) { 6111 context_->UpdateRuleStats("encoding: reduced target domain"); 6112 } 6113 6114 if (context_->CanBeUsedAsLiteral(target_ref)) { 6115 // If target is now a literal, lets not process it here. 6116 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now."); 6117 return true; 6118 } 6119 6120 // Encode the encoding. 6121 absl::flat_hash_set<int64_t> value_set; 6122 for (const int64_t v : context_->DomainOf(target_ref).Values()) { 6123 value_set.insert(v); 6124 } 6125 for (const auto& [value, literals] : value_to_refs) { 6126 // If the value is not in the domain, just set all literal to false. 6127 if (!value_set.contains(value)) { 6128 for (const int lit : literals) { 6129 if (!context_->SetLiteralToFalse(lit)) return false; 6130 } 6131 continue; 6132 } 6133 6134 if (literals.size() == 1 && (in_exactly_one || value != rhs)) { 6135 // Optimization if there is just one literal for this value. 6136 // Note that for the "at most one" case, we can't do that for the rhs. 6137 ++*num_unique_terms; 6138 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) { 6139 return false; 6140 } 6141 } else { 6142 ++*num_multiple_terms; 6143 const int associated_lit = 6144 context_->GetOrCreateVarValueEncoding(target_ref, value); 6145 for (const int lit : literals) { 6146 context_->AddImplication(lit, associated_lit); 6147 } 6148 6149 // All false means associated_lit is false too. 6150 // But not for the rhs case if we are not in exactly one. 6151 if (in_exactly_one || value != rhs) { 6152 // TODO(user): Insted of bool_or + implications, we could add an 6153 // exactly one! Experiment with this. In particular it might capture 6154 // more structure for later heuristic to add the exactly one instead. 6155 // This also applies to automata/table/element expansion. 6156 auto* bool_or = 6157 context_->working_model->add_constraints()->mutable_bool_or(); 6158 for (const int lit : literals) bool_or->add_literals(lit); 6159 bool_or->add_literals(NegatedRef(associated_lit)); 6160 } 6161 } 6162 } 6163 6164 // Remove linear constraint now that it is fully encoded. 6165 context_->working_model->mutable_constraints(linear_encoding_ct_index) 6166 ->Clear(); 6167 context_->UpdateNewConstraintsVariableUsage(); 6168 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index); 6169 return true; 6170 } 6171 6172 namespace { 6173 6174 // We want something that is order invariant and is compatible with inclusion. 6175 uint64_t ComputeSignature(const std::vector<int>& integers) { 6176 uint64_t signature = 0; 6177 for (const int i : integers) signature |= (int64_t{1} << (i & 63)); 6178 return signature; 6179 } 6180 6181 } // namespace 6182 6183 void CpModelPresolver::ExtractEncodingFromLinear() { 6184 if (context_->time_limit()->LimitReached()) return; 6185 if (context_->ModelIsUnsat()) return; 6186 6187 WallTimer wall_timer; 6188 wall_timer.Start(); 6189 6190 struct Superset { 6191 int c; // Index of the constraint. 6192 std::vector<int> vars; 6193 bool is_exactly_one; 6194 }; 6195 std::vector<Superset> potential_supersets; 6196 6197 struct Subset { 6198 int c; // Index of the linear constraint. 6199 std::vector<int> vars; 6200 }; 6201 std::vector<Subset> potential_subsets; 6202 6203 // Loop over the constraints and fill the structures above. 6204 const int num_constraints = context_->working_model->constraints().size(); 6205 for (int c = 0; c < num_constraints; ++c) { 6206 const ConstraintProto& ct = context_->working_model->constraints(c); 6207 switch (ct.constraint_case()) { 6208 case ConstraintProto::kAtMostOne: { 6209 std::vector<int> vars; 6210 for (const int ref : ct.at_most_one().literals()) { 6211 vars.push_back(PositiveRef(ref)); 6212 } 6213 potential_supersets.push_back({c, std::move(vars), false}); 6214 break; 6215 } 6216 case ConstraintProto::kExactlyOne: { 6217 std::vector<int> vars; 6218 for (const int ref : ct.exactly_one().literals()) { 6219 vars.push_back(PositiveRef(ref)); 6220 } 6221 potential_supersets.push_back({c, std::move(vars), true}); 6222 break; 6223 } 6224 case ConstraintProto::kLinear: { 6225 // We only consider equality with no enforcement. 6226 if (!ct.enforcement_literal().empty()) continue; 6227 if (ct.linear().domain().size() != 2) continue; 6228 if (ct.linear().domain(0) != ct.linear().domain(1)) continue; 6229 6230 // We also want a single non-Boolean. 6231 // Note that this assume the constraint is canonicalized. 6232 std::vector<int> vars; 6233 bool is_candidate = true; 6234 int num_integers = 0; 6235 const int num_terms = ct.linear().vars().size(); 6236 for (int i = 0; i < num_terms; ++i) { 6237 const int ref = ct.linear().vars(i); 6238 if (context_->CanBeUsedAsLiteral(ref)) { 6239 vars.push_back(PositiveRef(ref)); 6240 } else { 6241 ++num_integers; 6242 if (std::abs(ct.linear().coeffs(i)) != 1) { 6243 is_candidate = false; 6244 break; 6245 } 6246 if (num_integers == 2) { 6247 is_candidate = false; 6248 break; 6249 } 6250 } 6251 } 6252 6253 // We ignore cases with just one Boolean as this should be already dealt 6254 // with elsewhere. 6255 if (is_candidate && num_integers == 1 && vars.size() > 1) { 6256 potential_subsets.push_back({c, std::move(vars)}); 6257 } 6258 break; 6259 } 6260 default: 6261 break; 6262 } 6263 } 6264 6265 if (potential_supersets.empty()) return; 6266 if (potential_subsets.empty()) return; 6267 6268 // Sort by size. We also want to process exactly_ones first in case a linear 6269 // constraint is also included in an at_most_one of the same size. 6270 std::sort(potential_supersets.begin(), potential_supersets.end(), 6271 [](const Superset& a, const Superset& b) { 6272 const int size_a = a.vars.size(); 6273 const int size_b = b.vars.size(); 6274 return std::tie(size_a, a.is_exactly_one) < 6275 std::tie(size_b, b.is_exactly_one); 6276 }); 6277 std::sort(potential_subsets.begin(), potential_subsets.end(), 6278 [](const Subset& a, const Subset& b) { 6279 return a.vars.size() < b.vars.size(); 6280 }); 6281 6282 // Stats. 6283 int64_t num_exactly_one_encodings = 0; 6284 int64_t num_at_most_one_encodings = 0; 6285 int64_t num_literals = 0; 6286 int64_t num_unique_terms = 0; 6287 int64_t num_multiple_terms = 0; 6288 6289 // Structure for efficient inclusion detection. 6290 // 6291 // TODO(user): Factor this and ComputeSignature() out and merge the 3 places 6292 // where we do something similar. 6293 int subset_index = 0; 6294 std::vector<uint64_t> signatures; 6295 std::vector<std::vector<int>> one_watcher; 6296 std::vector<bool> is_in_superset; 6297 for (const Superset& superset : potential_supersets) { 6298 // Include all the potential subset. 6299 const int superset_size = superset.vars.size(); 6300 for (; subset_index < potential_subsets.size(); ++subset_index) { 6301 const std::vector<int>& vars = potential_subsets[subset_index].vars; 6302 if (vars.size() > superset_size) break; 6303 6304 // Choose to watch the one with smallest list. 6305 int best_choice = -1; 6306 for (const int var : vars) { 6307 if (one_watcher.size() <= var) one_watcher.resize(var + 1); 6308 if (best_choice == -1 || 6309 one_watcher[var].size() < one_watcher[best_choice].size()) { 6310 best_choice = var; 6311 } 6312 } 6313 one_watcher[best_choice].push_back(subset_index); 6314 CHECK_EQ(signatures.size(), subset_index); 6315 signatures.push_back(ComputeSignature(vars)); 6316 } 6317 6318 // Find any subset included in current superset. 6319 DCHECK(absl::c_all_of(is_in_superset, [](bool b) { return !b; })); 6320 for (const int var : superset.vars) { 6321 if (var >= is_in_superset.size()) { 6322 is_in_superset.resize(var + 1, false); 6323 } 6324 is_in_superset[var] = true; 6325 } 6326 { 6327 const int max_size = std::max(one_watcher.size(), is_in_superset.size()); 6328 one_watcher.resize(max_size); 6329 is_in_superset.resize(max_size, false); 6330 } 6331 6332 const uint64_t superset_signature = ComputeSignature(superset.vars); 6333 for (const int superset_var : superset.vars) { 6334 for (int i = 0; i < one_watcher[superset_var].size(); ++i) { 6335 const int subset_index = one_watcher[superset_var][i]; 6336 const Subset& subset = potential_subsets[subset_index]; 6337 CHECK_LE(subset.vars.size(), superset_size); 6338 6339 // Quick check with signature. 6340 if ((signatures[subset_index] & ~superset_signature) != 0) continue; 6341 6342 // Long check with bitset. 6343 bool is_included = true; 6344 for (const int subset_var : subset.vars) { 6345 if (!is_in_superset[subset_var]) { 6346 is_included = false; 6347 break; 6348 } 6349 } 6350 if (!is_included) continue; 6351 6352 if (!superset.is_exactly_one) { 6353 ++num_at_most_one_encodings; 6354 } else { 6355 ++num_exactly_one_encodings; 6356 } 6357 num_literals += subset.vars.size(); 6358 context_->UpdateRuleStats("encoding: extracted from linear"); 6359 6360 if (!ProcessEncodingFromLinear( 6361 subset.c, context_->working_model->constraints(superset.c), 6362 &num_unique_terms, &num_multiple_terms)) { 6363 // UNSAT, we return right away, no cleanup needed. 6364 return; 6365 } 6366 6367 // Remove from the watcher list. 6368 std::swap(one_watcher[superset_var][i], 6369 one_watcher[superset_var].back()); 6370 one_watcher[superset_var].pop_back(); 6371 --i; 6372 } 6373 } 6374 6375 // Cleanup. 6376 for (const int var : superset.vars) { 6377 is_in_superset[var] = false; 6378 } 6379 } 6380 6381 SOLVER_LOG(logger_, "[ExtractEncodingFromLinear]", 6382 " #potential_supersets=", potential_supersets.size(), 6383 " #potential_subsets=", potential_subsets.size(), 6384 " #at_most_one_encodings=", num_at_most_one_encodings, 6385 " #exactly_one_encodings=", num_exactly_one_encodings, 6386 " #unique_terms=", num_unique_terms, 6387 " #multiple_terms=", num_multiple_terms, 6388 " #literals=", num_literals, " time=", wall_timer.Get(), "s"); 6389 } 6390 6391 void CpModelPresolver::TryToSimplifyDomain(int var) { 6392 CHECK(RefIsPositive(var)); 6393 CHECK(context_->ConstraintVariableGraphIsUpToDate()); 6394 if (context_->ModelIsUnsat()) return; 6395 if (context_->IsFixed(var)) return; 6396 if (context_->VariableWasRemoved(var)) return; 6397 if (context_->VariableIsNotUsedAnymore(var)) return; 6398 6399 const AffineRelation::Relation r = context_->GetAffineRelation(var); 6400 if (r.representative != var) return; 6401 6402 // TODO(user): We can still remove the variable even if we want to keep 6403 // all feasible solutions for the cases when we have a full encoding. 6404 // 6405 // TODO(user): In fixed search, we disable this rule because we don't update 6406 // the search strategy, but for some strategy we could. 6407 // 6408 // TODO(user): The hint might get lost if the encoding was created during 6409 // the presolve. 6410 if (context_->VariableIsRemovable(var) && 6411 !context_->CanBeUsedAsLiteral(var) && 6412 context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var) && 6413 context_->params().search_branching() != SatParameters::FIXED_SEARCH) { 6414 // We can currently only deal with the case where all encoding constraint 6415 // are of the form literal => var ==/!= value. 6416 // If they are more complex linear1 involved, we just abort. 6417 // 6418 // TODO(user): Also deal with the case all >= or <= where we can add a 6419 // serie of implication between all involved literals. 6420 absl::flat_hash_set<int64_t> values_set; 6421 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals; 6422 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals; 6423 bool abort = false; 6424 for (const int c : context_->VarToConstraints(var)) { 6425 if (c < 0) continue; 6426 const ConstraintProto& ct = context_->working_model->constraints(c); 6427 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear); 6428 CHECK_EQ(ct.linear().vars().size(), 1); 6429 int64_t coeff = ct.linear().coeffs(0); 6430 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) { 6431 abort = true; 6432 break; 6433 } 6434 if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1; 6435 const Domain rhs = 6436 ReadDomainFromProto(ct.linear()).InverseMultiplicationBy(coeff); 6437 6438 if (rhs.IsFixed()) { 6439 if (!context_->DomainOf(var).Contains(rhs.FixedValue())) { 6440 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) { 6441 return; 6442 } 6443 } else { 6444 values_set.insert(rhs.FixedValue()); 6445 value_to_equal_literals[rhs.FixedValue()].push_back( 6446 ct.enforcement_literal(0)); 6447 } 6448 } else { 6449 const Domain complement = 6450 context_->DomainOf(var).IntersectionWith(rhs.Complement()); 6451 if (complement.IsEmpty()) { 6452 // TODO(user): This should be dealt with elsewhere. 6453 abort = true; 6454 break; 6455 } 6456 if (complement.IsFixed()) { 6457 if (context_->DomainOf(var).Contains(complement.FixedValue())) { 6458 values_set.insert(complement.FixedValue()); 6459 value_to_not_equal_literals[complement.FixedValue()].push_back( 6460 ct.enforcement_literal(0)); 6461 } 6462 } else { 6463 abort = true; 6464 break; 6465 } 6466 } 6467 } 6468 if (abort) { 6469 context_->UpdateRuleStats("TODO variables: only used in linear1."); 6470 } else if (value_to_not_equal_literals.empty() && 6471 value_to_equal_literals.empty()) { 6472 // This is just a variable not used anywhere, it should be removed by 6473 // another part of the presolve. 6474 } else { 6475 // For determinism, sort all the encoded values first. 6476 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end()); 6477 std::sort(encoded_values.begin(), encoded_values.end()); 6478 CHECK(!encoded_values.empty()); 6479 const bool is_fully_encoded = 6480 encoded_values.size() == context_->DomainOf(var).Size(); 6481 6482 // Link all Boolean in out linear1 to the encoding literals. Note that we 6483 // should hopefully already have detected such literal before and this 6484 // should add trivial implications. 6485 for (const int64_t v : encoded_values) { 6486 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v); 6487 const auto eq_it = value_to_equal_literals.find(v); 6488 if (eq_it != value_to_equal_literals.end()) { 6489 for (const int lit : eq_it->second) { 6490 context_->AddImplication(lit, encoding_lit); 6491 } 6492 } 6493 const auto neq_it = value_to_not_equal_literals.find(v); 6494 if (neq_it != value_to_not_equal_literals.end()) { 6495 for (const int lit : neq_it->second) { 6496 context_->AddImplication(lit, NegatedRef(encoding_lit)); 6497 } 6498 } 6499 } 6500 context_->UpdateNewConstraintsVariableUsage(); 6501 6502 // This is the set of other values. 6503 Domain other_values; 6504 if (!is_fully_encoded) { 6505 other_values = context_->DomainOf(var).IntersectionWith( 6506 Domain::FromValues(encoded_values).Complement()); 6507 } 6508 6509 // Update the objective if needed. Note that this operation can fail if 6510 // the new expression result in potential overflow. 6511 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) { 6512 int64_t min_value; 6513 const int64_t obj_coeff = context_->ObjectiveMap().at(var); 6514 if (is_fully_encoded) { 6515 // We substract the min_value from all coefficients. 6516 // This should reduce the objective size and helps with the bounds. 6517 min_value = 6518 obj_coeff > 0 ? encoded_values.front() : encoded_values.back(); 6519 } else { 6520 // Tricky: If the variable is not fully encoded, then when all 6521 // partial encoding literal are false, it must take the "best" value 6522 // in other_values. That depend on the sign of the objective coeff. 6523 // 6524 // We also restrict other value so that the postsolve code below 6525 // will fix the variable to the correct value when this happen. 6526 other_values = 6527 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max()); 6528 min_value = other_values.FixedValue(); 6529 } 6530 6531 // Checks for overflow before trying to substitute the variable in the 6532 // objective. 6533 int64_t accumulated = std::abs(min_value); 6534 for (const int64_t value : encoded_values) { 6535 accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value))); 6536 if (accumulated == std::numeric_limits<int64_t>::max()) { 6537 context_->UpdateRuleStats( 6538 "TODO variables: only used in objective and in encoding"); 6539 return; 6540 } 6541 } 6542 6543 ConstraintProto encoding_ct; 6544 LinearConstraintProto* linear = encoding_ct.mutable_linear(); 6545 const int64_t coeff_in_equality = -1; 6546 linear->add_vars(var); 6547 linear->add_coeffs(coeff_in_equality); 6548 6549 linear->add_domain(-min_value); 6550 linear->add_domain(-min_value); 6551 for (const int64_t value : encoded_values) { 6552 if (value == min_value) continue; 6553 const int enf = context_->GetOrCreateVarValueEncoding(var, value); 6554 const int64_t coeff = value - min_value; 6555 if (RefIsPositive(enf)) { 6556 linear->add_vars(enf); 6557 linear->add_coeffs(coeff); 6558 } else { 6559 // (1 - var) * coeff; 6560 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff); 6561 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff); 6562 linear->add_vars(PositiveRef(enf)); 6563 linear->add_coeffs(-coeff); 6564 } 6565 } 6566 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality, 6567 encoding_ct)) { 6568 context_->UpdateRuleStats( 6569 "TODO variables: only used in objective and in encoding"); 6570 return; 6571 } 6572 context_->UpdateRuleStats( 6573 "variables: only used in objective and in encoding"); 6574 } else { 6575 context_->UpdateRuleStats("variables: only used in encoding"); 6576 } 6577 6578 // Clear all involved constraint. 6579 auto copy = context_->VarToConstraints(var); 6580 for (const int c : copy) { 6581 if (c < 0) continue; 6582 context_->working_model->mutable_constraints(c)->Clear(); 6583 context_->UpdateConstraintVariableUsage(c); 6584 } 6585 6586 // Add enough constraints to the mapping model to recover a valid value 6587 // for var when all the booleans are fixed. 6588 for (const int64_t value : encoded_values) { 6589 const int enf = context_->GetOrCreateVarValueEncoding(var, value); 6590 ConstraintProto* ct = context_->mapping_model->add_constraints(); 6591 ct->add_enforcement_literal(enf); 6592 ct->mutable_linear()->add_vars(var); 6593 ct->mutable_linear()->add_coeffs(1); 6594 ct->mutable_linear()->add_domain(value); 6595 ct->mutable_linear()->add_domain(value); 6596 } 6597 6598 // This must be done after we removed all the constraint containing var. 6599 ConstraintProto* new_ct = context_->working_model->add_constraints(); 6600 if (is_fully_encoded) { 6601 // The encoding is full: add an exactly one. 6602 for (const int64_t value : encoded_values) { 6603 new_ct->mutable_exactly_one()->add_literals( 6604 context_->GetOrCreateVarValueEncoding(var, value)); 6605 } 6606 PresolveExactlyOne(new_ct); 6607 } else { 6608 // If all literal are false, then var must take one of the other values. 6609 ConstraintProto* mapping_ct = 6610 context_->mapping_model->add_constraints(); 6611 mapping_ct->mutable_linear()->add_vars(var); 6612 mapping_ct->mutable_linear()->add_coeffs(1); 6613 FillDomainInProto(other_values, mapping_ct->mutable_linear()); 6614 6615 for (const int64_t value : encoded_values) { 6616 const int literal = context_->GetOrCreateVarValueEncoding(var, value); 6617 mapping_ct->add_enforcement_literal(NegatedRef(literal)); 6618 new_ct->mutable_at_most_one()->add_literals(literal); 6619 } 6620 PresolveAtMostOne(new_ct); 6621 } 6622 6623 context_->UpdateNewConstraintsVariableUsage(); 6624 context_->MarkVariableAsRemoved(var); 6625 return; 6626 } 6627 } 6628 6629 // Special case: if a literal l appear in exactly two constraints: 6630 // - l => var in domain1 6631 // - not(l) => var in domain2 6632 // then we know that domain(var) is included in domain1 U domain2, 6633 // and that the literal l can be removed (and determined at postsolve). 6634 // 6635 // TODO(user): This could be generalized further to linear of size > 1 if for 6636 // example the terms are the same. 6637 // 6638 // We wait for the model expansion to take place in order to avoid removing 6639 // encoding that will later be re-created during expansion. 6640 if (context_->ModelIsExpanded() && context_->CanBeUsedAsLiteral(var) && 6641 context_->VariableIsRemovable(var) && 6642 context_->VarToConstraints(var).size() == 2) { 6643 bool abort = false; 6644 int ct_var = -1; 6645 Domain union_of_domain; 6646 int num_positive = 0; 6647 std::vector<int> constraint_indices_to_remove; 6648 for (const int c : context_->VarToConstraints(var)) { 6649 if (c < 0) { 6650 abort = true; 6651 continue; 6652 } 6653 constraint_indices_to_remove.push_back(c); 6654 const ConstraintProto& ct = context_->working_model->constraints(c); 6655 if (ct.enforcement_literal().size() != 1 || 6656 PositiveRef(ct.enforcement_literal(0)) != var || 6657 ct.constraint_case() != ConstraintProto::kLinear || 6658 ct.linear().vars().size() != 1) { 6659 abort = true; 6660 break; 6661 } 6662 if (ct.enforcement_literal(0) == var) ++num_positive; 6663 if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) { 6664 abort = true; 6665 break; 6666 } 6667 ct_var = PositiveRef(ct.linear().vars(0)); 6668 union_of_domain = union_of_domain.UnionWith( 6669 ReadDomainFromProto(ct.linear()) 6670 .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0)) 6671 ? ct.linear().coeffs(0) 6672 : -ct.linear().coeffs(0))); 6673 } 6674 if (!abort && num_positive == 1) { 6675 if (!context_->IntersectDomainWith(ct_var, union_of_domain)) { 6676 return; 6677 } 6678 context_->UpdateRuleStats("variables: removable enforcement literal"); 6679 for (const int c : constraint_indices_to_remove) { 6680 *context_->mapping_model->add_constraints() = 6681 context_->working_model->constraints(c); 6682 context_->mapping_model 6683 ->mutable_constraints( 6684 context_->mapping_model->constraints().size() - 1) 6685 ->set_name("here"); 6686 context_->working_model->mutable_constraints(c)->Clear(); 6687 context_->UpdateConstraintVariableUsage(c); 6688 } 6689 context_->MarkVariableAsRemoved(var); 6690 return; 6691 } 6692 } 6693 6694 // Only process discrete domain. 6695 const Domain& domain = context_->DomainOf(var); 6696 6697 // Special case for non-Boolean domain of size 2. 6698 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) { 6699 context_->CanonicalizeDomainOfSizeTwo(var); 6700 return; 6701 } 6702 6703 if (domain.NumIntervals() != domain.Size()) return; 6704 6705 const int64_t var_min = domain.Min(); 6706 int64_t gcd = domain[1].start - var_min; 6707 for (int index = 2; index < domain.NumIntervals(); ++index) { 6708 const ClosedInterval& i = domain[index]; 6709 DCHECK_EQ(i.start, i.end); 6710 const int64_t shifted_value = i.start - var_min; 6711 DCHECK_GT(shifted_value, 0); 6712 6713 gcd = MathUtil::GCD64(gcd, shifted_value); 6714 if (gcd == 1) break; 6715 } 6716 if (gcd == 1) return; 6717 6718 // This does all the work since var * 1 % gcd = var_min % gcd. 6719 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min); 6720 } 6721 6722 // Adds all affine relations to our model for the variables that are still used. 6723 void CpModelPresolver::EncodeAllAffineRelations() { 6724 int64_t num_added = 0; 6725 for (int var = 0; var < context_->working_model->variables_size(); ++var) { 6726 if (context_->IsFixed(var)) continue; 6727 6728 const AffineRelation::Relation r = context_->GetAffineRelation(var); 6729 if (r.representative == var) continue; 6730 6731 if (!context_->keep_all_feasible_solutions) { 6732 // TODO(user): It seems some affine relation are still removable at this 6733 // stage even though they should be removed inside PresolveToFixPoint(). 6734 // Investigate. For now, we just remove such relations. 6735 if (context_->VariableIsNotUsedAnymore(var)) continue; 6736 if (!PresolveAffineRelationIfAny(var)) break; 6737 if (context_->VariableIsNotUsedAnymore(var)) continue; 6738 if (context_->IsFixed(var)) continue; 6739 } 6740 6741 ++num_added; 6742 ConstraintProto* ct = context_->working_model->add_constraints(); 6743 auto* arg = ct->mutable_linear(); 6744 arg->add_vars(var); 6745 arg->add_coeffs(1); 6746 arg->add_vars(r.representative); 6747 arg->add_coeffs(-r.coeff); 6748 arg->add_domain(r.offset); 6749 arg->add_domain(r.offset); 6750 context_->UpdateNewConstraintsVariableUsage(); 6751 } 6752 6753 // Now that we encoded all remaining affine relation with constraints, we 6754 // remove the special marker to have a proper constraint variable graph. 6755 context_->RemoveAllVariablesFromAffineRelationConstraint(); 6756 6757 if (num_added > 0) { 6758 SOLVER_LOG(logger_, num_added, " affine relations still in the model."); 6759 } 6760 } 6761 6762 // Presolve a variable in relation with its representative. 6763 bool CpModelPresolver::PresolveAffineRelationIfAny(int var) { 6764 if (context_->VariableIsNotUsedAnymore(var)) return true; 6765 6766 const AffineRelation::Relation r = context_->GetAffineRelation(var); 6767 if (r.representative == var) return true; 6768 6769 // Propagate domains. 6770 if (!context_->PropagateAffineRelation(var)) return false; 6771 6772 // Once an affine relation is detected, the variables should be added to 6773 // the kAffineRelationConstraint. The only way to be unmarked is if the 6774 // variable do not appear in any other constraint and is not a representative, 6775 // in which case it should never be added back. 6776 if (context_->IsFixed(var)) return true; 6777 CHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint)); 6778 CHECK(!context_->VariableIsNotUsedAnymore(r.representative)); 6779 6780 // If var is no longer used, remove. Note that we can always do that since we 6781 // propagated the domain above and so we can find a feasible value for a for 6782 // any value of the representative. 6783 if (context_->VariableIsUniqueAndRemovable(var)) { 6784 // Add relation with current representative to the mapping model. 6785 ConstraintProto* ct = context_->mapping_model->add_constraints(); 6786 auto* arg = ct->mutable_linear(); 6787 arg->add_vars(var); 6788 arg->add_coeffs(1); 6789 arg->add_vars(r.representative); 6790 arg->add_coeffs(-r.coeff); 6791 arg->add_domain(r.offset); 6792 arg->add_domain(r.offset); 6793 context_->RemoveVariableFromAffineRelation(var); 6794 } 6795 return true; 6796 } 6797 6798 void CpModelPresolver::PresolveToFixPoint() { 6799 if (context_->ModelIsUnsat()) return; 6800 6801 // Limit on number of operations. 6802 const int64_t max_num_operations = 6803 context_->params().debug_max_num_presolve_operations() > 0 6804 ? context_->params().debug_max_num_presolve_operations() 6805 : std::numeric_limits<int64_t>::max(); 6806 6807 // This is used for constraint having unique variables in them (i.e. not 6808 // appearing anywhere else) to not call the presolve more than once for this 6809 // reason. 6810 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called; 6811 6812 TimeLimit* time_limit = context_->time_limit(); 6813 6814 // The queue of "active" constraints, initialized to the non-empty ones. 6815 std::vector<bool> in_queue(context_->working_model->constraints_size(), 6816 false); 6817 std::deque<int> queue; 6818 for (int c = 0; c < in_queue.size(); ++c) { 6819 if (context_->working_model->constraints(c).constraint_case() != 6820 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) { 6821 in_queue[c] = true; 6822 queue.push_back(c); 6823 } 6824 } 6825 6826 // When thinking about how the presolve works, it seems like a good idea to 6827 // process the "simple" constraints first in order to be more efficient. 6828 // In September 2019, experiment on the flatzinc problems shows no changes in 6829 // the results. We should actually count the number of rules triggered. 6830 if (context_->params().permute_presolve_constraint_order()) { 6831 std::shuffle(queue.begin(), queue.end(), *context_->random()); 6832 } else { 6833 std::sort(queue.begin(), queue.end(), [this](int a, int b) { 6834 const int score_a = context_->ConstraintToVars(a).size(); 6835 const int score_b = context_->ConstraintToVars(b).size(); 6836 return score_a < score_b || (score_a == score_b && a < b); 6837 }); 6838 } 6839 6840 while (!queue.empty() && !context_->ModelIsUnsat()) { 6841 if (time_limit->LimitReached()) break; 6842 if (context_->num_presolve_operations > max_num_operations) break; 6843 while (!queue.empty() && !context_->ModelIsUnsat()) { 6844 if (time_limit->LimitReached()) break; 6845 if (context_->num_presolve_operations > max_num_operations) break; 6846 const int c = queue.front(); 6847 in_queue[c] = false; 6848 queue.pop_front(); 6849 6850 const int old_num_constraint = 6851 context_->working_model->constraints_size(); 6852 const bool changed = PresolveOneConstraint(c); 6853 if (context_->ModelIsUnsat()) { 6854 SOLVER_LOG(logger_, "Unsat after presolving constraint #", c, 6855 " (warning, dump might be inconsistent): ", 6856 context_->working_model->constraints(c).ShortDebugString()); 6857 } 6858 6859 // Add to the queue any newly created constraints. 6860 const int new_num_constraints = 6861 context_->working_model->constraints_size(); 6862 if (new_num_constraints > old_num_constraint) { 6863 context_->UpdateNewConstraintsVariableUsage(); 6864 in_queue.resize(new_num_constraints, true); 6865 for (int c = old_num_constraint; c < new_num_constraints; ++c) { 6866 queue.push_back(c); 6867 } 6868 } 6869 6870 // TODO(user): Is seems safer to simply remove the changed Boolean. 6871 // We loose a bit of performance, but the code is simpler. 6872 if (changed) { 6873 context_->UpdateConstraintVariableUsage(c); 6874 } 6875 } 6876 6877 // This is needed to remove variable with a different representative from 6878 // the objective. This allows to remove them completely in the loop below. 6879 if (context_->ModelIsUnsat()) return; 6880 if (!context_->CanonicalizeObjective()) return; 6881 6882 // We also make sure all affine relations are propagated and any not 6883 // yet canonicalized domain is. 6884 // 6885 // TODO(user): maybe we can avoid iterating over all variables, but then 6886 // we already do that below. 6887 const int current_num_variables = context_->working_model->variables_size(); 6888 for (int v = 0; v < current_num_variables; ++v) { 6889 if (context_->ModelIsUnsat()) return; 6890 if (!PresolveAffineRelationIfAny(v)) return; 6891 6892 // Try to canonicalize the domain, note that we should have detected all 6893 // affine relations before, so we don't recreate "canononical" variables 6894 // if they already exist in the model. 6895 TryToSimplifyDomain(v); 6896 context_->UpdateNewConstraintsVariableUsage(); 6897 } 6898 6899 // Re-add to the queue constraints that have unique variables. Note that to 6900 // not enter an infinite loop, we call each (var, constraint) pair at most 6901 // once. 6902 const int num_vars = context_->working_model->variables_size(); 6903 in_queue.resize(context_->working_model->constraints_size(), false); 6904 for (int v = 0; v < num_vars; ++v) { 6905 const auto& constraints = context_->VarToConstraints(v); 6906 if (constraints.size() != 1) continue; 6907 const int c = *constraints.begin(); 6908 if (c < 0) continue; 6909 6910 // Note that to avoid bad complexity in problem like a TSP with just one 6911 // big constraint. we mark all the singleton variables of a constraint 6912 // even if this constraint is already in the queue. 6913 if (var_constraint_pair_already_called.contains( 6914 std::pair<int, int>(v, c))) { 6915 continue; 6916 } 6917 var_constraint_pair_already_called.insert({v, c}); 6918 6919 if (!in_queue[c]) { 6920 in_queue[c] = true; 6921 queue.push_back(c); 6922 } 6923 } 6924 6925 for (int i = 0; i < 2; ++i) { 6926 // Re-add to the queue the constraints that touch a variable that changed. 6927 // 6928 // TODO(user): Avoid reprocessing the constraints that changed the 6929 // variables with the use of time_exprstamp. 6930 if (context_->ModelIsUnsat()) return; 6931 in_queue.resize(context_->working_model->constraints_size(), false); 6932 for (const int v : context_->modified_domains.PositionsSetAtLeastOnce()) { 6933 if (context_->VariableIsNotUsedAnymore(v)) continue; 6934 if (context_->IsFixed(v)) context_->ExploitFixedDomain(v); 6935 for (const int c : context_->VarToConstraints(v)) { 6936 if (c >= 0 && !in_queue[c]) { 6937 in_queue[c] = true; 6938 queue.push_back(c); 6939 } 6940 } 6941 } 6942 6943 // If we reach the end of the loop, try to detect dominance relations. 6944 if (!queue.empty() || i == 1) break; 6945 6946 // Detect & exploit dominance between variables, or variables that can 6947 // move freely in one direction. Or variables that are just blocked by one 6948 // constraint in one direction. 6949 // 6950 // TODO(user): We can support assumptions but we need to not cut them out 6951 // of the feasible region. 6952 if (!context_->keep_all_feasible_solutions && 6953 context_->working_model->assumptions().empty()) { 6954 VarDomination var_dom; 6955 DualBoundStrengthening dual_bound_strengthening; 6956 DetectDominanceRelations(*context_, &var_dom, 6957 &dual_bound_strengthening); 6958 if (!dual_bound_strengthening.Strengthen(context_)) return; 6959 6960 // TODO(user): The Strengthen() function above might make some 6961 // inequality tight. Currently, because we only do that for implication, 6962 // this will not change who dominate who, but in general we should 6963 // process the new constraint direction before calling this. 6964 if (!ExploitDominanceRelations(var_dom, context_)) return; 6965 } 6966 } 6967 6968 // Make sure the order is deterministic! because var_to_constraints[] 6969 // order changes from one run to the next. 6970 std::sort(queue.begin(), queue.end()); 6971 context_->modified_domains.SparseClearAll(); 6972 } 6973 6974 if (context_->ModelIsUnsat()) return; 6975 6976 // Second "pass" for transformation better done after all of the above and 6977 // that do not need a fix-point loop. 6978 // 6979 // TODO(user): Also add deductions achieved during probing! 6980 // 6981 // TODO(user): ideally we should "wake-up" any constraint that contains an 6982 // absent interval in the main propagation loop above. But we currently don't 6983 // maintain such list. 6984 const int num_constraints = context_->working_model->constraints_size(); 6985 for (int c = 0; c < num_constraints; ++c) { 6986 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 6987 switch (ct->constraint_case()) { 6988 case ConstraintProto::ConstraintCase::kNoOverlap: 6989 // Filter out absent intervals. 6990 if (PresolveNoOverlap(ct)) { 6991 context_->UpdateConstraintVariableUsage(c); 6992 } 6993 break; 6994 case ConstraintProto::ConstraintCase::kNoOverlap2D: 6995 // Filter out absent intervals. 6996 if (PresolveNoOverlap2D(c, ct)) { 6997 context_->UpdateConstraintVariableUsage(c); 6998 } 6999 break; 7000 case ConstraintProto::ConstraintCase::kCumulative: 7001 // Filter out absent intervals. 7002 if (PresolveCumulative(ct)) { 7003 context_->UpdateConstraintVariableUsage(c); 7004 } 7005 break; 7006 case ConstraintProto::ConstraintCase::kBoolOr: { 7007 // Try to infer domain reductions from clauses and the saved "implies in 7008 // domain" relations. 7009 for (const auto& pair : 7010 context_->deductions.ProcessClause(ct->bool_or().literals())) { 7011 bool modified = false; 7012 if (!context_->IntersectDomainWith(pair.first, pair.second, 7013 &modified)) { 7014 return; 7015 } 7016 if (modified) { 7017 context_->UpdateRuleStats("deductions: reduced variable domain"); 7018 } 7019 } 7020 break; 7021 } 7022 default: 7023 break; 7024 } 7025 } 7026 7027 context_->deductions.MarkProcessingAsDoneForNow(); 7028 } 7029 7030 ModelCopy::ModelCopy(PresolveContext* context) : context_(context) {} 7031 7032 // TODO(user): Merge with the phase 1 of the presolve code. 7033 bool ModelCopy::ImportAndSimplifyConstraints( 7034 const CpModelProto& in_model, const std::vector<int>& ignored_constraints) { 7035 const absl::flat_hash_set<int> ignored_constraints_set( 7036 ignored_constraints.begin(), ignored_constraints.end()); 7037 context_->InitializeNewDomains(); 7038 7039 starting_constraint_index_ = context_->working_model->constraints_size(); 7040 for (int c = 0; c < in_model.constraints_size(); ++c) { 7041 if (ignored_constraints_set.contains(c)) continue; 7042 7043 const ConstraintProto& ct = in_model.constraints(c); 7044 if (OneEnforcementLiteralIsFalse(ct) && 7045 ct.constraint_case() != ConstraintProto::kInterval) { 7046 continue; 7047 } 7048 switch (ct.constraint_case()) { 7049 case ConstraintProto::CONSTRAINT_NOT_SET: { 7050 break; 7051 } 7052 case ConstraintProto::kBoolOr: { 7053 if (!CopyBoolOr(ct)) return CreateUnsatModel(); 7054 break; 7055 } 7056 case ConstraintProto::kBoolAnd: { 7057 if (!CopyBoolAnd(ct)) return CreateUnsatModel(); 7058 break; 7059 } 7060 case ConstraintProto::kLinear: { 7061 if (!CopyLinear(ct)) return CreateUnsatModel(); 7062 break; 7063 } 7064 case ConstraintProto::kAtMostOne: { 7065 if (!CopyAtMostOne(ct)) return CreateUnsatModel(); 7066 break; 7067 } 7068 case ConstraintProto::kExactlyOne: { 7069 if (!CopyExactlyOne(ct)) return CreateUnsatModel(); 7070 break; 7071 } 7072 case ConstraintProto::kInterval: { 7073 if (!CopyInterval(ct, c)) return CreateUnsatModel(); 7074 break; 7075 } 7076 default: { 7077 *context_->working_model->add_constraints() = ct; 7078 } 7079 } 7080 } 7081 7082 // Re-map interval indices for new constraints. 7083 // TODO(user): Support removing unperformed intervals. 7084 for (int c = starting_constraint_index_; 7085 c < context_->working_model->constraints_size(); ++c) { 7086 ConstraintProto& ct_ref = *context_->working_model->mutable_constraints(c); 7087 ApplyToAllIntervalIndices( 7088 [this](int* ref) { 7089 const auto& it = interval_mapping_.find(*ref); 7090 if (it != interval_mapping_.end()) { 7091 *ref = it->second; 7092 } 7093 }, 7094 &ct_ref); 7095 } 7096 7097 return true; 7098 } 7099 7100 void ModelCopy::CopyEnforcementLiterals(const ConstraintProto& orig, 7101 ConstraintProto* dest) { 7102 temp_enforcement_literals_.clear(); 7103 for (const int lit : orig.enforcement_literal()) { 7104 if (context_->LiteralIsTrue(lit)) { 7105 skipped_non_zero_++; 7106 continue; 7107 } 7108 temp_enforcement_literals_.push_back(lit); 7109 } 7110 dest->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(), 7111 temp_enforcement_literals_.end()); 7112 } 7113 7114 bool ModelCopy::OneEnforcementLiteralIsFalse(const ConstraintProto& ct) const { 7115 for (const int lit : ct.enforcement_literal()) { 7116 if (context_->LiteralIsFalse(lit)) { 7117 return true; 7118 } 7119 } 7120 return false; 7121 } 7122 7123 bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) { 7124 temp_literals_.clear(); 7125 for (const int lit : ct.enforcement_literal()) { 7126 if (context_->LiteralIsTrue(lit)) continue; 7127 temp_literals_.push_back(NegatedRef(lit)); 7128 } 7129 for (const int lit : ct.bool_or().literals()) { 7130 if (context_->LiteralIsTrue(lit)) { 7131 return true; 7132 } 7133 if (context_->LiteralIsFalse(lit)) { 7134 skipped_non_zero_++; 7135 } else { 7136 temp_literals_.push_back(lit); 7137 } 7138 } 7139 7140 context_->working_model->add_constraints() 7141 ->mutable_bool_or() 7142 ->mutable_literals() 7143 ->Add(temp_literals_.begin(), temp_literals_.end()); 7144 return !temp_literals_.empty(); 7145 } 7146 7147 bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) { 7148 bool at_least_one_false = false; 7149 int num_non_fixed_literals = 0; 7150 for (const int lit : ct.bool_and().literals()) { 7151 if (context_->LiteralIsFalse(lit)) { 7152 at_least_one_false = true; 7153 break; 7154 } 7155 if (!context_->LiteralIsTrue(lit)) { 7156 num_non_fixed_literals++; 7157 } 7158 } 7159 7160 if (at_least_one_false) { 7161 ConstraintProto* new_ct = context_->working_model->add_constraints(); 7162 BoolArgumentProto* bool_or = new_ct->mutable_bool_or(); 7163 7164 // One enforcement literal must be false. 7165 for (const int lit : ct.enforcement_literal()) { 7166 if (context_->LiteralIsTrue(lit)) { 7167 skipped_non_zero_++; 7168 continue; 7169 } 7170 bool_or->add_literals(NegatedRef(lit)); 7171 } 7172 return !bool_or->literals().empty(); 7173 } else if (num_non_fixed_literals > 0) { 7174 ConstraintProto* new_ct = context_->working_model->add_constraints(); 7175 CopyEnforcementLiterals(ct, new_ct); 7176 BoolArgumentProto* bool_and = new_ct->mutable_bool_and(); 7177 bool_and->mutable_literals()->Reserve(num_non_fixed_literals); 7178 for (const int lit : ct.bool_and().literals()) { 7179 if (context_->LiteralIsTrue(lit)) { 7180 skipped_non_zero_++; 7181 continue; 7182 } 7183 bool_and->add_literals(lit); 7184 } 7185 } 7186 return true; 7187 } 7188 7189 bool ModelCopy::CopyLinear(const ConstraintProto& ct) { 7190 non_fixed_variables_.clear(); 7191 non_fixed_coefficients_.clear(); 7192 int64_t offset = 0; 7193 for (int i = 0; i < ct.linear().vars_size(); ++i) { 7194 const int ref = ct.linear().vars(i); 7195 const int64_t coeff = ct.linear().coeffs(i); 7196 if (context_->IsFixed(ref)) { 7197 offset += coeff * context_->MinOf(ref); 7198 skipped_non_zero_++; 7199 } else { 7200 non_fixed_variables_.push_back(ref); 7201 non_fixed_coefficients_.push_back(coeff); 7202 } 7203 } 7204 7205 const Domain new_domain = 7206 ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset)); 7207 if (non_fixed_variables_.empty() && !new_domain.Contains(0)) { 7208 if (ct.enforcement_literal().empty()) { 7209 return false; 7210 } 7211 temp_literals_.clear(); 7212 for (const int literal : ct.enforcement_literal()) { 7213 if (context_->LiteralIsTrue(literal)) { 7214 skipped_non_zero_++; 7215 } else { 7216 temp_literals_.push_back(NegatedRef(literal)); 7217 } 7218 } 7219 context_->working_model->add_constraints() 7220 ->mutable_bool_or() 7221 ->mutable_literals() 7222 ->Add(temp_literals_.begin(), temp_literals_.end()); 7223 return !temp_literals_.empty(); 7224 } 7225 7226 // TODO(user): Compute domain and avoid copying constraint that are always 7227 // true. 7228 ConstraintProto* new_ct = context_->working_model->add_constraints(); 7229 CopyEnforcementLiterals(ct, new_ct); 7230 LinearConstraintProto* linear = new_ct->mutable_linear(); 7231 linear->mutable_vars()->Add(non_fixed_variables_.begin(), 7232 non_fixed_variables_.end()); 7233 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(), 7234 non_fixed_coefficients_.end()); 7235 FillDomainInProto(new_domain, linear); 7236 return true; 7237 } 7238 7239 bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) { 7240 int num_true = 0; 7241 temp_literals_.clear(); 7242 for (const int lit : ct.at_most_one().literals()) { 7243 if (context_->LiteralIsFalse(lit)) { 7244 skipped_non_zero_++; 7245 continue; 7246 } 7247 temp_literals_.push_back(lit); 7248 if (context_->LiteralIsTrue(lit)) num_true++; 7249 } 7250 7251 if (temp_literals_.size() <= 1) return true; 7252 if (num_true > 1) return false; 7253 7254 // TODO(user): presolve if num_true == 1. 7255 ConstraintProto* new_ct = context_->working_model->add_constraints(); 7256 CopyEnforcementLiterals(ct, new_ct); 7257 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(), 7258 temp_literals_.end()); 7259 return true; 7260 } 7261 7262 bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) { 7263 int num_true = 0; 7264 temp_literals_.clear(); 7265 for (const int lit : ct.exactly_one().literals()) { 7266 if (context_->LiteralIsFalse(lit)) { 7267 skipped_non_zero_++; 7268 continue; 7269 } 7270 temp_literals_.push_back(lit); 7271 if (context_->LiteralIsTrue(lit)) num_true++; 7272 } 7273 7274 if (temp_literals_.empty() || num_true > 1) return false; 7275 if (temp_literals_.size() == 1 && num_true == 1) return true; 7276 7277 // TODO(user): presolve if num_true == 1 and not everything is false. 7278 ConstraintProto* new_ct = context_->working_model->add_constraints(); 7279 CopyEnforcementLiterals(ct, new_ct); 7280 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(), 7281 temp_literals_.end()); 7282 return true; 7283 } 7284 7285 bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c) { 7286 // TODO(user): remove non performed intervals. 7287 CHECK_EQ(starting_constraint_index_, 0) 7288 << "Adding new interval constraints to partially filled model is not " 7289 "supported."; 7290 interval_mapping_[c] = context_->working_model->constraints_size(); 7291 *context_->working_model->add_constraints() = ct; 7292 return true; 7293 } 7294 7295 bool ModelCopy::CreateUnsatModel() { 7296 context_->working_model->mutable_constraints()->Clear(); 7297 context_->working_model->add_constraints()->mutable_bool_or(); 7298 return false; 7299 } 7300 7301 bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto& in_model, 7302 PresolveContext* context) { 7303 ModelCopy copier(context); 7304 if (copier.ImportAndSimplifyConstraints(in_model, {})) { 7305 CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(in_model, 7306 context); 7307 return true; 7308 } 7309 return context->NotifyThatModelIsUnsat(); 7310 } 7311 7312 void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext( 7313 const CpModelProto& in_model, PresolveContext* context) { 7314 if (!in_model.name().empty()) { 7315 context->working_model->set_name(in_model.name()); 7316 } 7317 if (in_model.has_objective()) { 7318 *context->working_model->mutable_objective() = in_model.objective(); 7319 } 7320 if (in_model.has_floating_point_objective()) { 7321 *context->working_model->mutable_floating_point_objective() = 7322 in_model.floating_point_objective(); 7323 } 7324 if (!in_model.search_strategy().empty()) { 7325 *context->working_model->mutable_search_strategy() = 7326 in_model.search_strategy(); 7327 } 7328 if (!in_model.assumptions().empty()) { 7329 *context->working_model->mutable_assumptions() = in_model.assumptions(); 7330 } 7331 if (in_model.has_symmetry()) { 7332 *context->working_model->mutable_symmetry() = in_model.symmetry(); 7333 } 7334 if (in_model.has_solution_hint()) { 7335 *context->working_model->mutable_solution_hint() = in_model.solution_hint(); 7336 } 7337 } 7338 7339 // ============================================================================= 7340 // Public API. 7341 // ============================================================================= 7342 7343 CpSolverStatus PresolveCpModel(PresolveContext* context, 7344 std::vector<int>* postsolve_mapping) { 7345 CpModelPresolver presolver(context, postsolve_mapping); 7346 return presolver.Presolve(); 7347 } 7348 7349 CpModelPresolver::CpModelPresolver(PresolveContext* context, 7350 std::vector<int>* postsolve_mapping) 7351 : postsolve_mapping_(postsolve_mapping), 7352 context_(context), 7353 logger_(context->logger()) {} 7354 7355 CpSolverStatus CpModelPresolver::InfeasibleStatus() { 7356 if (logger_->LoggingIsEnabled()) context_->LogInfo(); 7357 return CpSolverStatus::INFEASIBLE; 7358 } 7359 7360 // The presolve works as follow: 7361 // 7362 // First stage: 7363 // We will process all active constraints until a fix point is reached. During 7364 // this stage: 7365 // - Variable will never be deleted, but their domain will be reduced. 7366 // - Constraint will never be deleted (they will be marked as empty if needed). 7367 // - New variables and new constraints can be added after the existing ones. 7368 // - Constraints are added only when needed to the mapping_problem if they are 7369 // needed during the postsolve. 7370 // 7371 // Second stage: 7372 // - All the variables domain will be copied to the mapping_model. 7373 // - Everything will be remapped so that only the variables appearing in some 7374 // constraints will be kept and their index will be in [0, num_new_variables). 7375 CpSolverStatus CpModelPresolver::Presolve() { 7376 // TODO(user): move in the context. 7377 context_->keep_all_feasible_solutions = 7378 context_->params().keep_all_feasible_solutions_in_presolve() || 7379 context_->params().enumerate_all_solutions() || 7380 context_->params().fill_tightened_domains_in_response() || 7381 !context_->working_model->assumptions().empty() || 7382 !context_->params().cp_model_presolve(); 7383 7384 // We copy the search strategy to the mapping_model. 7385 for (const auto& decision_strategy : 7386 context_->working_model->search_strategy()) { 7387 *(context_->mapping_model->add_search_strategy()) = decision_strategy; 7388 } 7389 7390 // Initialize the initial context.working_model domains. 7391 context_->InitializeNewDomains(); 7392 7393 // Before initializing the constraint <-> variable graph (which is costly), we 7394 // run a bunch of simple presolve rules. Note that these function should NOT 7395 // use the graph, or the part that uses it should properly check for 7396 // context_->ConstraintVariableGraphIsUpToDate() before doing anything that 7397 // depends on the graph. 7398 if (context_->params().cp_model_presolve()) { 7399 for (int c = 0; c < context_->working_model->constraints_size(); ++c) { 7400 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 7401 PresolveEnforcementLiteral(ct); 7402 switch (ct->constraint_case()) { 7403 case ConstraintProto::ConstraintCase::kBoolOr: 7404 PresolveBoolOr(ct); 7405 break; 7406 case ConstraintProto::ConstraintCase::kBoolAnd: 7407 PresolveBoolAnd(ct); 7408 break; 7409 case ConstraintProto::ConstraintCase::kAtMostOne: 7410 PresolveAtMostOne(ct); 7411 break; 7412 case ConstraintProto::ConstraintCase::kExactlyOne: 7413 PresolveExactlyOne(ct); 7414 break; 7415 case ConstraintProto::ConstraintCase::kLinear: 7416 CanonicalizeLinear(ct); 7417 break; 7418 default: 7419 break; 7420 } 7421 if (context_->ModelIsUnsat()) break; 7422 } 7423 } 7424 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7425 7426 // If the objective is a floating point one, we scale it. 7427 // 7428 // TODO(user): We should probably try to delay this even more. For that we 7429 // just need to isolate more the "dual" reduction that usually need to look at 7430 // the objective. 7431 if (context_->working_model->has_floating_point_objective()) { 7432 if (!context_->ScaleFloatingPointObjective()) { 7433 SOLVER_LOG(logger_, 7434 "The floating point objective cannot be scaled with enough " 7435 "precision"); 7436 return CpSolverStatus::MODEL_INVALID; 7437 } 7438 7439 // At this point, we didn't create any new variables, so the integer 7440 // objective is in term of the orinal problem variables. We save it so that 7441 // we can expose to the user what exact objective we are actually 7442 // optimizing. 7443 *context_->mapping_model->mutable_objective() = 7444 context_->working_model->objective(); 7445 } 7446 7447 // Initialize the objective and the constraint <-> variable graph. 7448 context_->ReadObjectiveFromProto(); 7449 if (!context_->CanonicalizeObjective()) return InfeasibleStatus(); 7450 context_->UpdateNewConstraintsVariableUsage(); 7451 context_->RegisterVariablesUsedInAssumptions(); 7452 DCHECK(context_->ConstraintVariableUsageIsConsistent()); 7453 7454 // If presolve is false, just run expansion. 7455 if (!context_->params().cp_model_presolve()) { 7456 ExpandCpModel(context_); 7457 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7458 7459 // We need to append all the variable equivalence that are still used! 7460 EncodeAllAffineRelations(); 7461 if (logger_->LoggingIsEnabled()) context_->LogInfo(); 7462 return CpSolverStatus::UNKNOWN; 7463 } 7464 7465 // Main propagation loop. 7466 for (int iter = 0; iter < context_->params().max_presolve_iterations(); 7467 ++iter) { 7468 context_->UpdateRuleStats("presolve: iteration"); 7469 // Save some quantities to decide if we abort early the iteration loop. 7470 const int64_t old_num_presolve_op = context_->num_presolve_operations; 7471 int old_num_non_empty_constraints = 0; 7472 for (int c = 0; c < context_->working_model->constraints_size(); ++c) { 7473 const auto type = 7474 context_->working_model->constraints(c).constraint_case(); 7475 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue; 7476 old_num_non_empty_constraints++; 7477 } 7478 7479 // TODO(user): The presolve transformations we do after this is called might 7480 // result in even more presolve if we were to call this again! improve the 7481 // code. See for instance plusexample_6_sat.fzn were represolving the 7482 // presolved problem reduces it even more. 7483 PresolveToFixPoint(); 7484 7485 // Call expansion. 7486 if (!context_->ModelIsExpanded()) { 7487 ExtractEncodingFromLinear(); 7488 ExpandCpModel(context_); 7489 } 7490 DCHECK(context_->ConstraintVariableUsageIsConsistent()); 7491 7492 // TODO(user): do that and the pure-SAT part below more than once. 7493 if (context_->params().cp_model_probing_level() > 0) { 7494 if (!context_->time_limit()->LimitReached()) { 7495 Probe(); 7496 PresolveToFixPoint(); 7497 } 7498 } 7499 7500 // Runs SAT specific presolve on the pure-SAT part of the problem. 7501 // Note that because this can only remove/fix variable not used in the other 7502 // part of the problem, there is no need to redo more presolve afterwards. 7503 if (context_->params().cp_model_use_sat_presolve()) { 7504 if (!context_->time_limit()->LimitReached()) { 7505 PresolvePureSatPart(); 7506 } 7507 } 7508 7509 // Extract redundant at most one constraint form the linear ones. 7510 // 7511 // TODO(user): more generally if we do some probing, the same relation will 7512 // be detected (and more). Also add an option to turn this off? 7513 // 7514 // TODO(user): instead of extracting at most one, extract pairwise conflicts 7515 // and add them to bool_and clauses? this is some sort of small scale 7516 // probing, but good for sat presolve and clique later? 7517 if (!context_->ModelIsUnsat() && iter == 0) { 7518 const int old_size = context_->working_model->constraints_size(); 7519 for (int c = 0; c < old_size; ++c) { 7520 ConstraintProto* ct = context_->working_model->mutable_constraints(c); 7521 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) { 7522 continue; 7523 } 7524 ExtractAtMostOneFromLinear(ct); 7525 } 7526 context_->UpdateNewConstraintsVariableUsage(); 7527 } 7528 7529 if (iter == 0) TransformIntoMaxCliques(); 7530 7531 // TODO(user): Decide where is the best place for this. Fow now we do it 7532 // after max clique to get all the bool_and converted to at most ones. 7533 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() && 7534 !context_->time_limit()->LimitReached() && 7535 !context_->keep_all_feasible_solutions) { 7536 DetectAndExploitSymmetriesInPresolve(context_); 7537 } 7538 7539 // Process set packing, partitioning and covering constraint. 7540 if (!context_->time_limit()->LimitReached()) { 7541 ProcessSetPPC(); 7542 ExtractBoolAnd(); 7543 7544 // Call the main presolve to remove the fixed variables and do more 7545 // deductions. 7546 PresolveToFixPoint(); 7547 } 7548 7549 // Exit the loop if the reduction is not so large. 7550 // Hack: to facilitate experiments, if the requested number of iterations 7551 // is large, we always execute all of them. 7552 if (context_->params().max_presolve_iterations() >= 5) continue; 7553 if (context_->num_presolve_operations - old_num_presolve_op < 7554 0.8 * (context_->working_model->variables_size() + 7555 old_num_non_empty_constraints)) { 7556 break; 7557 } 7558 } 7559 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7560 7561 // Regroup no-overlaps into max-cliques. 7562 MergeNoOverlapConstraints(); 7563 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7564 7565 // Tries to spread the objective amongst many variables. 7566 if (context_->working_model->has_objective()) { 7567 ExpandObjective(); 7568 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7569 } 7570 7571 // Adds all needed affine relation to context_->working_model. 7572 EncodeAllAffineRelations(); 7573 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7574 7575 // Remove duplicate constraints. 7576 // 7577 // TODO(user): We might want to do that earlier so that our count of variable 7578 // usage is not biased by duplicate constraints. 7579 const std::vector<std::pair<int, int>> duplicates = 7580 FindDuplicateConstraints(*context_->working_model); 7581 for (const auto [dup, rep] : duplicates) { 7582 // Note that it is important to look at the type of the representative in 7583 // case the constraint became empty. 7584 const int type = 7585 context_->working_model->constraints(rep).constraint_case(); 7586 7587 // TODO(user): we could delete duplicate identical interval, but we need 7588 // to make sure reference to them are updated. 7589 if (type == ConstraintProto::ConstraintCase::kInterval) { 7590 continue; 7591 } 7592 7593 // For linear constraint, we merge their rhs since it was ignored in the 7594 // FindDuplicateConstraints() call. 7595 if (type == ConstraintProto::kLinear) { 7596 const Domain d1 = ReadDomainFromProto( 7597 context_->working_model->constraints(rep).linear()); 7598 const Domain d2 = ReadDomainFromProto( 7599 context_->working_model->constraints(dup).linear()); 7600 if (d1 != d2) { 7601 context_->UpdateRuleStats("duplicate: merged rhs of linear constraint"); 7602 const Domain rhs = d1.IntersectionWith(d2); 7603 if (rhs.IsEmpty()) { 7604 if (!MarkConstraintAsFalse( 7605 context_->working_model->mutable_constraints(rep))) { 7606 SOLVER_LOG(logger_, "Unsat after merging two linear constraints"); 7607 break; 7608 } 7609 7610 // The representative constraint is no longer a linear constraint, 7611 // so we will not enter this type case again and will just remove 7612 // all subsequent duplicate linear constraints. 7613 context_->UpdateConstraintVariableUsage(rep); 7614 continue; 7615 } 7616 FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep) 7617 ->mutable_linear()); 7618 } 7619 } 7620 7621 context_->working_model->mutable_constraints(dup)->Clear(); 7622 context_->UpdateConstraintVariableUsage(dup); 7623 context_->UpdateRuleStats("duplicate: removed constraint"); 7624 } 7625 7626 if (context_->ModelIsUnsat()) return InfeasibleStatus(); 7627 7628 // The strategy variable indices will be remapped in ApplyVariableMapping() 7629 // but first we use the representative of the affine relations for the 7630 // variables that are not present anymore. 7631 // 7632 // Note that we properly take into account the sign of the coefficient which 7633 // will result in the same domain reduction strategy. Moreover, if the 7634 // variable order is not CHOOSE_FIRST, then we also encode the associated 7635 // affine transformation in order to preserve the order. 7636 absl::flat_hash_set<int> used_variables; 7637 for (DecisionStrategyProto& strategy : 7638 *context_->working_model->mutable_search_strategy()) { 7639 DecisionStrategyProto copy = strategy; 7640 strategy.clear_variables(); 7641 strategy.clear_transformations(); 7642 for (const int ref : copy.variables()) { 7643 const int var = PositiveRef(ref); 7644 7645 // Remove fixed variables. 7646 if (context_->IsFixed(var)) continue; 7647 7648 // There is not point having a variable appear twice, so we only keep 7649 // the first occurrence in the first strategy in which it occurs. 7650 if (used_variables.contains(var)) continue; 7651 used_variables.insert(var); 7652 7653 if (context_->VarToConstraints(var).empty()) { 7654 const AffineRelation::Relation r = context_->GetAffineRelation(var); 7655 if (!context_->VarToConstraints(r.representative).empty()) { 7656 const int rep = (r.coeff > 0) == RefIsPositive(ref) 7657 ? r.representative 7658 : NegatedRef(r.representative); 7659 if (strategy.variable_selection_strategy() != 7660 DecisionStrategyProto::CHOOSE_FIRST) { 7661 DecisionStrategyProto::AffineTransformation* t = 7662 strategy.add_transformations(); 7663 t->set_index(strategy.variables_size()); 7664 t->set_offset(r.offset); 7665 t->set_positive_coeff(std::abs(r.coeff)); 7666 } 7667 strategy.add_variables(rep); 7668 } else { 7669 // TODO(user): this variable was removed entirely by the presolve (no 7670 // equivalent variable present). We simply ignore it entirely which 7671 // might result in a different search... 7672 } 7673 } else { 7674 strategy.add_variables(ref); 7675 } 7676 } 7677 } 7678 7679 // Sync the domains. 7680 for (int i = 0; i < context_->working_model->variables_size(); ++i) { 7681 FillDomainInProto(context_->DomainOf(i), 7682 context_->working_model->mutable_variables(i)); 7683 DCHECK_GT(context_->working_model->variables(i).domain_size(), 0); 7684 } 7685 7686 // Set the variables of the mapping_model. 7687 context_->mapping_model->mutable_variables()->CopyFrom( 7688 context_->working_model->variables()); 7689 7690 // Remove all the unused variables from the presolved model. 7691 postsolve_mapping_->clear(); 7692 std::vector<int> mapping(context_->working_model->variables_size(), -1); 7693 int num_free_variables = 0; 7694 for (int i = 0; i < context_->working_model->variables_size(); ++i) { 7695 if (mapping[i] != -1) continue; // Already mapped. 7696 7697 if (context_->VariableWasRemoved(i)) { 7698 // Heuristic: If a variable is removed and has a representative that is 7699 // not, we "move" the representative to the spot of that variable in the 7700 // original order. This is to preserve any info encoded in the variable 7701 // order by the modeler. 7702 const int r = PositiveRef(context_->GetAffineRelation(i).representative); 7703 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) { 7704 mapping[r] = postsolve_mapping_->size(); 7705 postsolve_mapping_->push_back(r); 7706 } 7707 continue; 7708 } 7709 7710 if (context_->VariableIsNotUsedAnymore(i) && 7711 !context_->keep_all_feasible_solutions) { 7712 // Tricky. Variables that where not removed by a presolve rule should be 7713 // fixed first during postsolve, so that more complex postsolve rules 7714 // can use their values. One way to do that is to fix them here. 7715 // 7716 // We prefer to fix them to zero if possible. 7717 ++num_free_variables; 7718 FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()), 7719 context_->mapping_model->mutable_variables(i)); 7720 continue; 7721 } 7722 7723 mapping[i] = postsolve_mapping_->size(); 7724 postsolve_mapping_->push_back(i); 7725 } 7726 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_free_variables, 7727 " unused variables removed.")); 7728 7729 if (context_->params().permute_variable_randomly()) { 7730 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(), 7731 *context_->random()); 7732 for (int i = 0; i < postsolve_mapping_->size(); ++i) { 7733 mapping[(*postsolve_mapping_)[i]] = i; 7734 } 7735 } 7736 7737 DCHECK(context_->ConstraintVariableUsageIsConsistent()); 7738 ApplyVariableMapping(mapping, *context_); 7739 7740 // Compact all non-empty constraint at the beginning. 7741 RemoveEmptyConstraints(); 7742 7743 // Hack to display the number of deductions stored. 7744 if (context_->deductions.NumDeductions() > 0) { 7745 context_->UpdateRuleStats(absl::StrCat( 7746 "deductions: ", context_->deductions.NumDeductions(), " stored")); 7747 } 7748 7749 // Stats and checks. 7750 if (logger_->LoggingIsEnabled()) context_->LogInfo(); 7751 7752 // This is not supposed to happen, and is more indicative of an error than an 7753 // INVALID model. But for our no-overflow preconditions, we might run into bad 7754 // situation that causes the final model to be invalid. 7755 { 7756 const std::string error = ValidateCpModel(*context_->working_model); 7757 if (!error.empty()) { 7758 SOLVER_LOG(logger_, "Error while validating postsolved model: ", error); 7759 return CpSolverStatus::MODEL_INVALID; 7760 } 7761 } 7762 { 7763 const std::string error = ValidateCpModel(*context_->mapping_model); 7764 if (!error.empty()) { 7765 SOLVER_LOG(logger_, 7766 "Error while validating mapping_model model: ", error); 7767 return CpSolverStatus::MODEL_INVALID; 7768 } 7769 } 7770 7771 return CpSolverStatus::UNKNOWN; 7772 } 7773 7774 void ApplyVariableMapping(const std::vector<int>& mapping, 7775 const PresolveContext& context) { 7776 CpModelProto* proto = context.working_model; 7777 7778 // Remap all the variable/literal references in the constraints and the 7779 // enforcement literals in the variables. 7780 auto mapping_function = [&mapping](int* ref) { 7781 const int image = mapping[PositiveRef(*ref)]; 7782 CHECK_GE(image, 0); 7783 *ref = RefIsPositive(*ref) ? image : NegatedRef(image); 7784 }; 7785 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) { 7786 ApplyToAllVariableIndices(mapping_function, &ct_ref); 7787 ApplyToAllLiteralIndices(mapping_function, &ct_ref); 7788 } 7789 7790 // Remap the objective variables. 7791 if (proto->has_objective()) { 7792 for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) { 7793 mapping_function(&mutable_ref); 7794 } 7795 } 7796 7797 // Remap the assumptions. 7798 for (int& mutable_ref : *proto->mutable_assumptions()) { 7799 mapping_function(&mutable_ref); 7800 } 7801 7802 // Remap the search decision heuristic. 7803 // Note that we delete any heuristic related to a removed variable. 7804 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) { 7805 const DecisionStrategyProto copy = strategy; 7806 strategy.clear_variables(); 7807 std::vector<int> new_indices(copy.variables().size(), -1); 7808 for (int i = 0; i < copy.variables().size(); ++i) { 7809 const int ref = copy.variables(i); 7810 const int image = mapping[PositiveRef(ref)]; 7811 if (image >= 0) { 7812 new_indices[i] = strategy.variables_size(); 7813 strategy.add_variables(RefIsPositive(ref) ? image : NegatedRef(image)); 7814 } 7815 } 7816 strategy.clear_transformations(); 7817 for (const auto& transform : copy.transformations()) { 7818 CHECK_LT(transform.index(), new_indices.size()); 7819 const int new_index = new_indices[transform.index()]; 7820 if (new_index == -1) continue; 7821 auto* new_transform = strategy.add_transformations(); 7822 *new_transform = transform; 7823 CHECK_LT(new_index, strategy.variables().size()); 7824 new_transform->set_index(new_index); 7825 } 7826 } 7827 7828 // Remap the solution hint. Note that after remapping, we may have duplicate 7829 // variable, so we only keep the first occurrence. 7830 if (proto->has_solution_hint()) { 7831 absl::flat_hash_set<int> used_vars; 7832 auto* mutable_hint = proto->mutable_solution_hint(); 7833 int new_size = 0; 7834 for (int i = 0; i < mutable_hint->vars_size(); ++i) { 7835 const int old_ref = mutable_hint->vars(i); 7836 int64_t old_value = mutable_hint->values(i); 7837 7838 // We always move a hint within bounds. 7839 // This also make sure a hint of INT_MIN or INT_MAX does not overflow. 7840 if (old_value < context.MinOf(old_ref)) { 7841 old_value = context.MinOf(old_ref); 7842 } 7843 if (old_value > context.MaxOf(old_ref)) { 7844 old_value = context.MaxOf(old_ref); 7845 } 7846 7847 // Note that if (old_value - r.offset) is not divisible by r.coeff, then 7848 // the hint is clearly infeasible, but we still set it to a "close" value. 7849 const AffineRelation::Relation r = context.GetAffineRelation(old_ref); 7850 const int var = r.representative; 7851 const int64_t value = (old_value - r.offset) / r.coeff; 7852 7853 const int image = mapping[var]; 7854 if (image >= 0) { 7855 if (!used_vars.insert(image).second) continue; 7856 mutable_hint->set_vars(new_size, image); 7857 mutable_hint->set_values(new_size, value); 7858 ++new_size; 7859 } 7860 } 7861 if (new_size > 0) { 7862 mutable_hint->mutable_vars()->Truncate(new_size); 7863 mutable_hint->mutable_values()->Truncate(new_size); 7864 } else { 7865 proto->clear_solution_hint(); 7866 } 7867 } 7868 7869 // Move the variable definitions. 7870 std::vector<IntegerVariableProto> new_variables; 7871 for (int i = 0; i < mapping.size(); ++i) { 7872 const int image = mapping[i]; 7873 if (image < 0) continue; 7874 if (image >= new_variables.size()) { 7875 new_variables.resize(image + 1, IntegerVariableProto()); 7876 } 7877 new_variables[image].Swap(proto->mutable_variables(i)); 7878 } 7879 proto->clear_variables(); 7880 for (IntegerVariableProto& proto_ref : new_variables) { 7881 proto->add_variables()->Swap(&proto_ref); 7882 } 7883 7884 // Check that all variables are used. 7885 for (const IntegerVariableProto& v : proto->variables()) { 7886 CHECK_GT(v.domain_size(), 0); 7887 } 7888 } 7889 7890 namespace { 7891 ConstraintProto CopyConstraintForDuplicateDetection(const ConstraintProto& ct) { 7892 ConstraintProto copy = ct; 7893 copy.clear_name(); 7894 if (ct.constraint_case() == ConstraintProto::kLinear) { 7895 copy.mutable_linear()->clear_domain(); 7896 } 7897 return copy; 7898 } 7899 } // namespace 7900 7901 std::vector<std::pair<int, int>> FindDuplicateConstraints( 7902 const CpModelProto& model_proto) { 7903 std::vector<std::pair<int, int>> result; 7904 7905 // We use a map hash: serialized_constraint_proto hash -> constraint index. 7906 ConstraintProto copy; 7907 absl::flat_hash_map<uint64_t, int> equiv_constraints; 7908 7909 std::string s; 7910 const int num_constraints = model_proto.constraints().size(); 7911 for (int c = 0; c < num_constraints; ++c) { 7912 if (model_proto.constraints(c).constraint_case() == 7913 ConstraintProto::CONSTRAINT_NOT_SET) { 7914 continue; 7915 } 7916 7917 // We ignore names when comparing constraints. 7918 // 7919 // TODO(user): This is not particularly efficient. 7920 copy = CopyConstraintForDuplicateDetection(model_proto.constraints(c)); 7921 s = copy.SerializeAsString(); 7922 7923 const uint64_t hash = absl::Hash<std::string>()(s); 7924 const auto [it, inserted] = equiv_constraints.insert({hash, c}); 7925 if (!inserted) { 7926 // Already present! 7927 const int other_c_with_same_hash = it->second; 7928 copy = CopyConstraintForDuplicateDetection( 7929 model_proto.constraints(other_c_with_same_hash)); 7930 if (s == copy.SerializeAsString()) { 7931 result.push_back({c, other_c_with_same_hash}); 7932 } 7933 } 7934 } 7935 7936 return result; 7937 } 7938 7939 } // namespace sat 7940 } // namespace operations_research 7941