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