OR-Tools  9.2
var_domination.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cstdint>
17#include <limits>
18
21
22namespace operations_research {
23namespace sat {
24
25void VarDomination::Reset(int num_variables) {
26 phase_ = 0;
27 num_vars_with_negation_ = 2 * num_variables;
28 partition_ = absl::make_unique<DynamicPartition>(num_vars_with_negation_);
29
30 can_freely_decrease_.assign(num_vars_with_negation_, true);
31
32 shared_buffer_.clear();
33 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
34
35 buffer_.clear();
36 dominating_vars_.assign(num_vars_with_negation_, IntegerVariableSpan());
37
38 ct_index_for_signature_ = 0;
39 block_down_signatures_.assign(num_vars_with_negation_, 0);
40}
41
42void VarDomination::RefinePartition(std::vector<int>* vars) {
43 if (vars->empty()) return;
44 partition_->Refine(*vars);
45 for (int& var : *vars) {
46 const IntegerVariable wrapped(var);
47 can_freely_decrease_[wrapped] = false;
48 can_freely_decrease_[NegationOf(wrapped)] = false;
49 var = NegationOf(wrapped).value();
50 }
51 partition_->Refine(*vars);
52}
53
54void VarDomination::CanOnlyDominateEachOther(absl::Span<const int> refs) {
55 if (phase_ != 0) return;
56 tmp_vars_.clear();
57 for (const int ref : refs) {
58 tmp_vars_.push_back(RefToIntegerVariable(ref).value());
59 }
60 RefinePartition(&tmp_vars_);
61 tmp_vars_.clear();
62}
63
64void VarDomination::ActivityShouldNotChange(absl::Span<const int> refs,
65 absl::Span<const int64_t> coeffs) {
66 if (phase_ != 0) return;
67 FillTempRanks(/*reverse_references=*/false, /*enforcements=*/{}, refs,
68 coeffs);
69 tmp_vars_.clear();
70 for (int i = 0; i < tmp_ranks_.size(); ++i) {
71 if (i > 0 && tmp_ranks_[i].rank != tmp_ranks_[i - 1].rank) {
72 RefinePartition(&tmp_vars_);
73 tmp_vars_.clear();
74 }
75 tmp_vars_.push_back(tmp_ranks_[i].var.value());
76 }
77 RefinePartition(&tmp_vars_);
78 tmp_vars_.clear();
79}
80
81// This correspond to a lower bounded constraint.
82void VarDomination::ProcessTempRanks() {
83 if (phase_ == 0) {
84 // We actually "split" tmp_ranks_ according to the current partition and
85 // process each resulting list independently for a faster algo.
86 ++ct_index_for_signature_;
87 for (IntegerVariableWithRank& entry : tmp_ranks_) {
88 can_freely_decrease_[entry.var] = false;
89 block_down_signatures_[entry.var] |= uint64_t{1}
90 << (ct_index_for_signature_ % 64);
91 entry.part = partition_->PartOf(entry.var.value());
92 }
93 std::stable_sort(
94 tmp_ranks_.begin(), tmp_ranks_.end(),
95 [](const IntegerVariableWithRank& a, const IntegerVariableWithRank& b) {
96 return a.part < b.part;
97 });
98 int start = 0;
99 for (int i = 1; i < tmp_ranks_.size(); ++i) {
100 if (tmp_ranks_[i].part != tmp_ranks_[start].part) {
101 Initialize({&tmp_ranks_[start], static_cast<size_t>(i - start)});
102 start = i;
103 }
104 }
105 if (start < tmp_ranks_.size()) {
106 Initialize({&tmp_ranks_[start], tmp_ranks_.size() - start});
107 }
108 } else if (phase_ == 1) {
109 FilterUsingTempRanks();
110 } else {
111 // This is only used for debugging, and we shouldn't reach here in prod.
112 CheckUsingTempRanks();
113 }
114}
115
117 absl::Span<const int> enforcements, absl::Span<const int> refs,
118 absl::Span<const int64_t> coeffs) {
119 FillTempRanks(/*reverse_references=*/false, enforcements, refs, coeffs);
120 ProcessTempRanks();
121}
122
124 absl::Span<const int> enforcements, absl::Span<const int> refs,
125 absl::Span<const int64_t> coeffs) {
126 FillTempRanks(/*reverse_references=*/true, enforcements, refs, coeffs);
127 ProcessTempRanks();
128}
129
130void VarDomination::MakeRankEqualToStartOfPart(
131 absl::Span<IntegerVariableWithRank> span) {
132 const int size = span.size();
133 int start = 0;
134 int previous_value = 0;
135 for (int i = 0; i < size; ++i) {
136 const int64_t value = span[i].rank;
137 if (value != previous_value) {
138 previous_value = value;
139 start = i;
140 }
141 span[i].rank = start;
142 }
143}
144
145void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
146 // The rank can be wrong and need to be recomputed because of how we splitted
147 // tmp_ranks_ into spans.
148 MakeRankEqualToStartOfPart(span);
149
150 const int future_start = shared_buffer_.size();
151 int first_start = -1;
152
153 // This is mainly to avoid corner case and hopefully, it should be big enough
154 // to not matter too much.
155 const int kSizeThreshold = 1000;
156 const int size = span.size();
157 for (int i = std::max(0, size - kSizeThreshold); i < size; ++i) {
158 const IntegerVariableWithRank entry = span[i];
159 const int num_candidates = size - entry.rank;
160 if (num_candidates >= kSizeThreshold) continue;
161
162 // Compute size to beat.
163 int size_threshold = kSizeThreshold;
164
165 // Take into account the current partition size.
166 const int var_part = partition_->PartOf(entry.var.value());
167 const int part_size = partition_->SizeOfPart(var_part);
168 size_threshold = std::min(size_threshold, part_size);
169
170 // Take into account our current best candidate if there is one.
171 const int current_num_candidates = initial_candidates_[entry.var].size;
172 if (current_num_candidates != 0) {
173 size_threshold = std::min(size_threshold, current_num_candidates);
174 }
175
176 if (num_candidates < size_threshold) {
177 if (first_start == -1) first_start = entry.rank;
178 initial_candidates_[entry.var] = {
179 future_start - first_start + static_cast<int>(entry.rank),
180 num_candidates};
181 }
182 }
183
184 // Only store what is necessary.
185 if (first_start == -1) return;
186 for (int i = first_start; i < size; ++i) {
187 shared_buffer_.push_back(span[i].var);
188 }
189}
190
191// TODO(user): Use more heuristics to not miss as much dominance relation when
192// we crop initial lists.
194 CHECK_EQ(phase_, 0);
195 phase_ = 1;
196
197 // Some initial lists ar too long and will be cropped to this size.
198 // We will handle them slightly differently.
199 //
200 // TODO(user): Tune the initial size, 50 might be a bit large, since our
201 // complexity is borned by this number times the number of entries in the
202 // constraints. Still we should in most situation be a lot lower than that.
203 const int kMaxInitialSize = 50;
204 std::vector<IntegerVariable> cropped_lists;
205 absl::StrongVector<IntegerVariable, bool> is_cropped(num_vars_with_negation_,
206 false);
207
208 // Fill the initial domination candidates.
209 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
210 if (can_freely_decrease_[var]) continue;
211 const int part = partition_->PartOf(var.value());
212 const int part_size = partition_->SizeOfPart(part);
213
214 const int start = buffer_.size();
215 int new_size = 0;
216
217 const uint64_t var_sig = block_down_signatures_[var];
218 const uint64_t not_var_sig = block_down_signatures_[NegationOf(var)];
219 const int stored_size = initial_candidates_[var].size;
220 if (stored_size == 0 || part_size < stored_size) {
221 // We start with the partition part.
222 // Note that all constraint will be filtered again in the second pass.
223 int num_tested = 0;
224 for (const int value : partition_->ElementsInPart(part)) {
225 const IntegerVariable c = IntegerVariable(value);
226
227 // This is to limit the complexity to 1k * num_vars. We fill the list
228 // with dummy node so that the heuristic below will fill it with
229 // potential transpose candidates.
230 if (++num_tested > 1000) {
231 is_cropped[var] = true;
232 cropped_lists.push_back(var);
233 int extra = new_size;
234 while (extra < kMaxInitialSize) {
235 ++extra;
236 buffer_.push_back(kNoIntegerVariable);
237 }
238 break;
239 }
240 if (PositiveVariable(c) == PositiveVariable(var)) continue;
241 if (can_freely_decrease_[NegationOf(c)]) continue;
242 if (var_sig & ~block_down_signatures_[c]) continue; // !included.
243 if (block_down_signatures_[NegationOf(c)] & ~not_var_sig) continue;
244 ++new_size;
245 buffer_.push_back(c);
246
247 // We do not want too many candidates per variables.
248 // TODO(user): randomize?
249 if (new_size > kMaxInitialSize) {
250 is_cropped[var] = true;
251 cropped_lists.push_back(var);
252 break;
253 }
254 }
255 } else {
256 // Copy the one that are in the same partition_ part.
257 //
258 // TODO(user): This can be too long maybe? even if we have list of at
259 // most 1000 at this point, see InitializeUsingTempRanks().
260 for (const IntegerVariable c : InitialDominatingCandidates(var)) {
261 if (PositiveVariable(c) == PositiveVariable(var)) continue;
262 if (can_freely_decrease_[NegationOf(c)]) continue;
263 if (partition_->PartOf(c.value()) != part) continue;
264 if (var_sig & ~block_down_signatures_[c]) continue; // !included.
265 if (block_down_signatures_[NegationOf(c)] & ~not_var_sig) continue;
266 ++new_size;
267 buffer_.push_back(c);
268
269 // We do not want too many candidates per variables.
270 // TODO(user): randomize?
271 if (new_size > kMaxInitialSize) {
272 is_cropped[var] = true;
273 cropped_lists.push_back(var);
274 break;
275 }
276 }
277 }
278
279 dominating_vars_[var] = {start, new_size};
280 }
281
282 // Heuristic: To try not to remove domination relations corresponding to short
283 // lists during transposition (see EndSecondPhase()), we fill half of the
284 // cropped list with the transpose of the short list relations. This helps
285 // finding more relation in the presence of cropped lists.
286 for (const IntegerVariable var : cropped_lists) {
287 if (kMaxInitialSize / 2 < dominating_vars_[var].size) {
288 dominating_vars_[var].size = kMaxInitialSize / 2; // Restrict.
289 }
290 }
291 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
292 for (const IntegerVariable dom : DominatingVariables(var)) {
293 if (!is_cropped[NegationOf(dom)]) continue;
294 IntegerVariableSpan& s = dominating_vars_[NegationOf(dom)];
295 if (s.size >= kMaxInitialSize) continue;
296 buffer_[s.start + s.size++] = NegationOf(var);
297 }
298 }
299
300 // Remove any duplicates.
301 //
302 // TODO(user): Maybe we should do that with all lists in case the
303 // input function are called with duplicates too.
304 for (const IntegerVariable var : cropped_lists) {
305 if (!is_cropped[var]) continue;
306 IntegerVariableSpan& s = dominating_vars_[var];
307 std::sort(&buffer_[s.start], &buffer_[s.start + s.size]);
308 const auto p = std::unique(&buffer_[s.start], &buffer_[s.start + s.size]);
309 s.size = p - &buffer_[s.start];
310 }
311
312 // We no longer need the first phase memory.
313 VLOG(1) << "Num initial list that where cropped: " << cropped_lists.size();
314 VLOG(1) << "Shared buffer size: " << shared_buffer_.size();
315 VLOG(1) << "Buffer size: " << buffer_.size();
316 gtl::STLClearObject(&initial_candidates_);
317 gtl::STLClearObject(&shared_buffer_);
318}
319
321 CHECK_EQ(phase_, 1);
322 phase_ = 2;
323
324 // Perform intersection with transpose.
325 shared_buffer_.clear();
326 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
327
328 // Pass 1: count.
329 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
330 for (const IntegerVariable dom : DominatingVariables(var)) {
331 ++initial_candidates_[NegationOf(dom)].size;
332 }
333 }
334
335 // Pass 2: compute starts.
336 int start = 0;
337 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
338 initial_candidates_[var].start = start;
339 start += initial_candidates_[var].size;
340 initial_candidates_[var].size = 0;
341 }
342 shared_buffer_.resize(start);
343
344 // Pass 3: transpose.
345 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
346 for (const IntegerVariable dom : DominatingVariables(var)) {
347 IntegerVariableSpan& span = initial_candidates_[NegationOf(dom)];
348 shared_buffer_[span.start + span.size++] = NegationOf(var);
349 }
350 }
351
352 // Pass 4: intersect.
353 int num_removed = 0;
354 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
355 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
356 for (const IntegerVariable dom : InitialDominatingCandidates(var)) {
357 tmp_var_to_rank_[dom] = 1;
358 }
359
360 int new_size = 0;
361 IntegerVariableSpan& span = dominating_vars_[var];
362 for (const IntegerVariable dom : DominatingVariables(var)) {
363 if (tmp_var_to_rank_[dom] != 1) {
364 ++num_removed;
365 continue;
366 }
367 buffer_[span.start + new_size++] = dom;
368 }
369 span.size = new_size;
370
371 for (const IntegerVariable dom : InitialDominatingCandidates(var)) {
372 tmp_var_to_rank_[dom] = -1;
373 }
374 }
375
376 VLOG(1) << "Transpose removed " << num_removed;
377 gtl::STLClearObject(&initial_candidates_);
378 gtl::STLClearObject(&shared_buffer_);
379}
380
381void VarDomination::FillTempRanks(bool reverse_references,
382 absl::Span<const int> enforcements,
383 absl::Span<const int> refs,
384 absl::Span<const int64_t> coeffs) {
385 tmp_ranks_.clear();
386 if (coeffs.empty()) {
387 // Simple case: all coefficients are assumed to be the same.
388 for (const int ref : refs) {
389 const IntegerVariable var =
390 RefToIntegerVariable(reverse_references ? NegatedRef(ref) : ref);
391 tmp_ranks_.push_back({var, 0, 0});
392 }
393 } else {
394 // Complex case: different coefficients.
395 for (int i = 0; i < refs.size(); ++i) {
396 if (coeffs[i] == 0) continue;
397 const IntegerVariable var = RefToIntegerVariable(
398 reverse_references ? NegatedRef(refs[i]) : refs[i]);
399 if (coeffs[i] > 0) {
400 tmp_ranks_.push_back({var, 0, coeffs[i]});
401 } else {
402 tmp_ranks_.push_back({NegationOf(var), 0, -coeffs[i]});
403 }
404 }
405 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
406 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
407 }
408
409 // Add the enforcement last with a new rank. We add their negation since
410 // we want the activity to not decrease, and we want to allow any
411 // enforcement-- to dominate a variable in the constraint.
412 const int enforcement_rank = tmp_ranks_.size();
413 for (const int ref : enforcements) {
414 tmp_ranks_.push_back(
415 {RefToIntegerVariable(NegatedRef(ref)), 0, enforcement_rank});
416 }
417}
418
419// We take the intersection of the current dominating candidate with the
420// restriction imposed by the current content of tmp_ranks_.
421void VarDomination::FilterUsingTempRanks() {
422 // Expand ranks in temp vector.
423 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
424 for (const IntegerVariableWithRank entry : tmp_ranks_) {
425 tmp_var_to_rank_[entry.var] = entry.rank;
426 }
427
428 // The activity of the variable in tmp_rank must not decrease.
429 for (const IntegerVariableWithRank entry : tmp_ranks_) {
430 // The only variables that can be paired with a var-- in the constriants are
431 // the var++ in the constraints with the same rank or higher.
432 //
433 // Note that we only filter the var-- domination lists here, we do not
434 // remove the var-- appearing in all the lists corresponding to wrong var++.
435 // This is left to the tranpose operation in EndSecondPhase().
436 {
437 IntegerVariableSpan& span = dominating_vars_[entry.var];
438 if (span.size == 0) continue;
439 int new_size = 0;
440 for (const IntegerVariable candidate : DominatingVariables(entry.var)) {
441 if (tmp_var_to_rank_[candidate] < entry.rank) continue;
442 buffer_[span.start + new_size++] = candidate;
443 }
444 span.size = new_size;
445 }
446 }
447
448 // Reset temporary vector to all -1.
449 for (const IntegerVariableWithRank entry : tmp_ranks_) {
450 tmp_var_to_rank_[entry.var] = -1;
451 }
452}
453
454// Slow: This is for debugging only.
455void VarDomination::CheckUsingTempRanks() {
456 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
457 for (const IntegerVariableWithRank entry : tmp_ranks_) {
458 tmp_var_to_rank_[entry.var] = entry.rank;
459 }
460
461 // The activity of the variable in tmp_rank must not decrease.
462 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
463 const int var_rank = tmp_var_to_rank_[var];
464 const int negated_var_rank = tmp_var_to_rank_[NegationOf(var)];
465 for (const IntegerVariable dom : DominatingVariables(var)) {
466 CHECK(!can_freely_decrease_[NegationOf(dom)]);
467
468 // Doing X--, Y++ is compatible if the rank[X] <= rank[Y]. But we also
469 // need to check if doing Not(Y)-- is compatible with Not(X)++.
470 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
471 CHECK_LE(tmp_var_to_rank_[NegationOf(dom)], negated_var_rank);
472 }
473 }
474
475 for (const IntegerVariableWithRank entry : tmp_ranks_) {
476 tmp_var_to_rank_[entry.var] = -1;
477 }
478}
479
482}
483
484bool VarDomination::CanFreelyDecrease(IntegerVariable var) const {
485 return can_freely_decrease_[var];
486}
487
488absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
489 IntegerVariable var) const {
490 const IntegerVariableSpan span = initial_candidates_[var];
491 if (span.size == 0) return absl::Span<const IntegerVariable>();
492 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
493 span.size);
494}
495
496absl::Span<const IntegerVariable> VarDomination::DominatingVariables(
497 int ref) const {
499}
500
501absl::Span<const IntegerVariable> VarDomination::DominatingVariables(
502 IntegerVariable var) const {
503 const IntegerVariableSpan span = dominating_vars_[var];
504 if (span.size == 0) return absl::Span<const IntegerVariable>();
505 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
506}
507
508std::string VarDomination::DominationDebugString(IntegerVariable var) const {
509 const int ref = IntegerVariableToRef(var);
510 std::string result =
511 absl::StrCat(PositiveRef(ref), RefIsPositive(ref) ? "--" : "++", " : ");
512 for (const IntegerVariable dom : DominatingVariables(var)) {
513 const int dom_ref = IntegerVariableToRef(dom);
514 absl::StrAppend(&result, PositiveRef(dom_ref),
515 RefIsPositive(dom_ref) ? "++" : "--", " ");
516 }
517 return result;
518}
519
520// TODO(user): No need to set locking_ct_index_[var] if num_locks_[var] > 1
521void DualBoundStrengthening::CannotDecrease(absl::Span<const int> refs,
522 int ct_index) {
523 for (const int ref : refs) {
524 const IntegerVariable var = RefToIntegerVariable(ref);
525 can_freely_decrease_until_[var] = kMaxIntegerValue;
526 num_locks_[var]++;
527 locking_ct_index_[var] = ct_index;
528 }
529}
530
531void DualBoundStrengthening::CannotIncrease(absl::Span<const int> refs,
532 int ct_index) {
533 for (const int ref : refs) {
534 const IntegerVariable var = RefToIntegerVariable(ref);
535 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
536 num_locks_[NegationOf(var)]++;
537 locking_ct_index_[NegationOf(var)] = ct_index;
538 }
539}
540
541void DualBoundStrengthening::CannotMove(absl::Span<const int> refs) {
542 for (const int ref : refs) {
543 const IntegerVariable var = RefToIntegerVariable(ref);
544 can_freely_decrease_until_[var] = kMaxIntegerValue;
545 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
546 num_locks_[var]++;
547 num_locks_[NegationOf(var)]++;
548 }
549}
550
551template <typename LinearProto>
553 bool is_objective, const PresolveContext& context,
554 const LinearProto& linear, int64_t min_activity, int64_t max_activity) {
555 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
556 const int64_t ub_limit = linear.domain(1);
557 const int num_terms = linear.vars_size();
558 for (int i = 0; i < num_terms; ++i) {
559 int ref = linear.vars(i);
560 int64_t coeff = linear.coeffs(i);
561 if (coeff < 0) {
562 ref = NegatedRef(ref);
563 coeff = -coeff;
564 }
565
566 const int64_t min_term = coeff * context.MinOf(ref);
567 const int64_t max_term = coeff * context.MaxOf(ref);
568 const int64_t term_diff = max_term - min_term;
569 const IntegerVariable var = RefToIntegerVariable(ref);
570
571 // lb side.
572 if (min_activity < lb_limit) {
573 num_locks_[var]++;
574 if (min_activity + term_diff < lb_limit) {
575 can_freely_decrease_until_[var] = kMaxIntegerValue;
576 } else {
577 const IntegerValue slack(lb_limit - min_activity);
578 const IntegerValue var_diff =
579 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
580 can_freely_decrease_until_[var] =
581 std::max(can_freely_decrease_until_[var],
582 IntegerValue(context.MinOf(ref)) + var_diff);
583 }
584 }
585
586 if (is_objective) {
587 // We never want to increase the objective value. Note that if the
588 // objective is lower bounded, we checked that on the lb side above.
589 num_locks_[NegationOf(var)]++;
590 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
591 continue;
592 }
593
594 // ub side.
595 if (max_activity > ub_limit) {
596 num_locks_[NegationOf(var)]++;
597 if (max_activity - term_diff > ub_limit) {
598 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
599 } else {
600 const IntegerValue slack(max_activity - ub_limit);
601 const IntegerValue var_diff =
602 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
603 can_freely_decrease_until_[NegationOf(var)] =
604 std::max(can_freely_decrease_until_[NegationOf(var)],
605 -IntegerValue(context.MaxOf(ref)) + var_diff);
606 }
607 }
608 }
609}
610
612 const CpModelProto& cp_model = *context->working_model;
613 const int num_vars = cp_model.variables_size();
614 for (int var = 0; var < num_vars; ++var) {
615 if (context->IsFixed(var)) continue;
616
617 // Fix to lb?
618 const int64_t lb = context->MinOf(var);
619 const int64_t ub_limit = std::max(lb, CanFreelyDecreaseUntil(var));
620 if (ub_limit == lb) {
621 context->UpdateRuleStats("dual: fix variable");
622 CHECK(context->IntersectDomainWith(var, Domain(lb)));
623 continue;
624 }
625
626 // Fix to ub?
627 const int64_t ub = context->MaxOf(var);
628 const int64_t lb_limit =
630 if (lb_limit == ub) {
631 context->UpdateRuleStats("dual: fix variable");
632 CHECK(context->IntersectDomainWith(var, Domain(ub)));
633 continue;
634 }
635
636 // Here we can fix to any value in [ub_limit, lb_limit] that is compatible
637 // with the current domain. We prefer zero or the lowest possible magnitude.
638 if (lb_limit > ub_limit) {
639 const Domain domain =
640 context->DomainOf(var).IntersectionWith(Domain(ub_limit, lb_limit));
641 if (!domain.IsEmpty()) {
642 int64_t value = domain.Contains(0) ? 0 : domain.Min();
643 if (value != 0) {
644 for (const int64_t bound : domain.FlattenedIntervals()) {
645 if (std::abs(bound) < std::abs(value)) value = bound;
646 }
647 }
648 context->UpdateRuleStats("dual: fix variable with multiple choices");
649 CHECK(context->IntersectDomainWith(var, Domain(value)));
650 continue;
651 }
652 }
653
654 // Here we can reduce the domain, but we must be careful when the domain
655 // has holes.
656 if (lb_limit > lb || ub_limit < ub) {
657 const int64_t new_ub =
658 ub_limit < ub
659 ? context->DomainOf(var)
660 .IntersectionWith(
662 .Min()
663 : ub;
664 const int64_t new_lb =
665 lb_limit > lb
666 ? context->DomainOf(var)
667 .IntersectionWith(
669 .Max()
670 : lb;
671 context->UpdateRuleStats("dual: reduced domain");
672 CHECK(context->IntersectDomainWith(var, Domain(new_lb, new_ub)));
673 }
674 }
675
676 // If (a => b) is the only constraint blocking a literal a in the up
677 // direction, then we can set a == b !
678 //
679 // TODO(user): We can deal with more general situation. For instance an at
680 // most one that is the only blocking constraint can become an exactly one.
681 std::vector<bool> processed(num_vars, false);
682 for (int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
683 if (processed[positive_ref]) continue;
684 if (context->IsFixed(positive_ref)) continue;
685 const IntegerVariable var = RefToIntegerVariable(positive_ref);
686 int ct_index = -1;
687 if (num_locks_[var] == 1 && locking_ct_index_[var] != -1) {
688 ct_index = locking_ct_index_[var];
689 } else if (num_locks_[NegationOf(var)] == 1 &&
690 locking_ct_index_[NegationOf(var)] != -1) {
691 ct_index = locking_ct_index_[NegationOf(var)];
692 } else {
693 continue;
694 }
695 const ConstraintProto& ct = context->working_model->constraints(ct_index);
696 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
697 context->UpdateRuleStats("TODO dual: tighten at most one");
698 continue;
699 }
700
701 if (ct.constraint_case() != ConstraintProto::kBoolAnd) continue;
702 if (ct.enforcement_literal().size() != 1) continue;
703
704 // Recover a => b where a is having an unique up_lock (i.e this constraint).
705 // Note that if many implications are encoded in the same bool_and, we have
706 // to be careful that a is appearing in just one of them.
707 int a = ct.enforcement_literal(0);
708 int b = 1;
709 if (PositiveRef(a) == positive_ref &&
710 num_locks_[RefToIntegerVariable(NegatedRef(a))] == 1) {
711 // Here, we can only add the equivalence if the literal is the only
712 // on the lhs, otherwise there is actually more lock.
713 if (ct.bool_and().literals().size() != 1) continue;
714 b = ct.bool_and().literals(0);
715 } else {
716 bool found = false;
717 b = NegatedRef(ct.enforcement_literal(0));
718 for (const int lhs : ct.bool_and().literals()) {
719 if (PositiveRef(lhs) == positive_ref &&
720 num_locks_[RefToIntegerVariable(lhs)] == 1) {
721 found = true;
722 a = NegatedRef(lhs);
723 break;
724 }
725 }
726 CHECK(found);
727 }
728 CHECK_EQ(num_locks_[RefToIntegerVariable(NegatedRef(a))], 1);
729
730 processed[PositiveRef(a)] = true;
731 processed[PositiveRef(b)] = true;
732 context->StoreBooleanEqualityRelation(a, b);
733 context->UpdateRuleStats("dual: enforced equivalence");
734 }
735
736 return true;
737}
738
739namespace {
740
741// TODO(user): Maybe we should avoid recomputing that here.
742template <typename LinearExprProto>
743void FillMinMaxActivity(const PresolveContext& context,
744 const LinearExprProto& proto, int64_t* min_activity,
745 int64_t* max_activity) {
746 *min_activity = 0;
747 *max_activity = 0;
748 const int num_vars = proto.vars().size();
749 for (int i = 0; i < num_vars; ++i) {
750 const int64_t a = proto.coeffs(i) * context.MinOf(proto.vars(i));
751 const int64_t b = proto.coeffs(i) * context.MaxOf(proto.vars(i));
752 *min_activity += std::min(a, b);
753 *max_activity += std::max(a, b);
754 }
755}
756
757} // namespace
758
760 const PresolveContext& context, VarDomination* var_domination,
761 DualBoundStrengthening* dual_bound_strengthening) {
762 const CpModelProto& cp_model = *context.working_model;
763 const int num_vars = cp_model.variables().size();
764 var_domination->Reset(num_vars);
765 dual_bound_strengthening->Reset(num_vars);
766
767 int64_t min_activity = std::numeric_limits<int64_t>::min();
768 int64_t max_activity = std::numeric_limits<int64_t>::max();
769
770 for (int var = 0; var < num_vars; ++var) {
771 // Deal with the affine relations that are not part of the proto.
772 // Those only need to be processed in the first pass.
773 //
774 // TODO(user): This is not ideal since if only the representative is still
775 // used, we shouldn't restrict any dominance relation involving it.
776 const AffineRelation::Relation r = context.GetAffineRelation(var);
777 if (r.representative != var) {
778 dual_bound_strengthening->CannotMove({var, r.representative});
779 if (r.coeff == 1) {
780 var_domination->CanOnlyDominateEachOther(
782 } else if (r.coeff == -1) {
783 var_domination->CanOnlyDominateEachOther({var, r.representative});
784 } else {
785 var_domination->CanOnlyDominateEachOther({var});
786 var_domination->CanOnlyDominateEachOther({r.representative});
787 }
788 }
789
790 // Also ignore variables that have been substitued already or are unused.
791 if (context.IsFixed(var) || context.VariableWasRemoved(var) ||
792 context.VariableIsNotUsedAnymore(var)) {
793 dual_bound_strengthening->CannotMove({var});
794 var_domination->CanOnlyDominateEachOther({var});
795 }
796 }
797
798 // TODO(user): Benchmark and experiment with 3 phases algo:
799 // - Only ActivityShouldNotChange()/CanOnlyDominateEachOther().
800 // - The other cases once.
801 // - EndFirstPhase() and then the other cases a second time.
802 std::vector<int> tmp;
803 const int num_constraints = cp_model.constraints_size();
804 for (int phase = 0; phase < 2; phase++) {
805 for (int c = 0; c < num_constraints; ++c) {
806 const ConstraintProto& ct = cp_model.constraints(c);
807 if (phase == 0) {
808 dual_bound_strengthening->CannotIncrease(ct.enforcement_literal(), c);
809 }
810 switch (ct.constraint_case()) {
812 if (phase == 0) {
813 dual_bound_strengthening->CannotDecrease(ct.bool_or().literals());
814 }
815 var_domination->ActivityShouldNotDecrease(ct.enforcement_literal(),
816 ct.bool_or().literals(),
817 /*coeffs=*/{});
818 break;
820 if (phase == 0) {
821 dual_bound_strengthening->CannotDecrease(ct.bool_and().literals(),
822 c);
823 }
824
825 // We process it like n clauses.
826 //
827 // TODO(user): the way we process that is a bit restrictive. By
828 // working on the implication graph we could detect more dominance
829 // relations. Since if a => b we say that a++ can only be paired with
830 // b--, but it could actually be paired with any variables that when
831 // dereased implies b = 0. This is a bit mitigated by the fact that
832 // we regroup when we can such implications into big at most ones.
833 tmp.clear();
834 for (const int ref : ct.enforcement_literal()) {
835 tmp.push_back(NegatedRef(ref));
836 }
837 for (const int ref : ct.bool_and().literals()) {
838 tmp.push_back(ref);
839 var_domination->ActivityShouldNotDecrease(/*enforcements=*/{}, tmp,
840 /*coeffs=*/{});
841 tmp.pop_back();
842 }
843 break;
845 if (phase == 0) {
846 dual_bound_strengthening->CannotIncrease(
847 ct.at_most_one().literals(), c);
848 }
849 var_domination->ActivityShouldNotIncrease(ct.enforcement_literal(),
850 ct.at_most_one().literals(),
851 /*coeffs=*/{});
852 break;
854 if (phase == 0) {
855 dual_bound_strengthening->CannotMove(ct.exactly_one().literals());
856 }
857 var_domination->ActivityShouldNotChange(ct.exactly_one().literals(),
858 /*coeffs=*/{});
859 break;
861 FillMinMaxActivity(context, ct.linear(), &min_activity,
862 &max_activity);
863 if (phase == 0) {
864 dual_bound_strengthening->ProcessLinearConstraint(
865 false, context, ct.linear(), min_activity, max_activity);
866 }
867 const bool domain_is_simple = ct.linear().domain().size() == 2;
868 const bool free_to_increase =
869 domain_is_simple && ct.linear().domain(1) >= max_activity;
870 const bool free_to_decrease =
871 domain_is_simple && ct.linear().domain(0) <= min_activity;
872 if (free_to_decrease && free_to_increase) break;
873 if (free_to_increase) {
874 var_domination->ActivityShouldNotDecrease(ct.enforcement_literal(),
875 ct.linear().vars(),
876 ct.linear().coeffs());
877 } else if (free_to_decrease) {
878 var_domination->ActivityShouldNotIncrease(ct.enforcement_literal(),
879 ct.linear().vars(),
880 ct.linear().coeffs());
881 } else {
882 // TODO(user): Handle enforcement better here.
883 if (!ct.enforcement_literal().empty()) {
884 var_domination->ActivityShouldNotIncrease(
885 /*enforcements=*/{}, ct.enforcement_literal(), /*coeffs=*/{});
886 }
887 var_domination->ActivityShouldNotChange(ct.linear().vars(),
888 ct.linear().coeffs());
889 }
890 break;
891 }
892 default:
893 // We cannot infer anything if we don't know the constraint.
894 // TODO(user): Handle enforcement better here.
895 if (phase == 0) {
896 dual_bound_strengthening->CannotMove(context.ConstraintToVars(c));
897 }
898 for (const int var : context.ConstraintToVars(c)) {
899 var_domination->CanOnlyDominateEachOther({var});
900 }
901 break;
902 }
903 }
904
905 // The objective is handled like a <= constraints, or an == constraint if
906 // there is a non-trivial domain.
907 if (cp_model.has_objective()) {
908 // WARNING: The proto objective might not be up to date, so we need to
909 // write it first.
910 if (phase == 0) {
911 context.WriteObjectiveToProto();
912 }
913 FillMinMaxActivity(context, cp_model.objective(), &min_activity,
914 &max_activity);
915 if (phase == 0) {
916 dual_bound_strengthening->ProcessLinearConstraint(
917 true, context, cp_model.objective(), min_activity, max_activity);
918 }
919 const auto& domain = cp_model.objective().domain();
920 if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
921 var_domination->ActivityShouldNotIncrease(
922 /*enforcements=*/{}, cp_model.objective().vars(),
923 cp_model.objective().coeffs());
924 } else {
925 var_domination->ActivityShouldNotChange(cp_model.objective().vars(),
926 cp_model.objective().coeffs());
927 }
928 }
929
930 if (phase == 0) var_domination->EndFirstPhase();
931 if (phase == 1) var_domination->EndSecondPhase();
932 }
933
934 // Some statistics.
935 int64_t num_unconstrained_refs = 0;
936 int64_t num_dominated_refs = 0;
937 int64_t num_dominance_relations = 0;
938 for (int var = 0; var < num_vars; ++var) {
939 if (context.IsFixed(var)) continue;
940
941 for (const int ref : {var, NegatedRef(var)}) {
942 if (var_domination->CanFreelyDecrease(ref)) {
943 num_unconstrained_refs++;
944 } else if (!var_domination->DominatingVariables(ref).empty()) {
945 num_dominated_refs++;
946 num_dominance_relations +=
947 var_domination->DominatingVariables(ref).size();
948 }
949 }
950 }
951 if (num_unconstrained_refs == 0 && num_dominated_refs == 0) return;
952 VLOG(1) << "Dominance:"
953 << " num_unconstrained_refs=" << num_unconstrained_refs
954 << " num_dominated_refs=" << num_dominated_refs
955 << " num_dominance_relations=" << num_dominance_relations;
956}
957
958bool ExploitDominanceRelations(const VarDomination& var_domination,
960 const CpModelProto& cp_model = *context->working_model;
961 const int num_vars = cp_model.variables_size();
962
963 // Abort early if there is nothing to do.
964 bool work_to_do = false;
965 for (int var = 0; var < num_vars; ++var) {
966 if (context->IsFixed(var)) continue;
967 if (!var_domination.DominatingVariables(var).empty() ||
968 !var_domination.DominatingVariables(NegatedRef(var)).empty()) {
969 work_to_do = true;
970 break;
971 }
972 }
973 if (!work_to_do) return true;
974
975 absl::StrongVector<IntegerVariable, int64_t> var_lb_to_ub_diff(num_vars * 2,
976 0);
977 absl::StrongVector<IntegerVariable, bool> in_constraints(num_vars * 2, false);
978
979 const int num_constraints = cp_model.constraints_size();
980 for (int c = 0; c < num_constraints; ++c) {
981 const ConstraintProto& ct = cp_model.constraints(c);
982
983 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
984 if (ct.enforcement_literal().size() != 1) continue;
985 const int a = ct.enforcement_literal(0);
986 if (context->IsFixed(a)) continue;
987 for (const int b : ct.bool_and().literals()) {
988 if (context->IsFixed(b)) continue;
989
990 // If (a--, b--) is valid, we can always set a to false.
991 for (const IntegerVariable ivar :
992 var_domination.DominatingVariables(a)) {
993 const int ref = VarDomination::IntegerVariableToRef(ivar);
994 if (ref == NegatedRef(b)) {
995 context->UpdateRuleStats("domination: in implication");
996 if (!context->SetLiteralToFalse(a)) return false;
997 break;
998 }
999 }
1000 if (context->IsFixed(a)) break;
1001
1002 // If (b++, a++) is valid, then we can always set b to true.
1003 for (const IntegerVariable ivar :
1004 var_domination.DominatingVariables(NegatedRef(b))) {
1005 const int ref = VarDomination::IntegerVariableToRef(ivar);
1006 if (ref == a) {
1007 context->UpdateRuleStats("domination: in implication");
1008 if (!context->SetLiteralToTrue(b)) return false;
1009 break;
1010 }
1011 }
1012 }
1013 continue;
1014 }
1015
1016 if (!ct.enforcement_literal().empty()) continue;
1017
1018 // TODO(user): Also deal with exactly one.
1019 // TODO(user): More generally, combine with probing? if a dominated variable
1020 // implies one of its dominant to zero, then it can be set to zero. It seems
1021 // adding the implication below should have the same effect? but currently
1022 // it requires a lot of presolve rounds.
1023 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
1024 for (const int ref : ct.at_most_one().literals()) {
1025 in_constraints[VarDomination::RefToIntegerVariable(ref)] = true;
1026 }
1027 for (const int ref : ct.at_most_one().literals()) {
1028 if (context->IsFixed(ref)) continue;
1029
1030 const auto dominating_ivars = var_domination.DominatingVariables(ref);
1031 if (dominating_ivars.empty()) continue;
1032 for (const IntegerVariable ivar : dominating_ivars) {
1033 if (!in_constraints[ivar]) continue;
1034 if (context->IsFixed(VarDomination::IntegerVariableToRef(ivar))) {
1035 continue;
1036 }
1037
1038 // We can set the dominated variable to false.
1039 context->UpdateRuleStats("domination: in at most one");
1040 if (!context->SetLiteralToFalse(ref)) return false;
1041 break;
1042 }
1043 }
1044 for (const int ref : ct.at_most_one().literals()) {
1045 in_constraints[VarDomination::RefToIntegerVariable(ref)] = false;
1046 }
1047 }
1048
1049 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
1050
1051 int num_dominated = 0;
1052 for (const int var : context->ConstraintToVars(c)) {
1053 if (!var_domination.DominatingVariables(var).empty()) ++num_dominated;
1054 if (!var_domination.DominatingVariables(NegatedRef(var)).empty()) {
1055 ++num_dominated;
1056 }
1057 }
1058 if (num_dominated == 0) continue;
1059
1060 // Precompute.
1061 int64_t min_activity = 0;
1062 int64_t max_activity = 0;
1063 const int num_terms = ct.linear().vars_size();
1064 for (int i = 0; i < num_terms; ++i) {
1065 int ref = ct.linear().vars(i);
1066 int64_t coeff = ct.linear().coeffs(i);
1067 if (coeff < 0) {
1068 ref = NegatedRef(ref);
1069 coeff = -coeff;
1070 }
1071 const int64_t min_term = coeff * context->MinOf(ref);
1072 const int64_t max_term = coeff * context->MaxOf(ref);
1073 min_activity += min_term;
1074 max_activity += max_term;
1075 const IntegerVariable ivar = VarDomination::RefToIntegerVariable(ref);
1076 var_lb_to_ub_diff[ivar] = max_term - min_term;
1077 var_lb_to_ub_diff[NegationOf(ivar)] = min_term - max_term;
1078 }
1079 const int64_t rhs_lb = ct.linear().domain(0);
1080 const int64_t rhs_ub = ct.linear().domain(ct.linear().domain_size() - 1);
1081 if (max_activity < rhs_lb || min_activity > rhs_ub) {
1082 return context->NotifyThatModelIsUnsat("linear equation unsat.");
1083 }
1084
1085 // Look for dominated var.
1086 for (int i = 0; i < num_terms; ++i) {
1087 const int ref = ct.linear().vars(i);
1088 const int64_t coeff = ct.linear().coeffs(i);
1089 const int64_t coeff_magnitude = std::abs(coeff);
1090 if (context->IsFixed(ref)) continue;
1091
1092 for (const int current_ref : {ref, NegatedRef(ref)}) {
1093 const absl::Span<const IntegerVariable> dominated_by =
1094 var_domination.DominatingVariables(current_ref);
1095 if (dominated_by.empty()) continue;
1096
1097 const bool ub_side = (coeff > 0) == (current_ref == ref);
1098 if (ub_side) {
1099 if (max_activity <= rhs_ub) continue;
1100 } else {
1101 if (min_activity >= rhs_lb) continue;
1102 }
1103 const int64_t slack =
1104 ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1105
1106 // Compute the delta in activity if all dominating var moves to their
1107 // other bound.
1108 int64_t delta = 0;
1109 for (const IntegerVariable ivar : dominated_by) {
1110 if (ub_side) {
1111 delta += std::max(int64_t{0}, var_lb_to_ub_diff[ivar]);
1112 } else {
1113 delta += std::max(int64_t{0}, -var_lb_to_ub_diff[ivar]);
1114 }
1115 }
1116
1117 const int64_t lb = context->MinOf(current_ref);
1118 if (delta + coeff_magnitude > slack) {
1119 context->UpdateRuleStats("domination: fixed to lb.");
1120 if (!context->IntersectDomainWith(current_ref, Domain(lb))) {
1121 return false;
1122 }
1123
1124 // We need to update the precomputed quantities.
1125 const IntegerVariable current_var =
1127 if (ub_side) {
1128 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1129 max_activity -= var_lb_to_ub_diff[current_var];
1130 } else {
1131 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1132 min_activity -= var_lb_to_ub_diff[current_var];
1133 }
1134 var_lb_to_ub_diff[current_var] = 0;
1135 var_lb_to_ub_diff[NegationOf(current_var)] = 0;
1136
1137 continue;
1138 }
1139
1140 const IntegerValue diff = FloorRatio(IntegerValue(slack - delta),
1141 IntegerValue(coeff_magnitude));
1142 int64_t new_ub = lb + diff.value();
1143 if (new_ub < context->MaxOf(current_ref)) {
1144 // Tricky: If there are holes, we can't just reduce the domain to
1145 // new_ub if it is not a valid value, so we need to compute the Min()
1146 // of the intersection.
1147 new_ub = context->DomainOf(current_ref)
1148 .IntersectionWith(
1150 .Min();
1151 }
1152 if (new_ub < context->MaxOf(current_ref)) {
1153 context->UpdateRuleStats("domination: reduced ub.");
1154 if (!context->IntersectDomainWith(current_ref, Domain(lb, new_ub))) {
1155 return false;
1156 }
1157
1158 // We need to update the precomputed quantities.
1159 const IntegerVariable current_var =
1161 if (ub_side) {
1162 CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1163 max_activity -= var_lb_to_ub_diff[current_var];
1164 } else {
1165 CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1166 min_activity -= var_lb_to_ub_diff[current_var];
1167 }
1168 const int64_t new_diff = std::abs(coeff_magnitude * (new_ub - lb));
1169 if (ub_side) {
1170 var_lb_to_ub_diff[current_var] = new_diff;
1171 var_lb_to_ub_diff[NegationOf(current_var)] = -new_diff;
1172 max_activity += new_diff;
1173 } else {
1174 var_lb_to_ub_diff[current_var] = -new_diff;
1175 var_lb_to_ub_diff[NegationOf(current_var)] = +new_diff;
1176 min_activity -= new_diff;
1177 }
1178 }
1179 }
1180 }
1181
1182 // Restore.
1183 for (const int ref : ct.linear().vars()) {
1184 const IntegerVariable ivar = VarDomination::RefToIntegerVariable(ref);
1185 var_lb_to_ub_diff[ivar] = 0;
1186 var_lb_to_ub_diff[NegationOf(ivar)] = 0;
1187 }
1188 }
1189
1190 // For any dominance relation still left (i.e. between non-fixed vars), if
1191 // the variable are Boolean and X is dominated by Y, we can add
1192 // (X == 1) => (Y = 1). But, as soon as we do that, we break some symmetry
1193 // and cannot add any incompatible relations.
1194 //
1195 // EX: It is possible that X dominate Y and Y dominate X if they are both
1196 // appearing in exactly the same constraint with the same coefficient.
1197 //
1198 // TODO(user): generalize to non Booleans?
1199 // TODO(user): We always keep adding the same relations. Do that only once!
1200 int num_added = 0;
1201 absl::StrongVector<IntegerVariable, bool> increase_is_forbidden(2 * num_vars,
1202 false);
1203 for (int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1204 if (context->IsFixed(positive_ref)) continue;
1205 if (!context->CanBeUsedAsLiteral(positive_ref)) continue;
1206 for (const int ref : {positive_ref, NegatedRef(positive_ref)}) {
1207 const IntegerVariable var = VarDomination::RefToIntegerVariable(ref);
1208 if (increase_is_forbidden[NegationOf(var)]) continue;
1209 for (const IntegerVariable dom :
1210 var_domination.DominatingVariables(ref)) {
1211 if (increase_is_forbidden[dom]) continue;
1212 const int dom_ref = VarDomination::IntegerVariableToRef(dom);
1213 if (context->IsFixed(dom_ref)) continue;
1214 if (!context->CanBeUsedAsLiteral(dom_ref)) continue;
1215 ++num_added;
1216 context->AddImplication(ref, dom_ref);
1217
1218 // dom-- or var++ are now forbidden.
1219 increase_is_forbidden[var] = true;
1220 increase_is_forbidden[NegationOf(dom)] = true;
1221 }
1222 }
1223 }
1224 if (num_added > 0) {
1225 VLOG(1) << "Added " << num_added << " domination implications.";
1226 context->UpdateNewConstraintsVariableUsage();
1227 context->UpdateRuleStats("domination: added implications", num_added);
1228 }
1229
1230 return true;
1231}
1232
1233} // namespace sat
1234} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
#define CHECK_LE(val1, val2)
Definition: base/logging.h:704
#define VLOG(verboselevel)
Definition: base/logging.h:983
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
size_type size() const
void push_back(const value_type &x)
We call domain any subset of Int64 = [kint64min, kint64max].
bool Contains(int64_t value) const
Returns true iff value is in Domain.
std::vector< int64_t > FlattenedIntervals() const
This method returns the flattened list of interval bounds of the domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
const ::operations_research::sat::CpObjectiveProto & objective() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity)
void CannotMove(absl::Span< const int > refs)
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
void ActivityShouldNotIncrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void ActivityShouldNotChange(absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
static int IntegerVariableToRef(IntegerVariable var)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
std::string DominationDebugString(IntegerVariable var) const
static IntegerVariable RefToIntegerVariable(int ref)
void CanOnlyDominateEachOther(absl::Span< const int > refs)
int64_t b
int64_t a
CpModelProto proto
DecisionBuilder *const phase
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
void STLClearObject(T *obj)
Definition: stl_util.h:123
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:92
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool RefIsPositive(int ref)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:83
const IntegerVariable kNoIntegerVariable(-1)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:143
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:30
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
Collection of objects used to extend the Constraint Solver library.
int64_t delta
Definition: resource.cc:1692
Fractional coeff_magnitude
int64_t bound
int64_t start
const double coeff