Google OR-Tools v9.15
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 <cstdlib>
19#include <functional>
20#include <limits>
21#include <numeric>
22#include <optional>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/log/log.h"
30#include "absl/strings/str_cat.h"
31#include "absl/types/span.h"
32#include "google/protobuf/arena.h"
33#include "google/protobuf/repeated_field.h"
34#include "google/protobuf/repeated_ptr_field.h"
35#include "google/protobuf/text_format.h"
45
46namespace operations_research {
47namespace sat {
48
49namespace {
50// This assumes an identity mapping between positive proto refs and Boolean
51// variables (this might not be the case if the input proto contains non Boolean
52// variables between Boolean ones).
53Literal RefToLiteral(int ref) {
54 return Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref));
55}
56int LiteralToRef(Literal lit) {
57 const int var = lit.Variable().value();
58 return lit.IsPositive() ? var : NegatedRef(var);
59}
60} // namespace
61
63 LratProofHandler* lrat_proof_handler)
64 : context_(context), lrat_proof_handler_(lrat_proof_handler) {}
65
66ClauseId ModelCopy::NextInferredClauseId() {
67 return next_inferred_clause_id_++;
68}
69
71 const CpModelProto& in_model) {
72 if (context_->params().ignore_names()) {
73 context_->working_model->clear_variables();
74 context_->working_model->mutable_variables()->Reserve(
75 in_model.variables_size());
76 for (const IntegerVariableProto& var_proto : in_model.variables()) {
77 *context_->working_model->add_variables()->mutable_domain() =
78 var_proto.domain();
79 }
80 } else {
81 *context_->working_model->mutable_variables() = in_model.variables();
82 }
83}
84
85void ModelCopy::CreateVariablesFromDomains(absl::Span<const Domain> domains) {
86 for (const Domain& domain : domains) {
87 FillDomainInProto(domain, context_->working_model->add_variables());
88 }
89}
90
91// TODO(user): Merge with the phase 1 of the presolve code.
92//
93// TODO(user): It seems easy to forget to update this if any new constraint
94// contains an interval or if we add a field to an existing constraint. Find a
95// way to remind contributor to not forget this.
97 const CpModelProto& in_model, bool first_copy,
98 std::function<bool(int)> active_constraints) {
99 context_->InitializeNewDomains();
100 if (context_->ModelIsUnsat()) return false;
101 const bool ignore_names = context_->params().ignore_names();
102
103 // If first_copy is true, we reorder the scheduling constraint to be sure they
104 // refer to interval before them.
105 std::vector<int> constraints_using_intervals;
106
107 interval_mapping_.assign(in_model.constraints().size(), -1);
108 boolean_product_encoding_.clear();
109
110 // The LRAT ASCII file format numbers input problem clauses from 1 to n.
111 // Assuming that each input constraint yields at most one clause, we can
112 // number the inferred clauses starting from in_model.constraints_size() + 1
113 // without risk of collisions.
114 next_inferred_clause_id_ = ClauseId(in_model.constraints_size() + 1);
115 unit_clause_ids_.clear();
116
117 starting_constraint_index_ = context_->working_model->constraints_size();
118 for (int c = 0; c < in_model.constraints_size(); ++c) {
119 if (active_constraints != nullptr && !active_constraints(c)) {
120 continue;
121 }
122 const ConstraintProto& ct = in_model.constraints(c);
123 if (first_copy) {
124 if (!PrepareEnforcementCopyWithDup(ct)) continue;
125 } else {
126 if (!PrepareEnforcementCopy(ct)) continue;
127 }
128
129 // TODO(user): if ignore_names is false, we should make sure the
130 // name are properly copied by all these functions. Or we should never copy
131 // name and have a separate if (!ignore_name) copy the name...
132 switch (ct.constraint_case()) {
134 break;
136 if (first_copy) {
137 if (!CopyBoolOrWithDupSupport(ct, ClauseId(c + 1))) {
138 return CreateUnsatModel(c, ct);
139 }
140 } else {
141 if (!CopyBoolOr(ct)) return CreateUnsatModel(c, ct);
142 }
143 break;
145 if (temp_enforcement_literals_.empty()) {
146 for (const int lit : ct.bool_and().literals()) {
147 context_->UpdateRuleStats("bool_and: non-reified");
148 if (!context_->SetLiteralToTrue(lit)) {
149 return CreateUnsatModel(c, ct);
150 }
151 }
152 } else if (first_copy) {
153 if (!CopyBoolAndWithDupSupport(ct)) return CreateUnsatModel(c, ct);
154 } else {
155 if (!CopyBoolAnd(ct)) return CreateUnsatModel(c, ct);
156 }
157 break;
159 if (!CopyLinear(ct, /*canonicalize=*/first_copy)) {
160 return CreateUnsatModel(c, ct);
161 }
162 break;
164 if (!CopyIntProd(ct, ignore_names)) return CreateUnsatModel(c, ct);
165 break;
167 if (!CopyIntDiv(ct, ignore_names)) return CreateUnsatModel(c, ct);
168 break;
170 if (!CopyIntMod(ct, ignore_names)) return CreateUnsatModel(c, ct);
171 break;
173 if (!CopyElement(ct)) return CreateUnsatModel(c, ct);
174 break;
176 if (!CopyTable(ct)) return CreateUnsatModel(c, ct);
177 break;
179 if (!CopyAutomaton(ct)) return CreateUnsatModel(c, ct);
180 break;
182 if (!CopyAllDiff(ct)) return CreateUnsatModel(c, ct);
183 break;
185 if (!CopyLinMax(ct)) return CreateUnsatModel(c, ct);
186 break;
188 if (!CopyAtMostOne(ct)) return CreateUnsatModel(c, ct);
189 break;
191 if (!CopyExactlyOne(ct)) return CreateUnsatModel(c, ct);
192 break;
194 if (!CopyInterval(ct, c, ignore_names)) return CreateUnsatModel(c, ct);
195 if (first_copy) {
196 if (!AddLinearConstraintForInterval(ct))
197 return CreateUnsatModel(c, ct);
198 }
199 break;
201 if (first_copy) {
202 constraints_using_intervals.push_back(c);
203 } else {
204 CopyAndMapNoOverlap(ct);
205 }
206 break;
208 if (first_copy) {
209 constraints_using_intervals.push_back(c);
210 } else {
211 CopyAndMapNoOverlap2D(ct);
212 }
213 break;
215 if (first_copy) {
216 constraints_using_intervals.push_back(c);
217 } else {
218 if (!CopyAndMapCumulative(ct)) return CreateUnsatModel(c, ct);
219 }
220 break;
221 default: {
222 ConstraintProto* new_ct = context_->working_model->add_constraints();
223 *new_ct = ct;
224 new_ct->mutable_enforcement_literal()->Clear();
225 FinishEnforcementCopy(new_ct);
226 if (ignore_names) {
227 // TODO(user): find a better way than copy then clear_name()?
228 new_ct->clear_name();
229 }
230 }
231 }
232 }
233
234 // This should be empty if first_copy is false.
235 DCHECK(first_copy || constraints_using_intervals.empty());
236 for (const int c : constraints_using_intervals) {
237 const ConstraintProto& ct = in_model.constraints(c);
238 if (!PrepareEnforcementCopyWithDup(ct)) continue;
239 switch (ct.constraint_case()) {
241 CopyAndMapNoOverlap(ct);
242 break;
244 CopyAndMapNoOverlap2D(ct);
245 break;
247 if (!CopyAndMapCumulative(ct)) return CreateUnsatModel(c, ct);
248 break;
249 default:
250 LOG(DFATAL) << "Shouldn't be here.";
251 }
252 }
253
254 if (first_copy) {
255 ExpandNonAffineExpressions();
256 }
257 return true;
258}
259
260bool ModelCopy::PrepareEnforcementCopy(const ConstraintProto& ct) {
261 temp_enforcement_literals_.clear();
262 for (const int lit : ct.enforcement_literal()) {
263 if (context_->LiteralIsTrue(lit)) continue;
264 if (context_->LiteralIsFalse(lit)) {
265 context_->UpdateRuleStats("enforcement: always false");
266 return false;
267 }
268 temp_enforcement_literals_.push_back(lit);
269 }
270 return true; // Continue processing.
271}
272
273bool ModelCopy::PrepareEnforcementCopyWithDup(const ConstraintProto& ct) {
274 temp_enforcement_literals_.clear();
275 temp_enforcement_literals_set_.clear();
276 for (const int lit : ct.enforcement_literal()) {
277 if (context_->LiteralIsTrue(lit)) continue;
278 if (temp_enforcement_literals_set_.contains(lit)) {
279 context_->UpdateRuleStats("enforcement: removed duplicate literal");
280 continue;
281 }
282
283 // Cannot be satisfied.
284 if (context_->LiteralIsFalse(lit)) {
285 context_->UpdateRuleStats("enforcement: always false");
286 return false;
287 }
288 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
289 context_->UpdateRuleStats("enforcement: contains x and not(x)");
290 return false;
291 }
292
293 temp_enforcement_literals_.push_back(lit);
294 temp_enforcement_literals_set_.insert(lit);
295 }
296 return true; // Continue processing.
297}
298
299void ModelCopy::FinishEnforcementCopy(ConstraintProto* ct) {
300 ct->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
301 temp_enforcement_literals_.end());
302}
303
304bool ModelCopy::FinishBoolOrCopy(ClauseId clause_id) {
305 if (temp_literals_.empty()) return false;
306
307 if (temp_literals_.size() == 1) {
308 context_->UpdateRuleStats("bool_or: only one literal");
309 if (lrat_proof_handler_ != nullptr) {
310 unit_clause_ids_[RefToLiteral(temp_literals_[0])] = clause_id;
311 }
312 return context_->SetLiteralToTrue(temp_literals_[0]);
313 }
314
315 context_->working_model->add_constraints()
316 ->mutable_bool_or()
317 ->mutable_literals()
318 ->Add(temp_literals_.begin(), temp_literals_.end());
319 return true;
320}
321
322bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) {
323 temp_literals_.clear();
324 for (const int lit : temp_enforcement_literals_) {
325 temp_literals_.push_back(NegatedRef(lit));
326 }
327 for (const int lit : ct.bool_or().literals()) {
328 if (context_->LiteralIsTrue(lit)) {
329 return true;
330 }
331 if (!context_->LiteralIsFalse(lit)) {
332 temp_literals_.push_back(lit);
333 }
334 }
335 return FinishBoolOrCopy();
336}
337
338bool ModelCopy::CopyBoolOrWithDupSupport(const ConstraintProto& ct,
339 ClauseId clause_id) {
340 temp_literals_.clear();
341 temp_literals_set_.clear();
342 for (const int enforcement_lit : temp_enforcement_literals_) {
343 // Having an enforcement literal is the same as having its negation on
344 // the clause.
345 const int lit = NegatedRef(enforcement_lit);
346
347 // Note that we already dealt with duplicate since we should have called
348 // PrepareEnforcementCopyWithDup() in this case.
349 temp_literals_set_.insert(lit);
350 temp_literals_.push_back(lit);
351 }
352 for (const int lit : ct.bool_or().literals()) {
353 if (context_->LiteralIsTrue(lit)) {
354 context_->UpdateRuleStats("bool_or: always true");
355 return true;
356 }
357 if (context_->LiteralIsFalse(lit)) continue;
358 if (temp_literals_set_.contains(NegatedRef(lit))) {
359 context_->UpdateRuleStats("bool_or: always true");
360 return true;
361 }
362 const auto [it, inserted] = temp_literals_set_.insert(lit);
363 if (inserted) temp_literals_.push_back(lit);
364 }
365 if (lrat_proof_handler_ != nullptr) {
366 // Add the original clause as a problem clause, and its simplified version
367 // as an inferred clause (only if it is different), with proof.
368 temp_clause_.clear();
369 for (const int lit : ct.enforcement_literal()) {
370 temp_clause_.push_back(RefToLiteral(lit).Negated());
371 }
372 for (const int lit : ct.bool_or().literals()) {
373 temp_clause_.push_back(RefToLiteral(lit));
374 }
375 lrat_proof_handler_->AddProblemClause(clause_id, temp_clause_);
376
377 if (temp_literals_set_.size() != temp_clause_.size()) {
378 temp_clause_ids_.clear();
379 for (const Literal lit : temp_clause_) {
380 if (!temp_literals_set_.contains(LiteralToRef(lit))) {
381 DCHECK(unit_clause_ids_.contains(lit.Negated())) << lit.Negated();
382 temp_clause_ids_.push_back(unit_clause_ids_[lit.Negated()]);
383 }
384 }
385 temp_clause_ids_.push_back(clause_id);
386 temp_simplified_clause_.clear();
387 for (const int lit : temp_literals_set_) {
388 temp_simplified_clause_.push_back(RefToLiteral(lit));
389 }
390 ClauseId new_clause_id = NextInferredClauseId();
391 lrat_proof_handler_->AddInferredClause(
392 new_clause_id, temp_simplified_clause_, temp_clause_ids_);
393 lrat_proof_handler_->DeleteClause(clause_id, temp_clause_);
394 clause_id = new_clause_id;
395 }
396 }
397 return FinishBoolOrCopy(clause_id);
398}
399
400bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) {
401 bool at_least_one_false = false;
402 int num_non_fixed_literals = 0;
403 for (const int lit : ct.bool_and().literals()) {
404 if (context_->LiteralIsFalse(lit)) {
405 at_least_one_false = true;
406 break;
407 }
408 if (!context_->LiteralIsTrue(lit)) {
409 num_non_fixed_literals++;
410 }
411 }
412
413 if (at_least_one_false) {
414 // One enforcement literal must be false.
415 BoolArgumentProto* bool_or =
416 context_->working_model->add_constraints()->mutable_bool_or();
417 for (const int lit : temp_enforcement_literals_) {
418 bool_or->add_literals(NegatedRef(lit));
419 }
420 return !bool_or->literals().empty();
421 } else if (num_non_fixed_literals > 0) {
422 ConstraintProto* new_ct = context_->working_model->add_constraints();
423 FinishEnforcementCopy(new_ct);
424 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
425 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
426 for (const int lit : ct.bool_and().literals()) {
427 if (context_->LiteralIsTrue(lit)) continue;
428 bool_and->add_literals(lit);
429 }
430 }
431 return true;
432}
433
434bool ModelCopy::CopyBoolAndWithDupSupport(const ConstraintProto& ct) {
435 DCHECK(!temp_enforcement_literals_.empty());
436
437 bool at_least_one_false = false;
438 temp_literals_.clear();
439 temp_literals_set_.clear();
440 for (const int lit : ct.bool_and().literals()) {
441 if (context_->LiteralIsFalse(lit)) {
442 context_->UpdateRuleStats("bool and: always false");
443 at_least_one_false = true;
444 break;
445 }
446 if (temp_literals_set_.contains(NegatedRef(lit))) {
447 context_->UpdateRuleStats("bool and: => x and not(x) ");
448 at_least_one_false = true;
449 break;
450 }
451 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
452 context_->UpdateRuleStats("bool and: not(x) => x");
453 at_least_one_false = true;
454 break;
455 }
456
457 if (context_->LiteralIsTrue(lit)) continue;
458 if (temp_enforcement_literals_set_.contains(lit)) {
459 context_->UpdateRuleStats("bool and: x => x");
460 continue;
461 }
462 const auto [it, inserted] = temp_literals_set_.insert(lit);
463 if (inserted) temp_literals_.push_back(lit);
464 }
465
466 if (at_least_one_false) {
467 // One enforcement literal must be false.
468 BoolArgumentProto* bool_or =
469 context_->working_model->add_constraints()->mutable_bool_or();
470 for (const int lit : temp_enforcement_literals_) {
471 bool_or->add_literals(NegatedRef(lit));
472 }
473 return !bool_or->literals().empty();
474 }
475
476 if (temp_literals_.empty()) {
477 context_->UpdateRuleStats("bool and: empty");
478 return true;
479 }
480
481 // Copy.
482 ConstraintProto* new_ct = context_->working_model->add_constraints();
483 FinishEnforcementCopy(new_ct);
484 new_ct->mutable_bool_and()->mutable_literals()->Add(temp_literals_.begin(),
485 temp_literals_.end());
486 return true;
487}
488
489bool ModelCopy::CopyLinearExpression(
491 absl::Span<const int> enforcement_literals) {
492 non_fixed_variables_.clear();
493 non_fixed_coefficients_.clear();
494 int64_t offset = expr.offset();
495 for (int i = 0; i < expr.vars_size(); ++i) {
496 const int ref = expr.vars(i);
497 const int64_t coeff = expr.coeffs(i);
498 if (coeff == 0) continue;
499 if (context_->IsFixed(ref)) {
500 offset += coeff * context_->MinOf(ref);
501 continue;
502 }
503
504 // Make sure we never have negative ref in a linear constraint.
505 if (RefIsPositive(ref)) {
506 non_fixed_variables_.push_back(ref);
507 non_fixed_coefficients_.push_back(coeff);
508 } else {
509 non_fixed_variables_.push_back(NegatedRef(ref));
510 non_fixed_coefficients_.push_back(-coeff);
511 }
512 }
513
514 dst->set_offset(offset);
515 dst->mutable_vars()->Add(non_fixed_variables_.begin(),
516 non_fixed_variables_.end());
517 dst->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
518 non_fixed_coefficients_.end());
519 // TODO(user): We could save work by only doing this if this is the first
520 // copy.
521 context_->CanonicalizeLinearExpression(enforcement_literals, dst);
522 return true;
523}
524
525bool ModelCopy::CopyLinear(const ConstraintProto& ct, bool canonicalize) {
526 non_fixed_variables_.clear();
527 non_fixed_coefficients_.clear();
528 int64_t offset = 0;
529 int64_t min_activity = 0;
530 int64_t max_activity = 0;
531 for (int i = 0; i < ct.linear().vars_size(); ++i) {
532 const int ref = ct.linear().vars(i);
533 const int64_t coeff = ct.linear().coeffs(i);
534 if (coeff == 0) continue;
535 if (context_->IsFixed(ref)) {
536 offset += coeff * context_->MinOf(ref);
537 continue;
538 }
539
540 if (coeff > 0) {
541 min_activity += coeff * context_->MinOf(ref);
542 max_activity += coeff * context_->MaxOf(ref);
543 } else {
544 min_activity += coeff * context_->MaxOf(ref);
545 max_activity += coeff * context_->MinOf(ref);
546 }
547
548 // Make sure we never have negative ref in a linear constraint.
549 if (RefIsPositive(ref)) {
550 non_fixed_variables_.push_back(ref);
551 non_fixed_coefficients_.push_back(coeff);
552 } else {
553 non_fixed_variables_.push_back(NegatedRef(ref));
554 non_fixed_coefficients_.push_back(-coeff);
555 }
556 }
557
558 const Domain implied(min_activity, max_activity);
559 const Domain new_rhs =
560 ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset));
561
562 // Trivial constraint?
563 if (implied.IsIncludedIn(new_rhs)) {
564 context_->UpdateRuleStats("linear: always true");
565 return true;
566 }
567
568 // Constraint is false?
569 const Domain tight_domain = implied.IntersectionWith(new_rhs);
570 if (tight_domain.IsEmpty()) {
571 if (ct.enforcement_literal().empty()) return false;
572 temp_literals_.clear();
573 for (const int literal : ct.enforcement_literal()) {
574 if (!context_->LiteralIsTrue(literal)) {
575 temp_literals_.push_back(NegatedRef(literal));
576 }
577 }
578 context_->working_model->add_constraints()
579 ->mutable_bool_or()
580 ->mutable_literals()
581 ->Add(temp_literals_.begin(), temp_literals_.end());
582 return !temp_literals_.empty();
583 }
584
585 DCHECK(!non_fixed_variables_.empty());
586
587 if (non_fixed_variables_.size() == 1 && ct.enforcement_literal().empty()) {
588 context_->UpdateRuleStats("linear1: x in domain");
589 return context_->IntersectDomainWith(
590 non_fixed_variables_[0],
591 new_rhs.InverseMultiplicationBy(non_fixed_coefficients_[0]));
592 }
593
594 ConstraintProto* new_ct = context_->working_model->add_constraints();
595 FinishEnforcementCopy(new_ct);
596 LinearConstraintProto* linear = new_ct->mutable_linear();
597 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
598 non_fixed_variables_.end());
599 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
600 non_fixed_coefficients_.end());
601 FillDomainInProto(tight_domain, linear);
602 if (canonicalize) {
603 context_->CanonicalizeLinearConstraint(new_ct);
604 // We checked if the constraint was trivial above, but canonicalization can
605 // make it trivial again by simplifying expressions like (x - x).
606 if (new_ct->linear().vars().empty() &&
607 ReadDomainFromProto(new_ct->linear()).Contains(0)) {
608 context_->UpdateRuleStats("linear: trivial 0=0");
609 context_->working_model->mutable_constraints()->RemoveLast();
610 return true;
611 }
612 }
613 return true;
614}
615
616bool ModelCopy::CopyElement(const ConstraintProto& ct) {
617 ConstraintProto* new_ct = context_->working_model->add_constraints();
618 if (ct.element().vars().empty() && !ct.element().exprs().empty()) {
619 // New format, just copy.
620 *new_ct = ct;
621 new_ct->mutable_enforcement_literal()->Clear();
622 FinishEnforcementCopy(new_ct);
623 return true;
624 }
625
626 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
627 if (context_->IsFixed(var)) {
628 expr->set_offset(context_->FixedValue(var));
629 } else {
630 DCHECK(RefIsPositive(var));
631 expr->mutable_vars()->Reserve(1);
632 expr->mutable_coeffs()->Reserve(1);
633 expr->add_vars(var);
634 expr->add_coeffs(1);
635 }
636 };
637
638 FinishEnforcementCopy(new_ct);
639 fill_expr(ct.element().index(),
640 new_ct->mutable_element()->mutable_linear_index());
641 fill_expr(ct.element().target(),
642 new_ct->mutable_element()->mutable_linear_target());
643 for (const int var : ct.element().vars()) {
644 fill_expr(var, new_ct->mutable_element()->add_exprs());
645 }
646 return true;
647}
648
649bool ModelCopy::CopyAutomaton(const ConstraintProto& ct) {
650 ConstraintProto* new_ct = context_->working_model->add_constraints();
651 new_ct->mutable_automaton()->set_starting_state(
652 ct.automaton().starting_state());
653 *new_ct->mutable_automaton()->mutable_final_states() =
654 ct.automaton().final_states();
655 *new_ct->mutable_automaton()->mutable_transition_tail() =
656 ct.automaton().transition_tail();
657 *new_ct->mutable_automaton()->mutable_transition_head() =
658 ct.automaton().transition_head();
659 *new_ct->mutable_automaton()->mutable_transition_label() =
660 ct.automaton().transition_label();
661 for (const LinearExpressionProto& expr : ct.automaton().exprs()) {
662 CopyLinearExpression(expr, new_ct->mutable_automaton()->add_exprs());
663 }
664 FinishEnforcementCopy(new_ct);
665
666 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
667 if (context_->IsFixed(var)) {
668 expr->set_offset(context_->FixedValue(var));
669 } else {
670 DCHECK(RefIsPositive(var));
671 expr->mutable_vars()->Reserve(1);
672 expr->mutable_coeffs()->Reserve(1);
673 expr->add_vars(var);
674 expr->add_coeffs(1);
675 }
676 };
677
678 for (const int var : ct.automaton().vars()) {
679 fill_expr(var, new_ct->mutable_automaton()->add_exprs());
680 }
681
682 return true;
683}
684
685bool ModelCopy::CopyTable(const ConstraintProto& ct) {
686 ConstraintProto* new_ct = context_->working_model->add_constraints();
687 if (ct.table().vars().empty() && !ct.table().exprs().empty()) {
688 // New format, just copy.
689 *new_ct = ct;
690 new_ct->mutable_enforcement_literal()->Clear();
691 FinishEnforcementCopy(new_ct);
692 return true;
693 }
694
695 auto fill_expr = [this](int var, LinearExpressionProto* expr) mutable {
696 if (context_->IsFixed(var)) {
697 expr->set_offset(context_->FixedValue(var));
698 } else {
699 DCHECK(RefIsPositive(var));
700 expr->mutable_vars()->Reserve(1);
701 expr->mutable_coeffs()->Reserve(1);
702 expr->add_vars(var);
703 expr->add_coeffs(1);
704 }
705 };
706
707 FinishEnforcementCopy(new_ct);
708 for (const int var : ct.table().vars()) {
709 fill_expr(var, new_ct->mutable_table()->add_exprs());
710 }
711 *new_ct->mutable_table()->mutable_values() = ct.table().values();
712 new_ct->mutable_table()->set_negated(ct.table().negated());
713 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
714
715 return true;
716}
717
718bool ModelCopy::CopyAllDiff(const ConstraintProto& ct) {
719 if (ct.all_diff().exprs().size() <= 1) return true;
720 ConstraintProto* new_ct = context_->working_model->add_constraints();
721 for (const LinearExpressionProto& expr : ct.all_diff().exprs()) {
722 CopyLinearExpression(expr, new_ct->mutable_all_diff()->add_exprs());
723 }
724 FinishEnforcementCopy(new_ct);
725 return true;
726}
727
728bool ModelCopy::CopyLinMax(const ConstraintProto& ct) {
729 // We will create it lazily if we end up copying something.
730 ConstraintProto* new_ct = nullptr;
731
732 // Regroup all constant terms and copy the other.
733 int64_t max_of_fixed_terms = std::numeric_limits<int64_t>::min();
734 for (const auto& expr : ct.lin_max().exprs()) {
735 const std::optional<int64_t> fixed = context_->FixedValueOrNullopt(expr);
736 if (fixed != std::nullopt) {
737 max_of_fixed_terms = std::max(max_of_fixed_terms, fixed.value());
738 } else {
739 // copy.
740 if (new_ct == nullptr) {
741 new_ct = context_->working_model->add_constraints();
742 }
743 CopyLinearExpression(expr, new_ct->mutable_lin_max()->add_exprs());
744 }
745 }
746
747 // If we have no non-fixed expression, we can just fix the target when it
748 // involve at most one variable.
749 if (new_ct == nullptr && ct.enforcement_literal().empty() &&
750 ct.lin_max().target().vars().size() <= 1) {
751 context_->UpdateRuleStats("lin_max: all exprs fixed during copy");
752 return context_->IntersectDomainWith(ct.lin_max().target(),
753 Domain(max_of_fixed_terms));
754 }
755
756 // Otherwise, add a constant term if needed.
757 if (max_of_fixed_terms > std::numeric_limits<int64_t>::min()) {
758 if (new_ct == nullptr) {
759 new_ct = context_->working_model->add_constraints();
760 }
761 new_ct->mutable_lin_max()->add_exprs()->set_offset(max_of_fixed_terms);
762 }
763
764 // Finish by copying the target.
765 if (new_ct == nullptr) return false; // No expr == unsat.
766 CopyLinearExpression(ct.lin_max().target(),
767 new_ct->mutable_lin_max()->mutable_target());
768 FinishEnforcementCopy(new_ct);
769 return true;
770}
771
772bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) {
773 if (!ct.enforcement_literal().empty()) {
774 ConstraintProto new_ct;
775 FinishEnforcementCopy(&new_ct);
776 LiteralsToLinear(ct.at_most_one().literals(), /*lb=*/0, /*ub=*/1,
777 new_ct.mutable_linear());
778 return CopyLinear(new_ct, true);
779 }
780 int num_true = 0;
781 temp_literals_.clear();
782 for (const int lit : ct.at_most_one().literals()) {
783 if (context_->LiteralIsFalse(lit)) continue;
784 temp_literals_.push_back(lit);
785 if (context_->LiteralIsTrue(lit)) num_true++;
786 }
787
788 if (temp_literals_.size() <= 1) return true;
789 if (num_true > 1) return false;
790
791 // TODO(user): presolve if num_true == 1.
792 ConstraintProto* new_ct = context_->working_model->add_constraints();
793 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
794 temp_literals_.end());
795 return true;
796}
797
798bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) {
799 if (!ct.enforcement_literal().empty()) {
800 ConstraintProto new_ct;
801 FinishEnforcementCopy(&new_ct);
802 LiteralsToLinear(ct.exactly_one().literals(), /*lb=*/1, /*ub=*/1,
803 new_ct.mutable_linear());
804 return CopyLinear(new_ct, true);
805 }
806 int num_true = 0;
807 temp_literals_.clear();
808 for (const int lit : ct.exactly_one().literals()) {
809 if (context_->LiteralIsFalse(lit)) continue;
810 temp_literals_.push_back(lit);
811 if (context_->LiteralIsTrue(lit)) num_true++;
812 }
813
814 if (temp_literals_.empty() || num_true > 1) return false;
815 if (temp_literals_.size() == 1 && num_true == 1) return true;
816
817 // TODO(user): presolve if num_true == 1 and not everything is false.
818 ConstraintProto* new_ct = context_->working_model->add_constraints();
819 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
820 temp_literals_.end());
821 return true;
822}
823
824bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c,
825 bool ignore_names) {
826 CHECK_EQ(starting_constraint_index_, 0)
827 << "Adding new interval constraints to partially filled model is not "
828 "supported.";
829 interval_mapping_[c] = context_->working_model->constraints_size();
830 ConstraintProto* new_ct = context_->working_model->add_constraints();
831 if (!ignore_names) {
832 new_ct->set_name(ct.name());
833 }
834 if (temp_enforcement_literals_.size() > 1) {
835 temp_enforcement_literals_ = {
836 GetOrCreateVariableForConjunction(&temp_enforcement_literals_)};
837 }
838 FinishEnforcementCopy(new_ct);
839 CopyLinearExpression(ct.interval().start(),
840 new_ct->mutable_interval()->mutable_start(),
841 ct.enforcement_literal());
842 CopyLinearExpression(ct.interval().size(),
843 new_ct->mutable_interval()->mutable_size(),
844 ct.enforcement_literal());
845 CopyLinearExpression(ct.interval().end(),
846 new_ct->mutable_interval()->mutable_end(),
847 ct.enforcement_literal());
848 return true;
849}
850
851bool ModelCopy::CopyIntProd(const ConstraintProto& ct, bool ignore_names) {
852 ConstraintProto* new_ct = context_->working_model->add_constraints();
853 if (!ignore_names) {
854 new_ct->set_name(ct.name());
855 }
856 FinishEnforcementCopy(new_ct);
857 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
858 CopyLinearExpression(expr, new_ct->mutable_int_prod()->add_exprs());
859 }
860 CopyLinearExpression(ct.int_prod().target(),
861 new_ct->mutable_int_prod()->mutable_target());
862 return true;
863}
864
865bool ModelCopy::CopyIntDiv(const ConstraintProto& ct, bool ignore_names) {
866 ConstraintProto* new_ct = context_->working_model->add_constraints();
867 if (!ignore_names) {
868 new_ct->set_name(ct.name());
869 }
870 FinishEnforcementCopy(new_ct);
871 for (const LinearExpressionProto& expr : ct.int_div().exprs()) {
872 CopyLinearExpression(expr, new_ct->mutable_int_div()->add_exprs());
873 }
874 CopyLinearExpression(ct.int_div().target(),
875 new_ct->mutable_int_div()->mutable_target());
876 return true;
877}
878
879bool ModelCopy::CopyIntMod(const ConstraintProto& ct, bool ignore_names) {
880 ConstraintProto* new_ct = context_->working_model->add_constraints();
881 if (!ignore_names) {
882 new_ct->set_name(ct.name());
883 }
884 FinishEnforcementCopy(new_ct);
885 for (const LinearExpressionProto& expr : ct.int_mod().exprs()) {
886 CopyLinearExpression(expr, new_ct->mutable_int_mod()->add_exprs());
887 }
888 CopyLinearExpression(ct.int_mod().target(),
889 new_ct->mutable_int_mod()->mutable_target());
890 return true;
891}
892
893bool ModelCopy::AddLinearConstraintForInterval(const ConstraintProto& ct) {
894 // Add the linear constraint enforcement => (start + size == end).
895 //
896 // We rely on the presolve for simplification, but deal with the trivial
897 // case of (start, offset, start + offset) here.
898 const IntervalConstraintProto& itv = ct.interval();
899 if (itv.size().vars().empty() &&
900 itv.start().offset() + itv.size().offset() == itv.end().offset() &&
901 absl::Span<const int>(itv.start().vars()) ==
902 absl::Span<const int>(itv.end().vars()) &&
903 absl::Span<const int64_t>(itv.start().coeffs()) ==
904 absl::Span<const int64_t>(itv.end().coeffs())) {
905 // Trivial constraint, nothing to do.
906 } else {
907 tmp_constraint_.Clear();
908 *tmp_constraint_.mutable_enforcement_literal() = ct.enforcement_literal();
909 LinearConstraintProto* mutable_linear = tmp_constraint_.mutable_linear();
910
911 mutable_linear->add_domain(0);
912 mutable_linear->add_domain(0);
913 AddLinearExpressionToLinearConstraint(itv.start(), 1, mutable_linear);
914 AddLinearExpressionToLinearConstraint(itv.size(), 1, mutable_linear);
915 AddLinearExpressionToLinearConstraint(itv.end(), -1, mutable_linear);
916 if (!CopyLinear(tmp_constraint_, true)) return false;
917 }
918
919 // An enforced interval must have its size non-negative.
920 const LinearExpressionProto& size_expr = itv.size();
921 if (context_->MinOf(size_expr) < 0) {
922 tmp_constraint_.Clear();
923 *tmp_constraint_.mutable_enforcement_literal() = ct.enforcement_literal();
924 *tmp_constraint_.mutable_linear()->mutable_vars() = size_expr.vars();
925 *tmp_constraint_.mutable_linear()->mutable_coeffs() = size_expr.coeffs();
926 tmp_constraint_.mutable_linear()->add_domain(-size_expr.offset());
927 tmp_constraint_.mutable_linear()->add_domain(
928 std::numeric_limits<int64_t>::max());
929 if (!CopyLinear(tmp_constraint_, true)) return false;
930 }
931
932 return true;
933}
934
935int ModelCopy::GetOrCreateVariableForConjunction(std::vector<int>* literals) {
936 std::sort(literals->begin(), literals->end());
937 auto it = boolean_product_encoding_.find(*literals);
938 if (it != boolean_product_encoding_.end()) return it->second;
939 const int new_var = context_->NewBoolVarWithConjunction(*literals);
940 boolean_product_encoding_[*literals] = new_var;
941 // Add the constraint 'literals => new_var'
942 auto* ct1 = context_->working_model->add_constraints();
943 ct1->mutable_bool_or()->mutable_literals()->Reserve(literals->size() + 1);
944 for (const int literal : *literals) {
945 ct1->mutable_bool_or()->add_literals(NegatedRef(literal));
946 }
947 ct1->mutable_bool_or()->add_literals(new_var);
948 // Add the constraint 'new_var => literals'
949 auto* ct2 = context_->working_model->add_constraints();
950 ct2->add_enforcement_literal(new_var);
951 *ct2->mutable_bool_and()->mutable_literals() = {literals->begin(),
952 literals->end()};
953 return new_var;
954}
955
956void ModelCopy::CopyAndMapNoOverlap(const ConstraintProto& ct) {
957 // Note that we don't copy names here.
958 auto* new_ct = context_->working_model->add_constraints();
959 FinishEnforcementCopy(new_ct);
960 NoOverlapConstraintProto* no_overlap = new_ct->mutable_no_overlap();
961 no_overlap->mutable_intervals()->Reserve(ct.no_overlap().intervals().size());
962 for (const int index : ct.no_overlap().intervals()) {
963 const int new_index = interval_mapping_[index];
964 if (new_index != -1) {
965 no_overlap->add_intervals(new_index);
966 }
967 }
968}
969
970void ModelCopy::CopyAndMapNoOverlap2D(const ConstraintProto& ct) {
971 // Note that we don't copy names here.
972 auto* new_ct = context_->working_model->add_constraints();
973 FinishEnforcementCopy(new_ct);
974 NoOverlap2DConstraintProto* no_overlap_2d = new_ct->mutable_no_overlap_2d();
975 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
976 no_overlap_2d->mutable_x_intervals()->Reserve(num_intervals);
977 no_overlap_2d->mutable_y_intervals()->Reserve(num_intervals);
978 for (int i = 0; i < num_intervals; ++i) {
979 const int new_x = interval_mapping_[ct.no_overlap_2d().x_intervals(i)];
980 if (new_x == -1) continue;
981 const int new_y = interval_mapping_[ct.no_overlap_2d().y_intervals(i)];
982 if (new_y == -1) continue;
983 no_overlap_2d->add_x_intervals(new_x);
984 no_overlap_2d->add_y_intervals(new_y);
985 }
986}
987
988bool ModelCopy::CopyAndMapCumulative(const ConstraintProto& ct) {
989 if (ct.cumulative().intervals().empty() &&
990 context_->IsFixed(ct.cumulative().capacity())) {
991 // Trivial constraint, either obviously SAT or UNSAT if enforced.
992 const int64_t capacity = context_->FixedValue(ct.cumulative().capacity());
993 if (temp_enforcement_literals_.empty()) {
994 return capacity >= 0;
995 }
996 if (capacity < 0) {
997 // At least one enforcement literal must be false.
998 auto* new_ct = context_->working_model->add_constraints();
999 for (const int literal : temp_enforcement_literals_) {
1000 new_ct->mutable_bool_or()->add_literals(NegatedRef(literal));
1001 }
1002 }
1003 return true;
1004 }
1005 // Note that we don't copy names here.
1006 auto* new_ct = context_->working_model->add_constraints();
1007 FinishEnforcementCopy(new_ct);
1008 CumulativeConstraintProto* cumulative = new_ct->mutable_cumulative();
1009 CopyLinearExpression(ct.cumulative().capacity(),
1010 cumulative->mutable_capacity());
1011
1012 const int num_intervals = ct.cumulative().intervals().size();
1013 cumulative->mutable_intervals()->Reserve(num_intervals);
1014 cumulative->mutable_demands()->Reserve(num_intervals);
1015 for (int i = 0; i < num_intervals; ++i) {
1016 const int new_index = interval_mapping_[ct.cumulative().intervals(i)];
1017 if (new_index != -1) {
1018 cumulative->add_intervals(new_index);
1019 CopyLinearExpression(ct.cumulative().demands(i),
1020 cumulative->add_demands());
1021 }
1022 }
1023
1024 return true;
1025}
1026
1027bool ModelCopy::CreateUnsatModel(int c, const ConstraintProto& ct) {
1028 context_->working_model->mutable_constraints()->Clear();
1029 context_->working_model->add_constraints()->mutable_bool_or();
1030
1031 // If the model was already marked as unsat, we keep the old message and just
1032 // return. TODO(user): Append messages instead?
1033 if (context_->ModelIsUnsat()) return false;
1034
1035 std::string proto_string;
1036#if !defined(__PORTABLE_PLATFORM__)
1037 google::protobuf::TextFormat::Printer printer;
1038 SetupTextFormatPrinter(&printer);
1039 printer.PrintToString(ct, &proto_string);
1040#endif // !defined(__PORTABLE_PLATFORM__)
1041 std::string message = absl::StrCat(
1042 "proven during initial copy of constraint #", c, ":\n", proto_string);
1043 std::vector<int> vars = UsedVariables(ct);
1044 if (vars.size() < 10) {
1045 absl::StrAppend(&message, "With current variable domains:\n");
1046 for (const int var : vars) {
1047 absl::StrAppend(&message, "var:", var,
1048 " domain:", context_->DomainOf(var).ToString(), "\n");
1049 }
1050 }
1051 return context_->NotifyThatModelIsUnsat(message);
1052}
1053
1054void ModelCopy::ExpandNonAffineExpressions() {
1055 // Make sure all domains are initialized (they are used in
1056 // MaybeExpandNonAffineExpression()).
1057 context_->InitializeNewDomains();
1058
1059 non_affine_expression_to_new_var_.clear();
1060 for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
1061 ConstraintProto* const ct = context_->working_model->mutable_constraints(c);
1062 switch (ct->constraint_case()) {
1064 MaybeExpandNonAffineExpressions(ct->mutable_int_div());
1065 break;
1067 MaybeExpandNonAffineExpressions(ct->mutable_int_mod());
1068 break;
1070 MaybeExpandNonAffineExpressions(ct->mutable_int_prod());
1071 break;
1073 for (LinearExpressionProto& expr :
1074 *ct->mutable_all_diff()->mutable_exprs()) {
1075 MaybeExpandNonAffineExpression(&expr);
1076 }
1077 break;
1079 if (!ct->element().exprs().empty()) {
1080 MaybeExpandNonAffineExpression(
1081 ct->mutable_element()->mutable_linear_index());
1082 MaybeExpandNonAffineExpression(
1083 ct->mutable_element()->mutable_linear_target());
1084 for (LinearExpressionProto& expr :
1085 *ct->mutable_element()->mutable_exprs()) {
1086 MaybeExpandNonAffineExpression(&expr);
1087 }
1088 }
1089 break;
1091 MaybeExpandNonAffineExpression(ct->mutable_interval()->mutable_start());
1092 MaybeExpandNonAffineExpression(ct->mutable_interval()->mutable_end());
1093 MaybeExpandNonAffineExpression(ct->mutable_interval()->mutable_size());
1094 break;
1096 for (LinearExpressionProto& expr :
1097 *ct->mutable_reservoir()->mutable_time_exprs()) {
1098 MaybeExpandNonAffineExpression(&expr);
1099 }
1100 break;
1103 *ct->mutable_routes()->mutable_dimensions()) {
1104 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
1105 MaybeExpandNonAffineExpression(&expr);
1106 }
1107 }
1108 break;
1110 for (LinearExpressionProto& expr :
1111 *ct->mutable_table()->mutable_exprs()) {
1112 MaybeExpandNonAffineExpression(&expr);
1113 }
1114 break;
1116 for (LinearExpressionProto& expr :
1117 *ct->mutable_automaton()->mutable_exprs()) {
1118 MaybeExpandNonAffineExpression(&expr);
1119 }
1120 break;
1121 default:
1122 break;
1123 }
1124 }
1125}
1126
1127// Replaces the expression sum a_i * x_i + c with gcd * y + c, where y is a new
1128// variable defined with an additional constraint y = sum a_i / gcd * x_i.
1129void ModelCopy::MaybeExpandNonAffineExpression(LinearExpressionProto* expr) {
1130 int new_size = 0;
1131 for (int i = 0; i < expr->vars().size(); ++i) {
1132 if (expr->coeffs(i) != 0) {
1133 expr->set_vars(new_size, expr->vars(i));
1134 expr->set_coeffs(new_size, expr->coeffs(i));
1135 ++new_size;
1136 }
1137 }
1138 expr->mutable_vars()->Truncate(new_size);
1139 expr->mutable_coeffs()->Truncate(new_size);
1140 if (expr->vars_size() < 2) return;
1141
1142 int64_t gcd = std::abs(expr->coeffs(0));
1143 for (int i = 1; i < expr->coeffs().size(); ++i) {
1144 gcd = std::gcd(gcd, std::abs(expr->coeffs(i)));
1145 }
1146 Domain domain(0);
1147 std::vector<std::pair<int, int64_t>> definition;
1148 for (int i = 0; i < expr->vars().size(); ++i) {
1149 const int var = expr->vars(i);
1150 const int64_t coeff = expr->coeffs(i) / gcd;
1151 domain =
1152 domain.AdditionWith(context_->DomainOf(var).MultiplicationBy(coeff));
1153 definition.push_back({var, coeff});
1154 }
1155 std::sort(definition.begin(), definition.end());
1156 int new_var;
1157 auto it = non_affine_expression_to_new_var_.find(definition);
1158 if (it != non_affine_expression_to_new_var_.end()) {
1159 new_var = it->second;
1160 } else {
1161 std::vector<std::pair<int, int64_t>> negated_definition;
1162 negated_definition.reserve(definition.size());
1163 for (const auto [var, coeff] : definition) {
1164 negated_definition.push_back({var, -coeff});
1165 }
1166 std::sort(negated_definition.begin(), negated_definition.end());
1167 it = non_affine_expression_to_new_var_.find(negated_definition);
1168 if (it != non_affine_expression_to_new_var_.end()) {
1169 new_var = it->second;
1170 gcd = -gcd;
1171 } else {
1172 new_var = context_->NewIntVar(domain);
1173 non_affine_expression_to_new_var_[definition] = new_var;
1174 auto* new_linear =
1175 context_->working_model->add_constraints()->mutable_linear();
1176 new_linear->add_vars(new_var);
1177 new_linear->add_coeffs(-1);
1178 for (const auto [var, coeff] : definition) {
1179 new_linear->add_vars(var);
1180 new_linear->add_coeffs(coeff);
1181 }
1182 new_linear->add_domain(0);
1183 new_linear->add_domain(0);
1184 context_->solution_crush().SetVarToLinearExpression(new_var, definition);
1185 }
1186 }
1187 expr->clear_vars();
1188 expr->clear_coeffs();
1189 expr->add_vars(new_var);
1190 expr->add_coeffs(gcd);
1191}
1192
1193void ModelCopy::MaybeExpandNonAffineExpressions(
1194 LinearArgumentProto* linear_argument) {
1195 MaybeExpandNonAffineExpression(linear_argument->mutable_target());
1196 for (LinearExpressionProto& expr : *linear_argument->mutable_exprs()) {
1197 MaybeExpandNonAffineExpression(&expr);
1198 }
1199}
1200
1202 const CpModelProto& in_model, PresolveContext* context,
1203 LratProofHandler* lrat_proof_handler) {
1204 ModelCopy copier(context, lrat_proof_handler);
1205 copier.ImportVariablesAndMaybeIgnoreNames(in_model);
1206 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/true)) {
1208 context);
1209 return true;
1210 }
1211 return !context->ModelIsUnsat();
1212}
1213
1215 const CpModelProto& in_model, absl::Span<const Domain> domains,
1216 std::function<bool(int)> active_constraints, PresolveContext* context,
1217 std::vector<int>* interval_mapping) {
1218 CHECK_EQ(domains.size(), in_model.variables_size());
1219 ModelCopy copier(context);
1220 copier.CreateVariablesFromDomains(domains);
1221 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/false,
1222 active_constraints)) {
1224 context);
1225 interval_mapping->assign(copier.InternalIntervalMapping().begin(),
1226 copier.InternalIntervalMapping().end());
1227 return true;
1228 }
1229 return !context->ModelIsUnsat();
1230}
1231
1233 const CpModelProto& in_model, PresolveContext* context) {
1234 if (!in_model.name().empty()) {
1235 context->working_model->set_name(in_model.name());
1236 }
1237 if (in_model.has_objective()) {
1238 *context->working_model->mutable_objective() = in_model.objective();
1239 }
1240 if (in_model.has_floating_point_objective()) {
1242 in_model.floating_point_objective();
1243 }
1244 if (!in_model.search_strategy().empty()) {
1245 // We make sure we do not use the old variables field.
1247 in_model.search_strategy();
1248 for (DecisionStrategyProto& strategy :
1250 google::protobuf::util::RemoveIf(strategy.mutable_exprs(),
1251 [](const LinearExpressionProto* expr) {
1252 return expr->vars().empty();
1253 });
1254 if (!strategy.variables().empty()) {
1255 CHECK(strategy.exprs().empty());
1256 for (const int ref : strategy.variables()) {
1257 LinearExpressionProto* expr = strategy.add_exprs();
1258 expr->add_vars(PositiveRef(ref));
1259 expr->add_coeffs(RefIsPositive(ref) ? 1 : -1);
1260 }
1261 strategy.clear_variables();
1262 }
1263 }
1264 }
1265 if (!in_model.assumptions().empty()) {
1266 *context->working_model->mutable_assumptions() = in_model.assumptions();
1267 }
1268 if (in_model.has_symmetry()) {
1269 *context->working_model->mutable_symmetry() = in_model.symmetry();
1270 }
1271 if (in_model.has_solution_hint()) {
1272 *context->working_model->mutable_solution_hint() = in_model.solution_hint();
1273
1274 // We make sure the hint is within the variables domain.
1275 //
1276 // This allows to avoid overflow because we know evaluating constraints on
1277 // the variables domains should be safe thanks to the initial validation.
1278 const int num_terms = in_model.solution_hint().vars().size();
1279 for (int i = 0; i < num_terms; ++i) {
1280 const int var = in_model.solution_hint().vars(i);
1281 const int64_t value = in_model.solution_hint().values(i);
1282 const Domain& domain = ReadDomainFromProto(in_model.variables(var));
1283 if (domain.IsEmpty()) continue; // UNSAT.
1284 const int64_t closest_domain_value = domain.ClosestValue(value);
1285 if (closest_domain_value != value) {
1286 context->UpdateRuleStats("hint: moved var hint within its domain");
1288 i, closest_domain_value);
1289 }
1290 }
1291 }
1292}
1293
1294} // namespace sat
1295} // 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()
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
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
::int32_t assumptions(int index) const
void set_name(Arg_ &&arg, Args_... args)
::operations_research::sat::SymmetryProto *PROTOBUF_NONNULL mutable_symmetry()
::operations_research::sat::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
::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
ModelCopy(PresolveContext *context, LratProofHandler *lrat_proof_handler=nullptr)
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
void CreateVariablesFromDomains(absl::Span< const Domain > domains)
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(std::string_view name, int num_times=1)
RoutesConstraintProto_NodeExpressions NodeExpressions
int RemoveIf(RepeatedPtrField< T > *array, const Pred &pr)
std::vector< int > UsedVariables(const ConstraintProto &ct)
void LiteralsToLinear(absl::Span< const int > literals, int64_t lb, int64_t ub, LinearConstraintProto *linear)
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context, LratProofHandler *lrat_proof_handler)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void SetupTextFormatPrinter(google::protobuf::TextFormat::Printer *printer)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
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)
OR-Tools root namespace.