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
21
22namespace operations_research {
23namespace sat {
24
25void VarDomination::Reset(int num_variables) {
26 phase_ = 0;
27 num_vars_with_negation_ = 2 * num_variables;
28 partition_ = absl::make_unique<DynamicPartition>(num_vars_with_negation_);
29
30 can_freely_decrease_.assign(num_vars_with_negation_, true);
31
32 shared_buffer_.clear();
33 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
34
35 buffer_.clear();
36 dominating_vars_.assign(num_vars_with_negation_, IntegerVariableSpan());
37
38 ct_index_for_signature_ = 0;
39 block_down_signatures_.assign(num_vars_with_negation_, 0);
40}
41
42void VarDomination::RefinePartition(std::vector<int>* vars) {
43 if (vars->empty()) return;
44 partition_->Refine(*vars);
45 for (int& var : *vars) {
46 const IntegerVariable wrapped(var);
47 can_freely_decrease_[wrapped] = false;
48 can_freely_decrease_[NegationOf(wrapped)] = false;
49 var = NegationOf(wrapped).value();
50 }
51 partition_->Refine(*vars);
52}
53
54void VarDomination::CanOnlyDominateEachOther(absl::Span<const int> refs) {
55 if (phase_ != 0) return;
56 tmp_vars_.clear();
57 for (const int ref : refs) {
58 tmp_vars_.push_back(RefToIntegerVariable(ref).value());
59 }
60 RefinePartition(&tmp_vars_);
61 tmp_vars_.clear();
62}
63
64void VarDomination::ActivityShouldNotChange(absl::Span<const int> refs,
65 absl::Span<const int64_t> coeffs) {
66 if (phase_ != 0) return;
67 FillTempRanks(/*reverse_references=*/false, /*enforcements=*/{}, refs,
68 coeffs);
69 tmp_vars_.clear();
70 for (int i = 0; i < tmp_ranks_.size(); ++i) {
71 if (i > 0 && tmp_ranks_[i].rank != tmp_ranks_[i - 1].rank) {
72 RefinePartition(&tmp_vars_);
73 tmp_vars_.clear();
74 }
75 tmp_vars_.push_back(tmp_ranks_[i].var.value());
76 }
77 RefinePartition(&tmp_vars_);
78 tmp_vars_.clear();
79}
80
81// This correspond to a lower bounded constraint.
82void VarDomination::ProcessTempRanks() {
83 if (phase_ == 0) {
84 // We actually "split" tmp_ranks_ according to the current partition and
85 // process each resulting list independently for a faster algo.
86 ++ct_index_for_signature_;
87 for (IntegerVariableWithRank& entry : tmp_ranks_) {
88 can_freely_decrease_[entry.var] = false;
89 block_down_signatures_[entry.var] |= uint64_t{1}
90 << (ct_index_for_signature_ % 64);
91 entry.part = partition_->PartOf(entry.var.value());
92 }
93 std::stable_sort(
94 tmp_ranks_.begin(), tmp_ranks_.end(),
95 [](const IntegerVariableWithRank& a, const IntegerVariableWithRank& b) {
96 return a.part < b.part;
97 });
98 int start = 0;
99 for (int i = 1; i < tmp_ranks_.size(); ++i) {
100 if (tmp_ranks_[i].part != tmp_ranks_[start].part) {
101 Initialize({&tmp_ranks_[start], static_cast<size_t>(i - start)});
102 start = i;
103 }
104 }
105 if (start < tmp_ranks_.size()) {
106 Initialize({&tmp_ranks_[start], tmp_ranks_.size() - start});
107 }
108 } else if (phase_ == 1) {
109 FilterUsingTempRanks();
110 } else {
111 // This is only used for debugging, and we shouldn't reach here in prod.
112 CheckUsingTempRanks();
113 }
114}
115
117 absl::Span<const int> enforcements, absl::Span<const int> refs,
118 absl::Span<const int64_t> coeffs) {
119 FillTempRanks(/*reverse_references=*/false, enforcements, refs, coeffs);
120 ProcessTempRanks();
121}
122
124 absl::Span<const int> enforcements, absl::Span<const int> refs,
125 absl::Span<const int64_t> coeffs) {
126 FillTempRanks(/*reverse_references=*/true, enforcements, refs, coeffs);
127 ProcessTempRanks();
128}
129
130void VarDomination::MakeRankEqualToStartOfPart(
131 absl::Span<IntegerVariableWithRank> span) {
132 const int size = span.size();
133 int start = 0;
134 int previous_value = 0;
135 for (int i = 0; i < size; ++i) {
136 const int64_t value = span[i].rank;
137 if (value != previous_value) {
138 previous_value = value;
139 start = i;
140 }
141 span[i].rank = start;
142 }
143}
144
145void VarDomination::Initialize(absl::Span<IntegerVariableWithRank> span) {
146 // The rank can be wrong and need to be recomputed because of how we splitted
147 // tmp_ranks_ into spans.
148 MakeRankEqualToStartOfPart(span);
149
150 const int future_start = shared_buffer_.size();
151 int first_start = -1;
152
153 // This is mainly to avoid corner case and hopefully, it should be big enough
154 // to not matter too much.
155 const int kSizeThreshold = 1000;
156 const int size = span.size();
157 for (int i = std::max(0, size - kSizeThreshold); i < size; ++i) {
158 const IntegerVariableWithRank entry = span[i];
159 const int num_candidates = size - entry.rank;
160 if (num_candidates >= kSizeThreshold) continue;
161
162 // Compute size to beat.
163 int size_threshold = kSizeThreshold;
164
165 // Take into account the current partition size.
166 const int var_part = partition_->PartOf(entry.var.value());
167 const int part_size = partition_->SizeOfPart(var_part);
168 size_threshold = std::min(size_threshold, part_size);
169
170 // Take into account our current best candidate if there is one.
171 const int current_num_candidates = initial_candidates_[entry.var].size;
172 if (current_num_candidates != 0) {
173 size_threshold = std::min(size_threshold, current_num_candidates);
174 }
175
176 if (num_candidates < size_threshold) {
177 if (first_start == -1) first_start = entry.rank;
178 initial_candidates_[entry.var] = {
179 future_start - first_start + static_cast<int>(entry.rank),
180 num_candidates};
181 }
182 }
183
184 // Only store what is necessary.
185 if (first_start == -1) return;
186 for (int i = first_start; i < size; ++i) {
187 shared_buffer_.push_back(span[i].var);
188 }
189}
190
191// TODO(user): Use more heuristics to not miss as much dominance relation when
192// we crop initial lists.
194 CHECK_EQ(phase_, 0);
195 phase_ = 1;
196
197 // Some initial lists ar too long and will be cropped to this size.
198 // We will handle them slightly differently.
199 //
200 // TODO(user): Tune the initial size, 50 might be a bit large, since our
201 // complexity is borned by this number times the number of entries in the
202 // constraints. Still we should in most situation be a lot lower than that.
203 const int kMaxInitialSize = 50;
204 std::vector<IntegerVariable> cropped_lists;
205 absl::StrongVector<IntegerVariable, bool> is_cropped(num_vars_with_negation_,
206 false);
207
208 // Fill the initial domination candidates.
209 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
210 if (can_freely_decrease_[var]) continue;
211 const int part = partition_->PartOf(var.value());
212 const int part_size = partition_->SizeOfPart(part);
213
214 const int start = buffer_.size();
215 int new_size = 0;
216
217 const uint64_t var_sig = block_down_signatures_[var];
218 const uint64_t not_var_sig = block_down_signatures_[NegationOf(var)];
219 const int stored_size = initial_candidates_[var].size;
220 if (stored_size == 0 || part_size < stored_size) {
221 // We start with the partition part.
222 // Note that all constraint will be filtered again in the second pass.
223 int num_tested = 0;
224 for (const int value : partition_->ElementsInPart(part)) {
225 const IntegerVariable c = IntegerVariable(value);
226
227 // This is to limit the complexity to 1k * num_vars. We fill the list
228 // with dummy node so that the heuristic below will fill it with
229 // potential transpose candidates.
230 if (++num_tested > 1000) {
231 is_cropped[var] = true;
232 cropped_lists.push_back(var);
233 int extra = new_size;
234 while (extra < kMaxInitialSize) {
235 ++extra;
236 buffer_.push_back(kNoIntegerVariable);
237 }
238 break;
239 }
240 if (PositiveVariable(c) == PositiveVariable(var)) continue;
241 if (can_freely_decrease_[NegationOf(c)]) continue;
242 if (var_sig & ~block_down_signatures_[c]) continue; // !included.
243 if (block_down_signatures_[NegationOf(c)] & ~not_var_sig) continue;
244 ++new_size;
245 buffer_.push_back(c);
246
247 // We do not want too many candidates per variables.
248 // TODO(user): randomize?
249 if (new_size > kMaxInitialSize) {
250 is_cropped[var] = true;
251 cropped_lists.push_back(var);
252 break;
253 }
254 }
255 } else {
256 // Copy the one that are in the same partition_ part.
257 //
258 // TODO(user): This can be too long maybe? even if we have list of at
259 // most 1000 at this point, see InitializeUsingTempRanks().
260 for (const IntegerVariable c : InitialDominatingCandidates(var)) {
261 if (PositiveVariable(c) == PositiveVariable(var)) continue;
262 if (can_freely_decrease_[NegationOf(c)]) continue;
263 if (partition_->PartOf(c.value()) != part) continue;
264 if (var_sig & ~block_down_signatures_[c]) continue; // !included.
265 if (block_down_signatures_[NegationOf(c)] & ~not_var_sig) continue;
266 ++new_size;
267 buffer_.push_back(c);
268
269 // We do not want too many candidates per variables.
270 // TODO(user): randomize?
271 if (new_size > kMaxInitialSize) {
272 is_cropped[var] = true;
273 cropped_lists.push_back(var);
274 break;
275 }
276 }
277 }
278
279 dominating_vars_[var] = {start, new_size};
280 }
281
282 // Heuristic: To try not to remove domination relations corresponding to short
283 // lists during transposition (see EndSecondPhase()), we fill half of the
284 // cropped list with the transpose of the short list relations. This helps
285 // finding more relation in the presence of cropped lists.
286 for (const IntegerVariable var : cropped_lists) {
287 if (kMaxInitialSize / 2 < dominating_vars_[var].size) {
288 dominating_vars_[var].size = kMaxInitialSize / 2; // Restrict.
289 }
290 }
291 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
292 for (const IntegerVariable dom : DominatingVariables(var)) {
293 if (!is_cropped[NegationOf(dom)]) continue;
294 IntegerVariableSpan& s = dominating_vars_[NegationOf(dom)];
295 if (s.size >= kMaxInitialSize) continue;
296 buffer_[s.start + s.size++] = NegationOf(var);
297 }
298 }
299
300 // Remove any duplicates.
301 //
302 // TODO(user): Maybe we should do that with all lists in case the
303 // input function are called with duplicates too.
304 for (const IntegerVariable var : cropped_lists) {
305 if (!is_cropped[var]) continue;
306 IntegerVariableSpan& s = dominating_vars_[var];
307 std::sort(&buffer_[s.start], &buffer_[s.start + s.size]);
308 const auto p = std::unique(&buffer_[s.start], &buffer_[s.start + s.size]);
309 s.size = p - &buffer_[s.start];
310 }
311
312 // We no longer need the first phase memory.
313 VLOG(1) << "Num initial list that where cropped: " << cropped_lists.size();
314 VLOG(1) << "Shared buffer size: " << shared_buffer_.size();
315 VLOG(1) << "Buffer size: " << buffer_.size();
316 gtl::STLClearObject(&initial_candidates_);
317 gtl::STLClearObject(&shared_buffer_);
318}
319
321 CHECK_EQ(phase_, 1);
322 phase_ = 2;
323
324 // Perform intersection with transpose.
325 shared_buffer_.clear();
326 initial_candidates_.assign(num_vars_with_negation_, IntegerVariableSpan());
327
328 // Pass 1: count.
329 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
330 for (const IntegerVariable dom : DominatingVariables(var)) {
331 ++initial_candidates_[NegationOf(dom)].size;
332 }
333 }
334
335 // Pass 2: compute starts.
336 int start = 0;
337 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
338 initial_candidates_[var].start = start;
339 start += initial_candidates_[var].size;
340 initial_candidates_[var].size = 0;
341 }
342 shared_buffer_.resize(start);
343
344 // Pass 3: transpose.
345 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
346 for (const IntegerVariable dom : DominatingVariables(var)) {
347 IntegerVariableSpan& span = initial_candidates_[NegationOf(dom)];
348 shared_buffer_[span.start + span.size++] = NegationOf(var);
349 }
350 }
351
352 // Pass 4: intersect.
353 int num_removed = 0;
354 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
355 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
356 for (const IntegerVariable dom : InitialDominatingCandidates(var)) {
357 tmp_var_to_rank_[dom] = 1;
358 }
359
360 int new_size = 0;
361 IntegerVariableSpan& span = dominating_vars_[var];
362 for (const IntegerVariable dom : DominatingVariables(var)) {
363 if (tmp_var_to_rank_[dom] != 1) {
364 ++num_removed;
365 continue;
366 }
367 buffer_[span.start + new_size++] = dom;
368 }
369 span.size = new_size;
370
371 for (const IntegerVariable dom : InitialDominatingCandidates(var)) {
372 tmp_var_to_rank_[dom] = -1;
373 }
374 }
375
376 VLOG(1) << "Transpose removed " << num_removed;
377 gtl::STLClearObject(&initial_candidates_);
378 gtl::STLClearObject(&shared_buffer_);
379}
380
381void VarDomination::FillTempRanks(bool reverse_references,
382 absl::Span<const int> enforcements,
383 absl::Span<const int> refs,
384 absl::Span<const int64_t> coeffs) {
385 tmp_ranks_.clear();
386 if (coeffs.empty()) {
387 // Simple case: all coefficients are assumed to be the same.
388 for (const int ref : refs) {
389 const IntegerVariable var =
390 RefToIntegerVariable(reverse_references ? NegatedRef(ref) : ref);
391 tmp_ranks_.push_back({var, 0, 0});
392 }
393 } else {
394 // Complex case: different coefficients.
395 for (int i = 0; i < refs.size(); ++i) {
396 if (coeffs[i] == 0) continue;
397 const IntegerVariable var = RefToIntegerVariable(
398 reverse_references ? NegatedRef(refs[i]) : refs[i]);
399 if (coeffs[i] > 0) {
400 tmp_ranks_.push_back({var, 0, coeffs[i]});
401 } else {
402 tmp_ranks_.push_back({NegationOf(var), 0, -coeffs[i]});
403 }
404 }
405 std::sort(tmp_ranks_.begin(), tmp_ranks_.end());
406 MakeRankEqualToStartOfPart({&tmp_ranks_[0], tmp_ranks_.size()});
407 }
408
409 // Add the enforcement last with a new rank. We add their negation since
410 // we want the activity to not decrease, and we want to allow any
411 // enforcement-- to dominate a variable in the constraint.
412 const int enforcement_rank = tmp_ranks_.size();
413 for (const int ref : enforcements) {
414 tmp_ranks_.push_back(
415 {RefToIntegerVariable(NegatedRef(ref)), 0, enforcement_rank});
416 }
417}
418
419// We take the intersection of the current dominating candidate with the
420// restriction imposed by the current content of tmp_ranks_.
421void VarDomination::FilterUsingTempRanks() {
422 // Expand ranks in temp vector.
423 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
424 for (const IntegerVariableWithRank entry : tmp_ranks_) {
425 tmp_var_to_rank_[entry.var] = entry.rank;
426 }
427
428 // The activity of the variable in tmp_rank must not decrease.
429 for (const IntegerVariableWithRank entry : tmp_ranks_) {
430 // The only variables that can be paired with a var-- in the constriants are
431 // the var++ in the constraints with the same rank or higher.
432 //
433 // Note that we only filter the var-- domination lists here, we do not
434 // remove the var-- appearing in all the lists corresponding to wrong var++.
435 // This is left to the tranpose operation in EndSecondPhase().
436 {
437 IntegerVariableSpan& span = dominating_vars_[entry.var];
438 if (span.size == 0) continue;
439 int new_size = 0;
440 for (const IntegerVariable candidate : DominatingVariables(entry.var)) {
441 if (tmp_var_to_rank_[candidate] < entry.rank) continue;
442 buffer_[span.start + new_size++] = candidate;
443 }
444 span.size = new_size;
445 }
446 }
447
448 // Reset temporary vector to all -1.
449 for (const IntegerVariableWithRank entry : tmp_ranks_) {
450 tmp_var_to_rank_[entry.var] = -1;
451 }
452}
453
454// Slow: This is for debugging only.
455void VarDomination::CheckUsingTempRanks() {
456 tmp_var_to_rank_.resize(num_vars_with_negation_, -1);
457 for (const IntegerVariableWithRank entry : tmp_ranks_) {
458 tmp_var_to_rank_[entry.var] = entry.rank;
459 }
460
461 // The activity of the variable in tmp_rank must not decrease.
462 for (IntegerVariable var(0); var < num_vars_with_negation_; ++var) {
463 const int var_rank = tmp_var_to_rank_[var];
464 const int negated_var_rank = tmp_var_to_rank_[NegationOf(var)];
465 for (const IntegerVariable dom : DominatingVariables(var)) {
466 CHECK(!can_freely_decrease_[NegationOf(dom)]);
467
468 // Doing X--, Y++ is compatible if the rank[X] <= rank[Y]. But we also
469 // need to check if doing Not(Y)-- is compatible with Not(X)++.
470 CHECK_LE(var_rank, tmp_var_to_rank_[dom]);
471 CHECK_LE(tmp_var_to_rank_[NegationOf(dom)], negated_var_rank);
472 }
473 }
474
475 for (const IntegerVariableWithRank entry : tmp_ranks_) {
476 tmp_var_to_rank_[entry.var] = -1;
477 }
478}
479
482}
483
484bool VarDomination::CanFreelyDecrease(IntegerVariable var) const {
485 return can_freely_decrease_[var];
486}
487
488absl::Span<const IntegerVariable> VarDomination::InitialDominatingCandidates(
489 IntegerVariable var) const {
490 const IntegerVariableSpan span = initial_candidates_[var];
491 if (span.size == 0) return absl::Span<const IntegerVariable>();
492 return absl::Span<const IntegerVariable>(&shared_buffer_[span.start],
493 span.size);
494}
495
496absl::Span<const IntegerVariable> VarDomination::DominatingVariables(
497 int ref) const {
499}
500
501absl::Span<const IntegerVariable> VarDomination::DominatingVariables(
502 IntegerVariable var) const {
503 const IntegerVariableSpan span = dominating_vars_[var];
504 if (span.size == 0) return absl::Span<const IntegerVariable>();
505 return absl::Span<const IntegerVariable>(&buffer_[span.start], span.size);
506}
507
508std::string VarDomination::DominationDebugString(IntegerVariable var) const {
509 const int ref = IntegerVariableToRef(var);
510 std::string result =
511 absl::StrCat(PositiveRef(ref), RefIsPositive(ref) ? "--" : "++", " : ");
512 for (const IntegerVariable dom : DominatingVariables(var)) {
513 const int dom_ref = IntegerVariableToRef(dom);
514 absl::StrAppend(&result, PositiveRef(dom_ref),
515 RefIsPositive(dom_ref) ? "++" : "--", " ");
516 }
517 return result;
518}
519
520// TODO(user): No need to set locking_ct_index_[var] if num_locks_[var] > 1
521void DualBoundStrengthening::CannotDecrease(absl::Span<const int> refs,
522 int ct_index) {
523 for (const int ref : refs) {
524 const IntegerVariable var = RefToIntegerVariable(ref);
525 can_freely_decrease_until_[var] = kMaxIntegerValue;
526 num_locks_[var]++;
527 locking_ct_index_[var] = ct_index;
528 }
529}
530
531void DualBoundStrengthening::CannotIncrease(absl::Span<const int> refs,
532 int ct_index) {
533 for (const int ref : refs) {
534 const IntegerVariable var = RefToIntegerVariable(ref);
535 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
536 num_locks_[NegationOf(var)]++;
537 locking_ct_index_[NegationOf(var)] = ct_index;
538 }
539}
540
541void DualBoundStrengthening::CannotMove(absl::Span<const int> refs) {
542 for (const int ref : refs) {
543 const IntegerVariable var = RefToIntegerVariable(ref);
544 can_freely_decrease_until_[var] = kMaxIntegerValue;
545 can_freely_decrease_until_[NegationOf(var)] = kMaxIntegerValue;
546 num_locks_[var]++;
547 num_locks_[NegationOf(var)]++;
548 }
549}
550
551template <typename LinearProto>
553 bool is_objective, const PresolveContext& context,
554 const LinearProto& linear, int64_t min_activity, int64_t max_activity) {
555 const int64_t lb_limit = linear.domain(linear.domain_size() - 2);
556 const int64_t ub_limit = linear.domain(1);
557 const int num_terms = linear.vars_size();
558 for (int i = 0; i < num_terms; ++i) {
559 int ref = linear.vars(i);
560 int64_t coeff = linear.coeffs(i);
561 if (coeff < 0) {
562 ref = NegatedRef(ref);
563 coeff = -coeff;
564 }
565
566 const int64_t min_term = coeff * context.MinOf(ref);
567 const int64_t max_term = coeff * context.MaxOf(ref);
568 const int64_t term_diff = max_term - min_term;
569 const IntegerVariable var = RefToIntegerVariable(ref);
570
571 // lb side.
572 if (min_activity < lb_limit) {
573 num_locks_[var]++;
574 if (min_activity + term_diff < lb_limit) {
575 can_freely_decrease_until_[var] = kMaxIntegerValue;
576 } else {
577 const IntegerValue slack(lb_limit - min_activity);
578 const IntegerValue var_diff =
579 CeilRatio(IntegerValue(slack), IntegerValue(coeff));
580 can_freely_decrease_until_[var] =
581 std::max(can_freely_decrease_until_[var],
582 IntegerValue(context.MinOf(ref)) + var_diff);
583 }
584 }
585
586 if (is_objective) {
587 // We never want to increase the objective value.
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
738namespace {
739
740// TODO(user): Maybe we should avoid recomputing that here.
741template <typename LinearExprProto>
742void 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
957bool 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
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
#define CHECK_LE(val1, val2)
Definition: base/logging.h:700
#define VLOG(verboselevel)
Definition: base/logging.h:979
void push_back(const value_type &x)
We call domain any subset of Int64 = [kint64min, kint64max].
bool Contains(int64_t value) const
Returns true iff value is in Domain.
std::vector< int64_t > FlattenedIntervals() const
This method returns the flattened list of interval bounds of the domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
const ::operations_research::sat::CpObjectiveProto & objective() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::PROTOBUF_NAMESPACE_ID::int64 coeffs(int index) const
::PROTOBUF_NAMESPACE_ID::int64 domain(int index) const
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity)
void CannotMove(absl::Span< const int > refs)
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
void ActivityShouldNotIncrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void ActivityShouldNotChange(absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
static int IntegerVariableToRef(IntegerVariable var)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
std::string DominationDebugString(IntegerVariable var) const
static IntegerVariable RefToIntegerVariable(int ref)
void CanOnlyDominateEachOther(absl::Span< const int > refs)
int64_t b
int64_t a
CpModelProto proto
DecisionBuilder *const phase
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
void STLClearObject(T *obj)
Definition: stl_util.h:123
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:91
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool RefIsPositive(int ref)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:82
const IntegerVariable kNoIntegerVariable(-1)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:142
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
Collection of objects used to extend the Constraint Solver library.
int64_t delta
Definition: resource.cc:1692
Fractional coeff_magnitude
int64_t bound