Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_copy.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <optional>
21#include <string>
22#include <vector>
23
24#include "absl/container/flat_hash_set.h"
25#include "absl/log/check.h"
26#include "absl/log/log.h"
27#include "absl/strings/str_cat.h"
28#include "absl/types/span.h"
29#include "google/protobuf/arena.h"
30#include "google/protobuf/repeated_field.h"
31#include "google/protobuf/repeated_ptr_field.h"
32#include "google/protobuf/text_format.h"
40
41namespace operations_research {
42namespace sat {
43
44ModelCopy::ModelCopy(PresolveContext* context) : context_(context) {}
45
47 const CpModelProto& in_model) {
48 if (context_->params().ignore_names()) {
49 context_->working_model->clear_variables();
50 context_->working_model->mutable_variables()->Reserve(
51 in_model.variables_size());
52 for (const IntegerVariableProto& var_proto : in_model.variables()) {
53 *context_->working_model->add_variables()->mutable_domain() =
54 var_proto.domain();
55 }
56 } else {
57 *context_->working_model->mutable_variables() = in_model.variables();
58 }
59}
60
61void ModelCopy::CreateVariablesFromDomains(absl::Span<const Domain> domains) {
62 for (const Domain& domain : domains) {
63 FillDomainInProto(domain, context_->working_model->add_variables());
64 }
65}
66
67// TODO(user): Merge with the phase 1 of the presolve code.
68//
69// TODO(user): It seems easy to forget to update this if any new constraint
70// contains an interval or if we add a field to an existing constraint. Find a
71// way to remind contributor to not forget this.
73 const CpModelProto& in_model, bool first_copy,
74 std::function<bool(int)> active_constraints) {
75 context_->InitializeNewDomains();
76 if (context_->ModelIsUnsat()) return false;
77 const bool ignore_names = context_->params().ignore_names();
78
79 // If first_copy is true, we reorder the scheduling constraint to be sure they
80 // refer to interval before them.
81 std::vector<int> constraints_using_intervals;
82
83 interval_mapping_.assign(in_model.constraints().size(), -1);
84
85 starting_constraint_index_ = context_->working_model->constraints_size();
86 for (int c = 0; c < in_model.constraints_size(); ++c) {
87 if (active_constraints != nullptr && !active_constraints(c)) {
88 continue;
89 }
90 const ConstraintProto& ct = in_model.constraints(c);
91 if (first_copy) {
92 if (!PrepareEnforcementCopyWithDup(ct)) continue;
93 } else {
94 if (!PrepareEnforcementCopy(ct)) continue;
95 }
96
97 // TODO(user): if ignore_names is false, we should make sure the
98 // name are properly copied by all these functions. Or we should never copy
99 // name and have a separate if (!ignore_name) copy the name...
100 switch (ct.constraint_case()) {
102 break;
104 if (first_copy) {
105 if (!CopyBoolOrWithDupSupport(ct)) return CreateUnsatModel(c, ct);
106 } else {
107 if (!CopyBoolOr(ct)) return CreateUnsatModel(c, ct);
108 }
109 break;
111 if (temp_enforcement_literals_.empty()) {
112 for (const int lit : ct.bool_and().literals()) {
113 context_->UpdateRuleStats("bool_and: non-reified.");
114 if (!context_->SetLiteralToTrue(lit)) {
115 return CreateUnsatModel(c, ct);
116 }
117 }
118 } else if (first_copy) {
119 if (!CopyBoolAndWithDupSupport(ct)) return CreateUnsatModel(c, ct);
120 } else {
121 if (!CopyBoolAnd(ct)) return CreateUnsatModel(c, ct);
122 }
123 break;
125 if (!CopyLinear(ct, /*canonicalize=*/first_copy)) {
126 return CreateUnsatModel(c, ct);
127 }
128 break;
130 if (!CopyIntProd(ct, ignore_names)) return CreateUnsatModel(c, ct);
131 break;
133 if (!CopyIntDiv(ct, ignore_names)) return CreateUnsatModel(c, ct);
134 break;
136 if (!CopyIntMod(ct, ignore_names)) return CreateUnsatModel(c, ct);
137 break;
139 if (!CopyElement(ct)) return CreateUnsatModel(c, ct);
140 break;
142 if (!CopyTable(ct)) return CreateUnsatModel(c, ct);
143 break;
145 if (!CopyAutomaton(ct)) return CreateUnsatModel(c, ct);
146 break;
148 if (!CopyAllDiff(ct)) return CreateUnsatModel(c, ct);
149 break;
151 if (!CopyLinMax(ct)) return CreateUnsatModel(c, ct);
152 break;
154 if (!CopyAtMostOne(ct)) return CreateUnsatModel(c, ct);
155 break;
157 if (!CopyExactlyOne(ct)) return CreateUnsatModel(c, ct);
158 break;
160 if (!CopyInterval(ct, c, ignore_names)) return CreateUnsatModel(c, ct);
161 if (first_copy) {
162 if (!AddLinearConstraintForInterval(ct))
163 return CreateUnsatModel(c, ct);
164 }
165 break;
167 if (first_copy) {
168 constraints_using_intervals.push_back(c);
169 } else {
170 CopyAndMapNoOverlap(ct);
171 }
172 break;
174 if (first_copy) {
175 constraints_using_intervals.push_back(c);
176 } else {
177 CopyAndMapNoOverlap2D(ct);
178 }
179 break;
181 if (first_copy) {
182 constraints_using_intervals.push_back(c);
183 } else {
184 if (!CopyAndMapCumulative(ct)) return CreateUnsatModel(c, ct);
185 }
186 break;
187 default: {
188 ConstraintProto* new_ct = context_->working_model->add_constraints();
189 *new_ct = ct;
190 new_ct->mutable_enforcement_literal()->Clear();
191 FinishEnforcementCopy(new_ct);
192 if (ignore_names) {
193 // TODO(user): find a better way than copy then clear_name()?
194 new_ct->clear_name();
195 }
196 }
197 }
198 }
199
200 // This should be empty if first_copy is false.
201 DCHECK(first_copy || constraints_using_intervals.empty());
202 for (const int c : constraints_using_intervals) {
203 const ConstraintProto& ct = in_model.constraints(c);
204 switch (ct.constraint_case()) {
206 CopyAndMapNoOverlap(ct);
207 break;
209 CopyAndMapNoOverlap2D(ct);
210 break;
212 if (!CopyAndMapCumulative(ct)) return CreateUnsatModel(c, ct);
213 break;
214 default:
215 LOG(DFATAL) << "Shouldn't be here.";
216 }
217 }
218
219 return true;
220}
221
222bool ModelCopy::PrepareEnforcementCopy(const ConstraintProto& ct) {
223 temp_enforcement_literals_.clear();
224 for (const int lit : ct.enforcement_literal()) {
225 if (context_->LiteralIsTrue(lit)) continue;
226 if (context_->LiteralIsFalse(lit)) {
227 context_->UpdateRuleStats("enforcement: always false");
228 return false;
229 }
230 temp_enforcement_literals_.push_back(lit);
231 }
232 return true; // Continue processing.
233}
234
235bool ModelCopy::PrepareEnforcementCopyWithDup(const ConstraintProto& ct) {
236 temp_enforcement_literals_.clear();
237 temp_enforcement_literals_set_.clear();
238 for (const int lit : ct.enforcement_literal()) {
239 if (context_->LiteralIsTrue(lit)) continue;
240 if (temp_enforcement_literals_set_.contains(lit)) {
241 context_->UpdateRuleStats("enforcement: removed duplicate literal");
242 continue;
243 }
244
245 // Cannot be satisfied.
246 if (context_->LiteralIsFalse(lit)) {
247 context_->UpdateRuleStats("enforcement: always false");
248 return false;
249 }
250 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
251 context_->UpdateRuleStats("enforcement: contains x and not(x)");
252 return false;
253 }
254
255 temp_enforcement_literals_.push_back(lit);
256 temp_enforcement_literals_set_.insert(lit);
257 }
258 return true; // Continue processing.
259}
260
261void ModelCopy::FinishEnforcementCopy(ConstraintProto* ct) {
262 ct->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
263 temp_enforcement_literals_.end());
264}
265
266bool ModelCopy::FinishBoolOrCopy() {
267 if (temp_literals_.empty()) return false;
268
269 if (temp_literals_.size() == 1) {
270 context_->UpdateRuleStats("bool_or: only one literal");
271 return context_->SetLiteralToTrue(temp_literals_[0]);
272 }
273
274 context_->working_model->add_constraints()
275 ->mutable_bool_or()
276 ->mutable_literals()
277 ->Add(temp_literals_.begin(), temp_literals_.end());
278 return true;
279}
280
281bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) {
282 temp_literals_.clear();
283 for (const int lit : temp_enforcement_literals_) {
284 temp_literals_.push_back(NegatedRef(lit));
285 }
286 for (const int lit : ct.bool_or().literals()) {
287 if (context_->LiteralIsTrue(lit)) {
288 return true;
289 }
290 if (!context_->LiteralIsFalse(lit)) {
291 temp_literals_.push_back(lit);
292 }
293 }
294 return FinishBoolOrCopy();
295}
296
297bool ModelCopy::CopyBoolOrWithDupSupport(const ConstraintProto& ct) {
298 temp_literals_.clear();
299 temp_literals_set_.clear();
300 for (const int enforcement_lit : temp_enforcement_literals_) {
301 // Having an enforcement literal is the same as having its negation on
302 // the clause.
303 const int lit = NegatedRef(enforcement_lit);
304
305 // Note that we already dealt with duplicate since we should have called
306 // PrepareEnforcementCopyWithDup() in this case.
307 temp_literals_set_.insert(lit);
308 temp_literals_.push_back(lit);
309 }
310 for (const int lit : ct.bool_or().literals()) {
311 if (context_->LiteralIsTrue(lit)) {
312 context_->UpdateRuleStats("bool_or: always true");
313 return true;
314 }
315 if (context_->LiteralIsFalse(lit)) continue;
316 if (temp_literals_set_.contains(NegatedRef(lit))) {
317 context_->UpdateRuleStats("bool_or: always true");
318 return true;
319 }
320 const auto [it, inserted] = temp_literals_set_.insert(lit);
321 if (inserted) temp_literals_.push_back(lit);
322 }
323 return FinishBoolOrCopy();
324}
325
326bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) {
327 bool at_least_one_false = false;
328 int num_non_fixed_literals = 0;
329 for (const int lit : ct.bool_and().literals()) {
330 if (context_->LiteralIsFalse(lit)) {
331 at_least_one_false = true;
332 break;
333 }
334 if (!context_->LiteralIsTrue(lit)) {
335 num_non_fixed_literals++;
336 }
337 }
338
339 if (at_least_one_false) {
340 // One enforcement literal must be false.
341 BoolArgumentProto* bool_or =
342 context_->working_model->add_constraints()->mutable_bool_or();
343 for (const int lit : temp_enforcement_literals_) {
344 bool_or->add_literals(NegatedRef(lit));
345 }
346 return !bool_or->literals().empty();
347 } else if (num_non_fixed_literals > 0) {
348 ConstraintProto* new_ct = context_->working_model->add_constraints();
349 FinishEnforcementCopy(new_ct);
350 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
351 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
352 for (const int lit : ct.bool_and().literals()) {
353 if (context_->LiteralIsTrue(lit)) continue;
354 bool_and->add_literals(lit);
355 }
356 }
357 return true;
358}
359
360bool ModelCopy::CopyBoolAndWithDupSupport(const ConstraintProto& ct) {
361 DCHECK(!temp_enforcement_literals_.empty());
362
363 bool at_least_one_false = false;
364 temp_literals_.clear();
365 temp_literals_set_.clear();
366 for (const int lit : ct.bool_and().literals()) {
367 if (context_->LiteralIsFalse(lit)) {
368 context_->UpdateRuleStats("bool and: always false");
369 at_least_one_false = true;
370 break;
371 }
372 if (temp_literals_set_.contains(NegatedRef(lit))) {
373 context_->UpdateRuleStats("bool and: => x and not(x) ");
374 at_least_one_false = true;
375 break;
376 }
377 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
378 context_->UpdateRuleStats("bool and: not(x) => x");
379 at_least_one_false = true;
380 break;
381 }
382
383 if (context_->LiteralIsTrue(lit)) continue;
384 if (temp_enforcement_literals_set_.contains(lit)) {
385 context_->UpdateRuleStats("bool and: x => x");
386 continue;
387 }
388 const auto [it, inserted] = temp_literals_set_.insert(lit);
389 if (inserted) temp_literals_.push_back(lit);
390 }
391
392 if (at_least_one_false) {
393 // One enforcement literal must be false.
394 BoolArgumentProto* bool_or =
395 context_->working_model->add_constraints()->mutable_bool_or();
396 for (const int lit : temp_enforcement_literals_) {
397 bool_or->add_literals(NegatedRef(lit));
398 }
399 return !bool_or->literals().empty();
400 }
401
402 if (temp_literals_.empty()) {
403 context_->UpdateRuleStats("bool and: empty");
404 return true;
405 }
406
407 // Copy.
408 ConstraintProto* new_ct = context_->working_model->add_constraints();
409 FinishEnforcementCopy(new_ct);
410 new_ct->mutable_bool_and()->mutable_literals()->Add(temp_literals_.begin(),
411 temp_literals_.end());
412 return true;
413}
414
415bool ModelCopy::CopyLinearExpression(
417 absl::Span<const int> enforcement_literals) {
418 non_fixed_variables_.clear();
419 non_fixed_coefficients_.clear();
420 int64_t offset = expr.offset();
421 for (int i = 0; i < expr.vars_size(); ++i) {
422 const int ref = expr.vars(i);
423 const int64_t coeff = expr.coeffs(i);
424 if (coeff == 0) continue;
425 if (context_->IsFixed(ref)) {
426 offset += coeff * context_->MinOf(ref);
427 continue;
428 }
429
430 // Make sure we never have negative ref in a linear constraint.
431 if (RefIsPositive(ref)) {
432 non_fixed_variables_.push_back(ref);
433 non_fixed_coefficients_.push_back(coeff);
434 } else {
435 non_fixed_variables_.push_back(NegatedRef(ref));
436 non_fixed_coefficients_.push_back(-coeff);
437 }
438 }
439
440 dst->set_offset(offset);
441 dst->mutable_vars()->Add(non_fixed_variables_.begin(),
442 non_fixed_variables_.end());
443 dst->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
444 non_fixed_coefficients_.end());
445 // TODO(user): We could save work by only doing this if this is the first
446 // copy.
447 context_->CanonicalizeLinearExpression(enforcement_literals, dst);
448 return true;
449}
450
451bool ModelCopy::CopyLinear(const ConstraintProto& ct, bool canonicalize) {
452 non_fixed_variables_.clear();
453 non_fixed_coefficients_.clear();
454 int64_t offset = 0;
455 int64_t min_activity = 0;
456 int64_t max_activity = 0;
457 for (int i = 0; i < ct.linear().vars_size(); ++i) {
458 const int ref = ct.linear().vars(i);
459 const int64_t coeff = ct.linear().coeffs(i);
460 if (coeff == 0) continue;
461 if (context_->IsFixed(ref)) {
462 offset += coeff * context_->MinOf(ref);
463 continue;
464 }
465
466 if (coeff > 0) {
467 min_activity += coeff * context_->MinOf(ref);
468 max_activity += coeff * context_->MaxOf(ref);
469 } else {
470 min_activity += coeff * context_->MaxOf(ref);
471 max_activity += coeff * context_->MinOf(ref);
472 }
473
474 // Make sure we never have negative ref in a linear constraint.
475 if (RefIsPositive(ref)) {
476 non_fixed_variables_.push_back(ref);
477 non_fixed_coefficients_.push_back(coeff);
478 } else {
479 non_fixed_variables_.push_back(NegatedRef(ref));
480 non_fixed_coefficients_.push_back(-coeff);
481 }
482 }
483
484 const Domain implied(min_activity, max_activity);
485 const Domain new_rhs =
486 ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset));
487
488 // Trivial constraint?
489 if (implied.IsIncludedIn(new_rhs)) {
490 context_->UpdateRuleStats("linear: always true");
491 return true;
492 }
493
494 // Constraint is false?
495 const Domain tight_domain = implied.IntersectionWith(new_rhs);
496 if (tight_domain.IsEmpty()) {
497 if (ct.enforcement_literal().empty()) return false;
498 temp_literals_.clear();
499 for (const int literal : ct.enforcement_literal()) {
500 if (!context_->LiteralIsTrue(literal)) {
501 temp_literals_.push_back(NegatedRef(literal));
502 }
503 }
504 context_->working_model->add_constraints()
505 ->mutable_bool_or()
506 ->mutable_literals()
507 ->Add(temp_literals_.begin(), temp_literals_.end());
508 return !temp_literals_.empty();
509 }
510
511 DCHECK(!non_fixed_variables_.empty());
512
513 if (non_fixed_variables_.size() == 1 && ct.enforcement_literal().empty()) {
514 context_->UpdateRuleStats("linear1: x in domain");
515 return context_->IntersectDomainWith(
516 non_fixed_variables_[0],
517 new_rhs.InverseMultiplicationBy(non_fixed_coefficients_[0]));
518 }
519
520 ConstraintProto* new_ct = context_->working_model->add_constraints();
521 FinishEnforcementCopy(new_ct);
522 LinearConstraintProto* linear = new_ct->mutable_linear();
523 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
524 non_fixed_variables_.end());
525 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
526 non_fixed_coefficients_.end());
527 FillDomainInProto(tight_domain, linear);
528 if (canonicalize) {
529 context_->CanonicalizeLinearConstraint(new_ct);
530 // We checked if the constraint was trivial above, but canonicalization can
531 // make it trivial again by simplifying expressions like (x - x).
532 if (new_ct->linear().vars().empty() &&
533 ReadDomainFromProto(new_ct->linear()).Contains(0)) {
534 context_->UpdateRuleStats("linear: trivial 0=0");
535 context_->working_model->mutable_constraints()->RemoveLast();
536 return true;
537 }
538 }
539 return true;
540}
541
542bool ModelCopy::CopyElement(const ConstraintProto& ct) {
543 ConstraintProto* new_ct = context_->working_model->add_constraints();
544 if (ct.element().vars().empty() && !ct.element().exprs().empty()) {
545 // New format, just copy.
546 *new_ct = ct;
547 return true;
548 }
549
550 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
551 if (context_->IsFixed(var)) {
552 expr->set_offset(context_->FixedValue(var));
553 } else {
554 DCHECK(RefIsPositive(var));
555 expr->mutable_vars()->Reserve(1);
556 expr->mutable_coeffs()->Reserve(1);
557 expr->add_vars(var);
558 expr->add_coeffs(1);
559 }
560 };
561
562 fill_expr(ct.element().index(),
563 new_ct->mutable_element()->mutable_linear_index());
564 fill_expr(ct.element().target(),
565 new_ct->mutable_element()->mutable_linear_target());
566 for (const int var : ct.element().vars()) {
567 fill_expr(var, new_ct->mutable_element()->add_exprs());
568 }
569 return true;
570}
571
572bool ModelCopy::CopyAutomaton(const ConstraintProto& ct) {
573 ConstraintProto* new_ct = context_->working_model->add_constraints();
574 *new_ct = ct;
575 if (new_ct->automaton().vars().empty()) return true;
576
577 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
578 if (context_->IsFixed(var)) {
579 expr->set_offset(context_->FixedValue(var));
580 } else {
581 DCHECK(RefIsPositive(var));
582 expr->mutable_vars()->Reserve(1);
583 expr->mutable_coeffs()->Reserve(1);
584 expr->add_vars(var);
585 expr->add_coeffs(1);
586 }
587 };
588
589 for (const int var : ct.automaton().vars()) {
590 fill_expr(var, new_ct->mutable_automaton()->add_exprs());
591 }
592 new_ct->mutable_automaton()->clear_vars();
593
594 return true;
595}
596
597bool ModelCopy::CopyTable(const ConstraintProto& ct) {
598 ConstraintProto* new_ct = context_->working_model->add_constraints();
599 if (ct.table().vars().empty() && !ct.table().exprs().empty()) {
600 // New format, just copy.
601 *new_ct = ct;
602 return true;
603 }
604
605 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
606 if (context_->IsFixed(var)) {
607 expr->set_offset(context_->FixedValue(var));
608 } else {
609 DCHECK(RefIsPositive(var));
610 expr->mutable_vars()->Reserve(1);
611 expr->mutable_coeffs()->Reserve(1);
612 expr->add_vars(var);
613 expr->add_coeffs(1);
614 }
615 };
616
617 for (const int var : ct.table().vars()) {
618 fill_expr(var, new_ct->mutable_table()->add_exprs());
619 }
620 *new_ct->mutable_table()->mutable_values() = ct.table().values();
621 new_ct->mutable_table()->set_negated(ct.table().negated());
622 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
623
624 return true;
625}
626
627bool ModelCopy::CopyAllDiff(const ConstraintProto& ct) {
628 if (ct.all_diff().exprs().size() <= 1) return true;
629 ConstraintProto* new_ct = context_->working_model->add_constraints();
630 for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
631 CopyLinearExpression(expr, new_ct->mutable_all_diff()->add_exprs());
632 }
633 return true;
634}
635
636bool ModelCopy::CopyLinMax(const ConstraintProto& ct) {
637 // We will create it lazily if we end up copying something.
638 ConstraintProto* new_ct = nullptr;
639
640 // Regroup all constant terms and copy the other.
641 int64_t max_of_fixed_terms = std::numeric_limits<int64_t>::min();
642 for (const auto& expr : ct.lin_max().exprs()) {
643 const std::optional<int64_t> fixed = context_->FixedValueOrNullopt(expr);
644 if (fixed != std::nullopt) {
645 max_of_fixed_terms = std::max(max_of_fixed_terms, fixed.value());
646 } else {
647 // copy.
648 if (new_ct == nullptr) {
649 new_ct = context_->working_model->add_constraints();
650 }
651 CopyLinearExpression(expr, new_ct->mutable_lin_max()->add_exprs());
652 }
653 }
654
655 // If we have no non-fixed expression, we can just fix the target when it
656 // involve at most one variable.
657 if (new_ct == nullptr && ct.enforcement_literal().empty() &&
658 ct.lin_max().target().vars().size() <= 1) {
659 context_->UpdateRuleStats("lin_max: all exprs fixed during copy");
660 return context_->IntersectDomainWith(ct.lin_max().target(),
661 Domain(max_of_fixed_terms));
662 }
663
664 // Otherwise, add a constant term if needed.
665 if (max_of_fixed_terms > std::numeric_limits<int64_t>::min()) {
666 if (new_ct == nullptr) {
667 new_ct = context_->working_model->add_constraints();
668 }
669 new_ct->mutable_lin_max()->add_exprs()->set_offset(max_of_fixed_terms);
670 }
671
672 // Finish by copying the target.
673 if (new_ct == nullptr) return false; // No expr == unsat.
674 CopyLinearExpression(ct.lin_max().target(),
675 new_ct->mutable_lin_max()->mutable_target());
676 return true;
677}
678
679bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) {
680 int num_true = 0;
681 temp_literals_.clear();
682 for (const int lit : ct.at_most_one().literals()) {
683 if (context_->LiteralIsFalse(lit)) continue;
684 temp_literals_.push_back(lit);
685 if (context_->LiteralIsTrue(lit)) num_true++;
686 }
687
688 if (temp_literals_.size() <= 1) return true;
689 if (num_true > 1) return false;
690
691 // TODO(user): presolve if num_true == 1.
692 ConstraintProto* new_ct = context_->working_model->add_constraints();
693 FinishEnforcementCopy(new_ct);
694 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
695 temp_literals_.end());
696 return true;
697}
698
699bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) {
700 int num_true = 0;
701 temp_literals_.clear();
702 for (const int lit : ct.exactly_one().literals()) {
703 if (context_->LiteralIsFalse(lit)) continue;
704 temp_literals_.push_back(lit);
705 if (context_->LiteralIsTrue(lit)) num_true++;
706 }
707
708 if (temp_literals_.empty() || num_true > 1) return false;
709 if (temp_literals_.size() == 1 && num_true == 1) return true;
710
711 // TODO(user): presolve if num_true == 1 and not everything is false.
712 ConstraintProto* new_ct = context_->working_model->add_constraints();
713 FinishEnforcementCopy(new_ct);
714 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
715 temp_literals_.end());
716 return true;
717}
718
719bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c,
720 bool ignore_names) {
721 CHECK_EQ(starting_constraint_index_, 0)
722 << "Adding new interval constraints to partially filled model is not "
723 "supported.";
724 interval_mapping_[c] = context_->working_model->constraints_size();
725 ConstraintProto* new_ct = context_->working_model->add_constraints();
726 if (!ignore_names) {
727 new_ct->set_name(ct.name());
728 }
729 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
730 CopyLinearExpression(ct.interval().start(),
731 new_ct->mutable_interval()->mutable_start(),
732 ct.enforcement_literal());
733 CopyLinearExpression(ct.interval().size(),
734 new_ct->mutable_interval()->mutable_size(),
735 ct.enforcement_literal());
736 CopyLinearExpression(ct.interval().end(),
737 new_ct->mutable_interval()->mutable_end(),
738 ct.enforcement_literal());
739 return true;
740}
741
742bool ModelCopy::CopyIntProd(const ConstraintProto& ct, bool ignore_names) {
743 ConstraintProto* new_ct = context_->working_model->add_constraints();
744 if (!ignore_names) {
745 new_ct->set_name(ct.name());
746 }
747 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
748 CopyLinearExpression(expr, new_ct->mutable_int_prod()->add_exprs());
749 }
750 CopyLinearExpression(ct.int_prod().target(),
751 new_ct->mutable_int_prod()->mutable_target());
752 return true;
753}
754
755bool ModelCopy::CopyIntDiv(const ConstraintProto& ct, bool ignore_names) {
756 ConstraintProto* new_ct = context_->working_model->add_constraints();
757 if (!ignore_names) {
758 new_ct->set_name(ct.name());
759 }
760 for (const LinearExpressionProto& expr : ct.int_div().exprs()) {
761 CopyLinearExpression(expr, new_ct->mutable_int_div()->add_exprs());
762 }
763 CopyLinearExpression(ct.int_div().target(),
764 new_ct->mutable_int_div()->mutable_target());
765 return true;
766}
767
768bool ModelCopy::CopyIntMod(const ConstraintProto& ct, bool ignore_names) {
769 ConstraintProto* new_ct = context_->working_model->add_constraints();
770 if (!ignore_names) {
771 new_ct->set_name(ct.name());
772 }
773 for (const LinearExpressionProto& expr : ct.int_mod().exprs()) {
774 CopyLinearExpression(expr, new_ct->mutable_int_mod()->add_exprs());
775 }
776 CopyLinearExpression(ct.int_mod().target(),
777 new_ct->mutable_int_mod()->mutable_target());
778 return true;
779}
780
781bool ModelCopy::AddLinearConstraintForInterval(const ConstraintProto& ct) {
782 // Add the linear constraint enforcement => (start + size == end).
783 //
784 // We rely on the presolve for simplification, but deal with the trivial
785 // case of (start, offset, start + offset) here.
786 const IntervalConstraintProto& itv = ct.interval();
787 if (itv.size().vars().empty() &&
788 itv.start().offset() + itv.size().offset() == itv.end().offset() &&
789 absl::Span<const int>(itv.start().vars()) ==
790 absl::Span<const int>(itv.end().vars()) &&
791 absl::Span<const int64_t>(itv.start().coeffs()) ==
792 absl::Span<const int64_t>(itv.end().coeffs())) {
793 // Trivial constraint, nothing to do.
794 } else {
795 tmp_constraint_.Clear();
796 *tmp_constraint_.mutable_enforcement_literal() = ct.enforcement_literal();
797 LinearConstraintProto* mutable_linear = tmp_constraint_.mutable_linear();
798
799 mutable_linear->add_domain(0);
800 mutable_linear->add_domain(0);
801 AddLinearExpressionToLinearConstraint(itv.start(), 1, mutable_linear);
802 AddLinearExpressionToLinearConstraint(itv.size(), 1, mutable_linear);
803 AddLinearExpressionToLinearConstraint(itv.end(), -1, mutable_linear);
804 if (!CopyLinear(tmp_constraint_, true)) return false;
805 }
806
807 // An enforced interval must have its size non-negative.
808 const LinearExpressionProto& size_expr = itv.size();
809 if (context_->MinOf(size_expr) < 0) {
810 tmp_constraint_.Clear();
811 *tmp_constraint_.mutable_enforcement_literal() = ct.enforcement_literal();
812 *tmp_constraint_.mutable_linear()->mutable_vars() = size_expr.vars();
813 *tmp_constraint_.mutable_linear()->mutable_coeffs() = size_expr.coeffs();
814 tmp_constraint_.mutable_linear()->add_domain(-size_expr.offset());
815 tmp_constraint_.mutable_linear()->add_domain(
816 std::numeric_limits<int64_t>::max());
817 if (!CopyLinear(tmp_constraint_, true)) return false;
818 }
819
820 return true;
821}
822
823void ModelCopy::CopyAndMapNoOverlap(const ConstraintProto& ct) {
824 // Note that we don't copy names or enforcement_literal (not supported) here.
825 auto* new_ct =
826 context_->working_model->add_constraints()->mutable_no_overlap();
827 new_ct->mutable_intervals()->Reserve(ct.no_overlap().intervals().size());
828 for (const int index : ct.no_overlap().intervals()) {
829 const int new_index = interval_mapping_[index];
830 if (new_index != -1) {
831 new_ct->add_intervals(new_index);
832 }
833 }
834}
835
836void ModelCopy::CopyAndMapNoOverlap2D(const ConstraintProto& ct) {
837 // Note that we don't copy names or enforcement_literal (not supported) here.
838 auto* new_ct =
839 context_->working_model->add_constraints()->mutable_no_overlap_2d();
840
841 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
842 new_ct->mutable_x_intervals()->Reserve(num_intervals);
843 new_ct->mutable_y_intervals()->Reserve(num_intervals);
844 for (int i = 0; i < num_intervals; ++i) {
845 const int new_x = interval_mapping_[ct.no_overlap_2d().x_intervals(i)];
846 if (new_x == -1) continue;
847 const int new_y = interval_mapping_[ct.no_overlap_2d().y_intervals(i)];
848 if (new_y == -1) continue;
849 new_ct->add_x_intervals(new_x);
850 new_ct->add_y_intervals(new_y);
851 }
852}
853
854bool ModelCopy::CopyAndMapCumulative(const ConstraintProto& ct) {
855 if (ct.cumulative().intervals().empty() &&
856 context_->IsFixed(ct.cumulative().capacity())) {
857 // Trivial constraint, either obviously SAT or UNSAT.
858 return context_->FixedValue(ct.cumulative().capacity()) >= 0;
859 }
860 // Note that we don't copy names or enforcement_literal (not supported) here.
861 auto* new_ct =
862 context_->working_model->add_constraints()->mutable_cumulative();
863 CopyLinearExpression(ct.cumulative().capacity(), new_ct->mutable_capacity());
864
865 const int num_intervals = ct.cumulative().intervals().size();
866 new_ct->mutable_intervals()->Reserve(num_intervals);
867 new_ct->mutable_demands()->Reserve(num_intervals);
868 for (int i = 0; i < num_intervals; ++i) {
869 const int new_index = interval_mapping_[ct.cumulative().intervals(i)];
870 if (new_index != -1) {
871 new_ct->add_intervals(new_index);
872 CopyLinearExpression(ct.cumulative().demands(i), new_ct->add_demands());
873 }
874 }
875
876 return true;
877}
878
879bool ModelCopy::CreateUnsatModel(int c, const ConstraintProto& ct) {
880 context_->working_model->mutable_constraints()->Clear();
881 context_->working_model->add_constraints()->mutable_bool_or();
882
883 // If the model was already marked as unsat, we keep the old message and just
884 // return. TODO(user): Append messages instead?
885 if (context_->ModelIsUnsat()) return false;
886
887 std::string proto_string;
888#if !defined(__PORTABLE_PLATFORM__)
889 google::protobuf::TextFormat::Printer printer;
890 SetupTextFormatPrinter(&printer);
891 printer.PrintToString(ct, &proto_string);
892#endif // !defined(__PORTABLE_PLATFORM__)
893 std::string message = absl::StrCat(
894 "proven during initial copy of constraint #", c, ":\n", proto_string);
895 std::vector<int> vars = UsedVariables(ct);
896 if (vars.size() < 10) {
897 absl::StrAppend(&message, "With current variable domains:\n");
898 for (const int var : vars) {
899 absl::StrAppend(&message, "var:", var,
900 " domain:", context_->DomainOf(var).ToString(), "\n");
901 }
902 }
903 return context_->NotifyThatModelIsUnsat(message);
904}
905
907 PresolveContext* context) {
908 ModelCopy copier(context);
909 copier.ImportVariablesAndMaybeIgnoreNames(in_model);
910 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/true)) {
912 context);
913 return true;
914 }
915 return !context->ModelIsUnsat();
916}
917
919 const CpModelProto& in_model, absl::Span<const Domain> domains,
920 std::function<bool(int)> active_constraints, PresolveContext* context,
921 std::vector<int>* interval_mapping) {
922 CHECK_EQ(domains.size(), in_model.variables_size());
923 ModelCopy copier(context);
924 copier.CreateVariablesFromDomains(domains);
925 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/false,
926 active_constraints)) {
928 context);
929 interval_mapping->assign(copier.InternalIntervalMapping().begin(),
930 copier.InternalIntervalMapping().end());
931 return true;
932 }
933 return !context->ModelIsUnsat();
934}
935
937 const CpModelProto& in_model, PresolveContext* context) {
938 if (!in_model.name().empty()) {
939 context->working_model->set_name(in_model.name());
940 }
941 if (in_model.has_objective()) {
942 *context->working_model->mutable_objective() = in_model.objective();
943 }
944 if (in_model.has_floating_point_objective()) {
946 in_model.floating_point_objective();
947 }
948 if (!in_model.search_strategy().empty()) {
949 // We make sure we do not use the old variables field.
951 in_model.search_strategy();
952 for (DecisionStrategyProto& strategy :
954 google::protobuf::util::RemoveIf(strategy.mutable_exprs(),
955 [](const LinearExpressionProto* expr) {
956 return expr->vars().empty();
957 });
958 if (!strategy.variables().empty()) {
959 CHECK(strategy.exprs().empty());
960 for (const int ref : strategy.variables()) {
961 LinearExpressionProto* expr = strategy.add_exprs();
962 expr->add_vars(PositiveRef(ref));
963 expr->add_coeffs(RefIsPositive(ref) ? 1 : -1);
964 }
965 strategy.clear_variables();
966 }
967 }
968 }
969 if (!in_model.assumptions().empty()) {
970 *context->working_model->mutable_assumptions() = in_model.assumptions();
971 }
972 if (in_model.has_symmetry()) {
973 *context->working_model->mutable_symmetry() = in_model.symmetry();
974 }
975 if (in_model.has_solution_hint()) {
976 *context->working_model->mutable_solution_hint() = in_model.solution_hint();
977
978 // We make sure the hint is within the variables domain.
979 //
980 // This allows to avoid overflow because we know evaluating constraints on
981 // the variables domains should be safe thanks to the initial validation.
982 const int num_terms = in_model.solution_hint().vars().size();
983 for (int i = 0; i < num_terms; ++i) {
984 const int var = in_model.solution_hint().vars(i);
985 const int64_t value = in_model.solution_hint().values(i);
986 const Domain& domain = ReadDomainFromProto(in_model.variables(var));
987 if (domain.IsEmpty()) continue; // UNSAT.
988 const int64_t closest_domain_value = domain.ClosestValue(value);
989 if (closest_domain_value != value) {
990 context->UpdateRuleStats("hint: moved var hint within its domain.");
992 i, closest_domain_value);
993 }
994 }
995 }
996}
997
998} // namespace sat
999} // namespace operations_research
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
Domain AdditionWith(const Domain &domain) const
int64_t ClosestValue(int64_t wanted) const
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::BoolArgumentProto & bool_and() const
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
::operations_research::sat::FloatObjectiveProto *PROTOBUF_NONNULL mutable_floating_point_objective()
bool has_symmetry() const
.operations_research.sat.SymmetryProto symmetry = 8;
bool has_solution_hint() const
.operations_research.sat.PartialVariableAssignment solution_hint = 6;
bool has_floating_point_objective() const
.operations_research.sat.FloatObjectiveProto floating_point_objective = 9;
const ::operations_research::sat::SymmetryProto & symmetry() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::FloatObjectiveProto & floating_point_objective() const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
bool has_objective() const
.operations_research.sat.CpObjectiveProto objective = 4;
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
::int32_t assumptions(int index) const
int constraints_size() const
repeated .operations_research.sat.ConstraintProto constraints = 3;
void set_name(Arg_ &&arg, Args_... args)
::operations_research::sat::SymmetryProto *PROTOBUF_NONNULL mutable_symmetry()
::operations_research::sat::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
int variables_size() const
repeated .operations_research.sat.IntegerVariableProto variables = 2;
::operations_research::sat::CpObjectiveProto *PROTOBUF_NONNULL mutable_objective()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_assumptions()
const ::std::string & name() const
::operations_research::sat::DecisionStrategyProto *PROTOBUF_NONNULL mutable_search_strategy(int index)
const ::operations_research::sat::PartialVariableAssignment & solution_hint() const
const ::operations_research::sat::CpObjectiveProto & objective() const
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
void CreateVariablesFromDomains(absl::Span< const Domain > domains)
ModelCopy(PresolveContext *context)
absl::Span< const int64_t > InternalIntervalMapping() const
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, bool first_copy=false, std::function< bool(int)> active_constraints=nullptr)
void UpdateRuleStats(const std::string &name, int num_times=1)
int RemoveIf(RepeatedPtrField< T > *array, const Pred &pr)
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
std::vector< int > UsedVariables(const ConstraintProto &ct)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void SetupTextFormatPrinter(google::protobuf::TextFormat::Printer *printer)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
Copies the non constraint, non variables part of the model.
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, absl::Span< const Domain > domains, std::function< bool(int)> active_constraints, PresolveContext *context, std::vector< int > *interval_mapping)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.