OR-Tools  9.1
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 
19 #include "ortools/base/stl_util.h"
21 
22 namespace operations_research {
23 namespace sat {
24 
25 void 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 
42 void 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 
54 void 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 
64 void 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.
82 void 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 
130 void 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 
145 void 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 
381 void 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_.
421 void 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.
455 void 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 
480 bool VarDomination::CanFreelyDecrease(int ref) const {
482 }
483 
484 bool VarDomination::CanFreelyDecrease(IntegerVariable var) const {
485  return can_freely_decrease_[var];
486 }
487 
488 absl::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 
496 absl::Span<const IntegerVariable> VarDomination::DominatingVariables(
497  int ref) const {
499 }
500 
501 absl::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 
508 std::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
521 void 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 
531 void 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 
541 void 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 
551 template <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.
588  num_locks_[NegationOf(var)]++;
589  can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
590  continue;
591  }
592 
593  // ub side.
594  if (max_activity > ub_limit) {
595  num_locks_[NegationOf(var)]++;
596  if (max_activity - term_diff > ub_limit) {
597  can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
598  } else {
599  const IntegerValue slack(max_activity - ub_limit);
600  const IntegerValue var_diff =
601  CeilRatio(IntegerValue(slack), IntegerValue(coeff));
602  can_freely_decrease_until_[NegationOf(var)] =
603  std::max(can_freely_decrease_until_[NegationOf(var)],
604  -IntegerValue(context.MaxOf(ref)) + var_diff);
605  }
606  }
607  }
608 }
609 
611  const CpModelProto& cp_model = *context->working_model;
612  const int num_vars = cp_model.variables_size();
613  for (int var = 0; var < num_vars; ++var) {
614  if (context->IsFixed(var)) continue;
615 
616  // Fix to lb?
617  const int64_t lb = context->MinOf(var);
618  const int64_t ub_limit = std::max(lb, CanFreelyDecreaseUntil(var));
619  if (ub_limit == lb) {
620  context->UpdateRuleStats("dual: fix variable");
621  CHECK(context->IntersectDomainWith(var, Domain(lb)));
622  continue;
623  }
624 
625  // Fix to ub?
626  const int64_t ub = context->MaxOf(var);
627  const int64_t lb_limit =
629  if (lb_limit == ub) {
630  context->UpdateRuleStats("dual: fix variable");
631  CHECK(context->IntersectDomainWith(var, Domain(ub)));
632  continue;
633  }
634 
635  // Here we can fix to any value in [ub_limit, lb_limit] that is compatible
636  // with the current domain. We prefer zero or the lowest possible magnitude.
637  if (lb_limit > ub_limit) {
638  const Domain domain =
639  context->DomainOf(var).IntersectionWith(Domain(ub_limit, lb_limit));
640  if (!domain.IsEmpty()) {
641  int64_t value = domain.Contains(0) ? 0 : domain.Min();
642  if (value != 0) {
643  for (const int64_t bound : domain.FlattenedIntervals()) {
644  if (std::abs(bound) < std::abs(value)) value = bound;
645  }
646  }
647  context->UpdateRuleStats("dual: fix variable with multiple choices");
648  CHECK(context->IntersectDomainWith(var, Domain(value)));
649  continue;
650  }
651  }
652 
653  // Here we can reduce the domain, but we must be careful when the domain
654  // has holes.
655  if (lb_limit > lb || ub_limit < ub) {
656  const int64_t new_ub =
657  ub_limit < ub
658  ? context->DomainOf(var)
659  .IntersectionWith(
661  .Min()
662  : ub;
663  const int64_t new_lb =
664  lb_limit > lb
665  ? context->DomainOf(var)
666  .IntersectionWith(
668  .Max()
669  : lb;
670  context->UpdateRuleStats("dual: reduced domain");
671  CHECK(context->IntersectDomainWith(var, Domain(new_lb, new_ub)));
672  }
673  }
674 
675  // If (a => b) is the only constraint blocking a literal a in the up
676  // direction, then we can set a == b !
677  //
678  // TODO(user): We can deal with more general situation. For instance an at
679  // most one that is the only blocking constraint can become an exactly one.
680  std::vector<bool> processed(num_vars, false);
681  for (int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
682  if (processed[positive_ref]) continue;
683  if (context->IsFixed(positive_ref)) continue;
684  const IntegerVariable var = RefToIntegerVariable(positive_ref);
685  int ct_index = -1;
686  if (num_locks_[var] == 1 && locking_ct_index_[var] != -1) {
687  ct_index = locking_ct_index_[var];
688  } else if (num_locks_[NegationOf(var)] == 1 &&
689  locking_ct_index_[NegationOf(var)] != -1) {
690  ct_index = locking_ct_index_[NegationOf(var)];
691  } else {
692  continue;
693  }
694  const ConstraintProto& ct = context->working_model->constraints(ct_index);
695  if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
696  context->UpdateRuleStats("TODO dual: tighten at most one");
697  continue;
698  }
699 
700  if (ct.constraint_case() != ConstraintProto::kBoolAnd) continue;
701  if (ct.enforcement_literal().size() != 1) continue;
702 
703  // Recover a => b where a is having an unique up_lock (i.e this constraint).
704  // Note that if many implications are encoded in the same bool_and, we have
705  // to be careful that a is appearing in just one of them.
706  int a = ct.enforcement_literal(0);
707  int b = 1;
708  if (PositiveRef(a) == positive_ref &&
709  num_locks_[RefToIntegerVariable(NegatedRef(a))] == 1) {
710  // Here, we can only add the equivalence if the literal is the only
711  // on the lhs, otherwise there is actually more lock.
712  if (ct.bool_and().literals().size() != 1) continue;
713  b = ct.bool_and().literals(0);
714  } else {
715  bool found = false;
716  b = NegatedRef(ct.enforcement_literal(0));
717  for (const int lhs : ct.bool_and().literals()) {
718  if (PositiveRef(lhs) == positive_ref &&
719  num_locks_[RefToIntegerVariable(lhs)] == 1) {
720  found = true;
721  a = NegatedRef(lhs);
722  break;
723  }
724  }
725  CHECK(found);
726  }
727  CHECK_EQ(num_locks_[RefToIntegerVariable(NegatedRef(a))], 1);
728 
729  processed[PositiveRef(a)] = true;
730  processed[PositiveRef(b)] = true;
731  context->StoreBooleanEqualityRelation(a, b);
732  context->UpdateRuleStats("dual: enforced equivalence");
733  }
734 
735  return true;
736 }
737 
738 namespace {
739 
740 // TODO(user): Maybe we should avoid recomputing that here.
741 template <typename LinearExprProto>
742 void FillMinMaxActivity(const PresolveContext& context,
743  const LinearExprProto& proto, int64_t* min_activity,
744  int64_t* max_activity) {
745  *min_activity = 0;
746  *max_activity = 0;
747  const int num_vars = proto.vars().size();
748  for (int i = 0; i < num_vars; ++i) {
749  const int64_t a = proto.coeffs(i) * context.MinOf(proto.vars(i));
750  const int64_t b = proto.coeffs(i) * context.MaxOf(proto.vars(i));
751  *min_activity += std::min(a, b);
752  *max_activity += std::max(a, b);
753  }
754 }
755 
756 } // namespace
757 
759  const PresolveContext& context, VarDomination* var_domination,
760  DualBoundStrengthening* dual_bound_strengthening) {
761  const CpModelProto& cp_model = *context.working_model;
762  const int num_vars = cp_model.variables().size();
763  var_domination->Reset(num_vars);
764  dual_bound_strengthening->Reset(num_vars);
765 
766  int64_t min_activity = std::numeric_limits<int64_t>::min();
767  int64_t max_activity = std::numeric_limits<int64_t>::max();
768 
769  for (int var = 0; var < num_vars; ++var) {
770  // Deal with the affine relations that are not part of the proto.
771  // Those only need to be processed in the first pass.
772  //
773  // TODO(user): This is not ideal since if only the representative is still
774  // used, we shouldn't restrict any dominance relation involving it.
775  const AffineRelation::Relation r = context.GetAffineRelation(var);
776  if (r.representative != var) {
777  dual_bound_strengthening->CannotMove({var, r.representative});
778  if (r.coeff == 1) {
779  var_domination->CanOnlyDominateEachOther(
781  } else if (r.coeff == -1) {
782  var_domination->CanOnlyDominateEachOther({var, r.representative});
783  } else {
784  var_domination->CanOnlyDominateEachOther({var});
785  var_domination->CanOnlyDominateEachOther({r.representative});
786  }
787  }
788 
789  // Also ignore variables that have been substitued already or are unused.
790  if (context.IsFixed(var) || context.VariableWasRemoved(var) ||
791  context.VariableIsNotUsedAnymore(var)) {
792  dual_bound_strengthening->CannotMove({var});
793  var_domination->CanOnlyDominateEachOther({var});
794  }
795  }
796 
797  // TODO(user): Benchmark and experiment with 3 phases algo:
798  // - Only ActivityShouldNotChange()/CanOnlyDominateEachOther().
799  // - The other cases once.
800  // - EndFirstPhase() and then the other cases a second time.
801  std::vector<int> tmp;
802  const int num_constraints = cp_model.constraints_size();
803  for (int phase = 0; phase < 2; phase++) {
804  for (int c = 0; c < num_constraints; ++c) {
805  const ConstraintProto& ct = cp_model.constraints(c);
806  if (phase == 0) {
807  dual_bound_strengthening->CannotIncrease(ct.enforcement_literal(), c);
808  }
809  switch (ct.constraint_case()) {
811  if (phase == 0) {
812  dual_bound_strengthening->CannotDecrease(ct.bool_or().literals());
813  }
814  var_domination->ActivityShouldNotDecrease(ct.enforcement_literal(),
815  ct.bool_or().literals(),
816  /*coeffs=*/{});
817  break;
819  if (phase == 0) {
820  dual_bound_strengthening->CannotDecrease(ct.bool_and().literals(),
821  c);
822  }
823 
824  // We process it like n clauses.
825  //
826  // TODO(user): the way we process that is a bit restrictive. By
827  // working on the implication graph we could detect more dominance
828  // relations. Since if a => b we say that a++ can only be paired with
829  // b--, but it could actually be paired with any variables that when
830  // dereased implies b = 0. This is a bit mitigated by the fact that
831  // we regroup when we can such implications into big at most ones.
832  tmp.clear();
833  for (const int ref : ct.enforcement_literal()) {
834  tmp.push_back(NegatedRef(ref));
835  }
836  for (const int ref : ct.bool_and().literals()) {
837  tmp.push_back(ref);
838  var_domination->ActivityShouldNotDecrease(/*enforcements=*/{}, tmp,
839  /*coeffs=*/{});
840  tmp.pop_back();
841  }
842  break;
844  if (phase == 0) {
845  dual_bound_strengthening->CannotIncrease(
846  ct.at_most_one().literals(), c);
847  }
848  var_domination->ActivityShouldNotIncrease(ct.enforcement_literal(),
849  ct.at_most_one().literals(),
850  /*coeffs=*/{});
851  break;
853  if (phase == 0) {
854  dual_bound_strengthening->CannotMove(ct.exactly_one().literals());
855  }
856  var_domination->ActivityShouldNotChange(ct.exactly_one().literals(),
857  /*coeffs=*/{});
858  break;
860  FillMinMaxActivity(context, ct.linear(), &min_activity,
861  &max_activity);
862  if (phase == 0) {
863  dual_bound_strengthening->ProcessLinearConstraint(
864  false, context, ct.linear(), min_activity, max_activity);
865  }
866  const bool domain_is_simple = ct.linear().domain().size() == 2;
867  const bool free_to_increase =
868  domain_is_simple && ct.linear().domain(1) >= max_activity;
869  const bool free_to_decrease =
870  domain_is_simple && ct.linear().domain(0) <= min_activity;
871  if (free_to_decrease && free_to_increase) break;
872  if (free_to_increase) {
873  var_domination->ActivityShouldNotDecrease(ct.enforcement_literal(),
874  ct.linear().vars(),
875  ct.linear().coeffs());
876  } else if (free_to_decrease) {
877  var_domination->ActivityShouldNotIncrease(ct.enforcement_literal(),
878  ct.linear().vars(),
879  ct.linear().coeffs());
880  } else {
881  // TODO(user): Handle enforcement better here.
882  if (!ct.enforcement_literal().empty()) {
883  var_domination->ActivityShouldNotIncrease(
884  /*enforcements=*/{}, ct.enforcement_literal(), /*coeffs=*/{});
885  }
886  var_domination->ActivityShouldNotChange(ct.linear().vars(),
887  ct.linear().coeffs());
888  }
889  break;
890  }
891  default:
892  // We cannot infer anything if we don't know the constraint.
893  // TODO(user): Handle enforcement better here.
894  if (phase == 0) {
895  dual_bound_strengthening->CannotMove(context.ConstraintToVars(c));
896  }
897  for (const int var : context.ConstraintToVars(c)) {
898  var_domination->CanOnlyDominateEachOther({var});
899  }
900  break;
901  }
902  }
903 
904  // The objective is handled like a <= constraints, or an == constraint if
905  // there is a non-trivial domain.
906  if (cp_model.has_objective()) {
907  // WARNING: The proto objective might not be up to date, so we need to
908  // write it first.
909  if (phase == 0) {
910  context.WriteObjectiveToProto();
911  }
912  FillMinMaxActivity(context, cp_model.objective(), &min_activity,
913  &max_activity);
914  if (phase == 0) {
915  dual_bound_strengthening->ProcessLinearConstraint(
916  true, context, cp_model.objective(), min_activity, max_activity);
917  }
918  const auto& domain = cp_model.objective().domain();
919  if (domain.empty() || (domain.size() == 2 && domain[0] <= min_activity)) {
920  var_domination->ActivityShouldNotIncrease(
921  /*enforcements=*/{}, cp_model.objective().vars(),
922  cp_model.objective().coeffs());
923  } else {
924  var_domination->ActivityShouldNotChange(cp_model.objective().vars(),
925  cp_model.objective().coeffs());
926  }
927  }
928 
929  if (phase == 0) var_domination->EndFirstPhase();
930  if (phase == 1) var_domination->EndSecondPhase();
931  }
932 
933  // Some statistics.
934  int64_t num_unconstrained_refs = 0;
935  int64_t num_dominated_refs = 0;
936  int64_t num_dominance_relations = 0;
937  for (int var = 0; var < num_vars; ++var) {
938  if (context.IsFixed(var)) continue;
939 
940  for (const int ref : {var, NegatedRef(var)}) {
941  if (var_domination->CanFreelyDecrease(ref)) {
942  num_unconstrained_refs++;
943  } else if (!var_domination->DominatingVariables(ref).empty()) {
944  num_dominated_refs++;
945  num_dominance_relations +=
946  var_domination->DominatingVariables(ref).size();
947  }
948  }
949  }
950  if (num_unconstrained_refs == 0 && num_dominated_refs == 0) return;
951  VLOG(1) << "Dominance:"
952  << " num_unconstrained_refs=" << num_unconstrained_refs
953  << " num_dominated_refs=" << num_dominated_refs
954  << " num_dominance_relations=" << num_dominance_relations;
955 }
956 
957 bool ExploitDominanceRelations(const VarDomination& var_domination,
959  const CpModelProto& cp_model = *context->working_model;
960  const int num_vars = cp_model.variables_size();
961 
962  // Abort early if there is nothing to do.
963  bool work_to_do = false;
964  for (int var = 0; var < num_vars; ++var) {
965  if (context->IsFixed(var)) continue;
966  if (!var_domination.DominatingVariables(var).empty() ||
967  !var_domination.DominatingVariables(NegatedRef(var)).empty()) {
968  work_to_do = true;
969  break;
970  }
971  }
972  if (!work_to_do) return true;
973 
974  absl::StrongVector<IntegerVariable, int64_t> var_lb_to_ub_diff(num_vars * 2,
975  0);
976  absl::StrongVector<IntegerVariable, bool> in_constraints(num_vars * 2, false);
977 
978  const int num_constraints = cp_model.constraints_size();
979  for (int c = 0; c < num_constraints; ++c) {
980  const ConstraintProto& ct = cp_model.constraints(c);
981 
982  if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
983  if (ct.enforcement_literal().size() != 1) continue;
984  const int a = ct.enforcement_literal(0);
985  if (context->IsFixed(a)) continue;
986  for (const int b : ct.bool_and().literals()) {
987  if (context->IsFixed(b)) continue;
988 
989  // If (a--, b--) is valid, we can always set a to false.
990  for (const IntegerVariable ivar :
991  var_domination.DominatingVariables(a)) {
992  const int ref = VarDomination::IntegerVariableToRef(ivar);
993  if (ref == NegatedRef(b)) {
994  context->UpdateRuleStats("domination: in implication");
995  if (!context->SetLiteralToFalse(a)) return false;
996  break;
997  }
998  }
999  if (context->IsFixed(a)) break;
1000 
1001  // If (b++, a++) is valid, then we can always set b to true.
1002  for (const IntegerVariable ivar :
1003  var_domination.DominatingVariables(NegatedRef(b))) {
1004  const int ref = VarDomination::IntegerVariableToRef(ivar);
1005  if (ref == a) {
1006  context->UpdateRuleStats("domination: in implication");
1007  if (!context->SetLiteralToTrue(b)) return false;
1008  break;
1009  }
1010  }
1011  }
1012  continue;
1013  }
1014 
1015  if (!ct.enforcement_literal().empty()) continue;
1016 
1017  // TODO(user): Also deal with exactly one.
1018  // TODO(user): More generally, combine with probing? if a dominated variable
1019  // implies one of its dominant to zero, then it can be set to zero. It seems
1020  // adding the implication below should have the same effect? but currently
1021  // it requires a lot of presolve rounds.
1022  if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
1023  for (const int ref : ct.at_most_one().literals()) {
1024  in_constraints[VarDomination::RefToIntegerVariable(ref)] = true;
1025  }
1026  for (const int ref : ct.at_most_one().literals()) {
1027  if (context->IsFixed(ref)) continue;
1028 
1029  const auto dominating_ivars = var_domination.DominatingVariables(ref);
1030  if (dominating_ivars.empty()) continue;
1031  for (const IntegerVariable ivar : dominating_ivars) {
1032  if (!in_constraints[ivar]) continue;
1033  if (context->IsFixed(VarDomination::IntegerVariableToRef(ivar))) {
1034  continue;
1035  }
1036 
1037  // We can set the dominated variable to false.
1038  context->UpdateRuleStats("domination: in at most one");
1039  if (!context->SetLiteralToFalse(ref)) return false;
1040  break;
1041  }
1042  }
1043  for (const int ref : ct.at_most_one().literals()) {
1044  in_constraints[VarDomination::RefToIntegerVariable(ref)] = false;
1045  }
1046  }
1047 
1048  if (ct.constraint_case() != ConstraintProto::kLinear) continue;
1049 
1050  int num_dominated = 0;
1051  for (const int var : context->ConstraintToVars(c)) {
1052  if (!var_domination.DominatingVariables(var).empty()) ++num_dominated;
1053  if (!var_domination.DominatingVariables(NegatedRef(var)).empty()) {
1054  ++num_dominated;
1055  }
1056  }
1057  if (num_dominated == 0) continue;
1058 
1059  // Precompute.
1060  int64_t min_activity = 0;
1061  int64_t max_activity = 0;
1062  const int num_terms = ct.linear().vars_size();
1063  for (int i = 0; i < num_terms; ++i) {
1064  int ref = ct.linear().vars(i);
1065  int64_t coeff = ct.linear().coeffs(i);
1066  if (coeff < 0) {
1067  ref = NegatedRef(ref);
1068  coeff = -coeff;
1069  }
1070  const int64_t min_term = coeff * context->MinOf(ref);
1071  const int64_t max_term = coeff * context->MaxOf(ref);
1072  min_activity += min_term;
1073  max_activity += max_term;
1074  const IntegerVariable ivar = VarDomination::RefToIntegerVariable(ref);
1075  var_lb_to_ub_diff[ivar] = max_term - min_term;
1076  var_lb_to_ub_diff[NegationOf(ivar)] = min_term - max_term;
1077  }
1078  const int64_t rhs_lb = ct.linear().domain(0);
1079  const int64_t rhs_ub = ct.linear().domain(ct.linear().domain_size() - 1);
1080  if (max_activity < rhs_lb || min_activity > rhs_ub) {
1081  return context->NotifyThatModelIsUnsat("linear equation unsat.");
1082  }
1083 
1084  // Look for dominated var.
1085  for (int i = 0; i < num_terms; ++i) {
1086  const int ref = ct.linear().vars(i);
1087  const int64_t coeff = ct.linear().coeffs(i);
1088  const int64_t coeff_magnitude = std::abs(coeff);
1089  if (context->IsFixed(ref)) continue;
1090 
1091  for (const int current_ref : {ref, NegatedRef(ref)}) {
1092  const absl::Span<const IntegerVariable> dominated_by =
1093  var_domination.DominatingVariables(current_ref);
1094  if (dominated_by.empty()) continue;
1095 
1096  const bool ub_side = (coeff > 0) == (current_ref == ref);
1097  if (ub_side) {
1098  if (max_activity <= rhs_ub) continue;
1099  } else {
1100  if (min_activity >= rhs_lb) continue;
1101  }
1102  const int64_t slack =
1103  ub_side ? rhs_ub - min_activity : max_activity - rhs_lb;
1104 
1105  // Compute the delta in activity if all dominating var moves to their
1106  // other bound.
1107  int64_t delta = 0;
1108  for (const IntegerVariable ivar : dominated_by) {
1109  if (ub_side) {
1110  delta += std::max(int64_t{0}, var_lb_to_ub_diff[ivar]);
1111  } else {
1112  delta += std::max(int64_t{0}, -var_lb_to_ub_diff[ivar]);
1113  }
1114  }
1115 
1116  const int64_t lb = context->MinOf(current_ref);
1117  if (delta + coeff_magnitude > slack) {
1118  context->UpdateRuleStats("domination: fixed to lb.");
1119  if (!context->IntersectDomainWith(current_ref, Domain(lb))) {
1120  return false;
1121  }
1122 
1123  // We need to update the precomputed quantities.
1124  const IntegerVariable current_var =
1126  if (ub_side) {
1127  CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1128  max_activity -= var_lb_to_ub_diff[current_var];
1129  } else {
1130  CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1131  min_activity -= var_lb_to_ub_diff[current_var];
1132  }
1133  var_lb_to_ub_diff[current_var] = 0;
1134  var_lb_to_ub_diff[NegationOf(current_var)] = 0;
1135 
1136  continue;
1137  }
1138 
1139  const IntegerValue diff = FloorRatio(IntegerValue(slack - delta),
1140  IntegerValue(coeff_magnitude));
1141  int64_t new_ub = lb + diff.value();
1142  if (new_ub < context->MaxOf(current_ref)) {
1143  // Tricky: If there are holes, we can't just reduce the domain to
1144  // new_ub if it is not a valid value, so we need to compute the Min()
1145  // of the intersection.
1146  new_ub = context->DomainOf(current_ref)
1147  .IntersectionWith(
1149  .Min();
1150  }
1151  if (new_ub < context->MaxOf(current_ref)) {
1152  context->UpdateRuleStats("domination: reduced ub.");
1153  if (!context->IntersectDomainWith(current_ref, Domain(lb, new_ub))) {
1154  return false;
1155  }
1156 
1157  // We need to update the precomputed quantities.
1158  const IntegerVariable current_var =
1160  if (ub_side) {
1161  CHECK_GE(var_lb_to_ub_diff[current_var], 0);
1162  max_activity -= var_lb_to_ub_diff[current_var];
1163  } else {
1164  CHECK_LE(var_lb_to_ub_diff[current_var], 0);
1165  min_activity -= var_lb_to_ub_diff[current_var];
1166  }
1167  const int64_t new_diff = std::abs(coeff_magnitude * (new_ub - lb));
1168  if (ub_side) {
1169  var_lb_to_ub_diff[current_var] = new_diff;
1170  var_lb_to_ub_diff[NegationOf(current_var)] = -new_diff;
1171  max_activity += new_diff;
1172  } else {
1173  var_lb_to_ub_diff[current_var] = -new_diff;
1174  var_lb_to_ub_diff[NegationOf(current_var)] = +new_diff;
1175  min_activity -= new_diff;
1176  }
1177  }
1178  }
1179  }
1180 
1181  // Restore.
1182  for (const int ref : ct.linear().vars()) {
1183  const IntegerVariable ivar = VarDomination::RefToIntegerVariable(ref);
1184  var_lb_to_ub_diff[ivar] = 0;
1185  var_lb_to_ub_diff[NegationOf(ivar)] = 0;
1186  }
1187  }
1188 
1189  // For any dominance relation still left (i.e. between non-fixed vars), if
1190  // the variable are Boolean and X is dominated by Y, we can add
1191  // (X == 1) => (Y = 1). But, as soon as we do that, we break some symmetry
1192  // and cannot add any incompatible relations.
1193  //
1194  // EX: It is possible that X dominate Y and Y dominate X if they are both
1195  // appearing in exactly the same constraint with the same coefficient.
1196  //
1197  // TODO(user): generalize to non Booleans?
1198  // TODO(user): We always keep adding the same relations. Do that only once!
1199  int num_added = 0;
1200  absl::StrongVector<IntegerVariable, bool> increase_is_forbidden(2 * num_vars,
1201  false);
1202  for (int positive_ref = 0; positive_ref < num_vars; ++positive_ref) {
1203  if (context->IsFixed(positive_ref)) continue;
1204  if (!context->CanBeUsedAsLiteral(positive_ref)) continue;
1205  for (const int ref : {positive_ref, NegatedRef(positive_ref)}) {
1206  const IntegerVariable var = VarDomination::RefToIntegerVariable(ref);
1207  if (increase_is_forbidden[NegationOf(var)]) continue;
1208  for (const IntegerVariable dom :
1209  var_domination.DominatingVariables(ref)) {
1210  if (increase_is_forbidden[dom]) continue;
1211  const int dom_ref = VarDomination::IntegerVariableToRef(dom);
1212  if (context->IsFixed(dom_ref)) continue;
1213  if (!context->CanBeUsedAsLiteral(dom_ref)) continue;
1214  ++num_added;
1215  context->AddImplication(ref, dom_ref);
1216 
1217  // dom-- or var++ are now forbidden.
1218  increase_is_forbidden[var] = true;
1219  increase_is_forbidden[NegationOf(dom)] = true;
1220  }
1221  }
1222  }
1223  if (num_added > 0) {
1224  VLOG(1) << "Added " << num_added << " domination implications.";
1225  context->UpdateNewConstraintsVariableUsage();
1226  context->UpdateRuleStats("domination: added implications", num_added);
1227  }
1228 
1229  return true;
1230 }
1231 
1232 } // namespace sat
1233 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
DecisionBuilder *const phase
int64_t bound
int64_t min
Definition: alldiff_cst.cc:139
::PROTOBUF_NAMESPACE_ID::int64 domain(int index) const
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
Fractional coeff_magnitude
#define VLOG(verboselevel)
Definition: base/logging.h:979
std::vector< int64_t > FlattenedIntervals() const
This method returns the flattened list of interval bounds of the domain.
std::string DominationDebugString(IntegerVariable var) const
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
static int IntegerVariableToRef(IntegerVariable var)
int64_t b
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:142
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
int64_t max
Definition: alldiff_cst.cc:140
void resize(size_type new_size)
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
CpModelProto proto
int64_t Min() const
Returns the min value of the domain.
void ActivityShouldNotIncrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
#define CHECK_LE(val1, val2)
Definition: base/logging.h:700
const ::operations_research::sat::ConstraintProto & constraints(int index) const
const ::operations_research::sat::CpObjectiveProto & objective() const
::PROTOBUF_NAMESPACE_ID::int64 coeffs(int index) const
void CanOnlyDominateEachOther(absl::Span< const int > refs)
void STLClearObject(T *obj)
Definition: stl_util.h:123
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
int64_t delta
Definition: resource.cc:1692
size_type size() const
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
void CannotMove(absl::Span< const int > refs)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
static IntegerVariable RefToIntegerVariable(int ref)
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity)
We call domain any subset of Int64 = [kint64min, kint64max].
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:91
bool Contains(int64_t value) const
Returns true iff value is in Domain.
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:82
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void assign(size_type n, const value_type &val)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
bool RefIsPositive(int ref)
IntVar * var
Definition: expr_array.cc:1874
void ActivityShouldNotChange(absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
GurobiMPCallbackContext * context
bool IsEmpty() const
Returns true if this is the empty set.
int64_t value
const Constraint * ct
int64_t a