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