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