OR-Tools  9.6
pb_constraint.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
17 #include <memory>
18 #include <string>
19 #include <utility>
20 #include <vector>
21 
22 #include "absl/container/flat_hash_map.h"
23 #include "absl/hash/hash.h"
24 #include "absl/strings/str_format.h"
25 #include "absl/types/span.h"
26 #include "ortools/base/logging.h"
28 #include "ortools/sat/sat_base.h"
29 #include "ortools/sat/sat_parameters.pb.h"
30 #include "ortools/util/bitset.h"
32 #include "ortools/util/stats.h"
34 
35 namespace operations_research {
36 namespace sat {
37 
38 namespace {
39 
40 bool LiteralComparator(const LiteralWithCoeff& a, const LiteralWithCoeff& b) {
41  return a.literal.Index() < b.literal.Index();
42 }
43 
44 bool CoeffComparator(const LiteralWithCoeff& a, const LiteralWithCoeff& b) {
45  if (a.coefficient == b.coefficient) {
46  return a.literal.Index() < b.literal.Index();
47  }
48  return a.coefficient < b.coefficient;
49 }
50 
51 } // namespace
52 
54  std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
55  Coefficient* max_value) {
56  // Note(user): For some reason, the IntType checking doesn't work here ?! that
57  // is a bit worrying, but the code seems to behave correctly.
58  *bound_shift = 0;
59  *max_value = 0;
60 
61  // First, sort by literal to remove duplicate literals.
62  // This also remove term with a zero coefficient.
63  std::sort(cst->begin(), cst->end(), LiteralComparator);
64  int index = 0;
66  for (int i = 0; i < cst->size(); ++i) {
67  const LiteralWithCoeff current = (*cst)[i];
68  if (current.coefficient == 0) continue;
69  if (representative != nullptr &&
70  current.literal.Variable() == representative->literal.Variable()) {
71  if (current.literal == representative->literal) {
72  if (!SafeAddInto(current.coefficient, &(representative->coefficient))) {
73  return false;
74  }
75  } else {
76  // Here current_literal is equal to (1 - representative).
77  if (!SafeAddInto(-current.coefficient,
78  &(representative->coefficient))) {
79  return false;
80  }
81  if (!SafeAddInto(-current.coefficient, bound_shift)) return false;
82  }
83  } else {
84  if (representative != nullptr && representative->coefficient == 0) {
85  --index;
86  }
87  (*cst)[index] = current;
88  representative = &((*cst)[index]);
89  ++index;
90  }
91  }
92  if (representative != nullptr && representative->coefficient == 0) {
93  --index;
94  }
95  cst->resize(index);
96 
97  // Then, make all coefficients positive by replacing a term "-c x" into
98  // "c(1-x) - c" which is the same as "c(not x) - c".
99  for (int i = 0; i < cst->size(); ++i) {
100  const LiteralWithCoeff current = (*cst)[i];
101  if (current.coefficient < 0) {
102  if (!SafeAddInto(-current.coefficient, bound_shift)) return false;
103  (*cst)[i].coefficient = -current.coefficient;
104  (*cst)[i].literal = current.literal.Negated();
105  }
106  if (!SafeAddInto((*cst)[i].coefficient, max_value)) return false;
107  }
108 
109  // Finally sort by increasing coefficients.
110  std::sort(cst->begin(), cst->end(), CoeffComparator);
111  DCHECK_GE(*max_value, 0);
112  return true;
113 }
114 
117  std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
118  Coefficient* max_value) {
119  int index = 0;
120  Coefficient shift_due_to_fixed_variables(0);
121  for (const LiteralWithCoeff& entry : *cst) {
122  if (mapping[entry.literal.Index()] >= 0) {
123  (*cst)[index] = LiteralWithCoeff(Literal(mapping[entry.literal.Index()]),
124  entry.coefficient);
125  ++index;
126  } else if (mapping[entry.literal.Index()] == kTrueLiteralIndex) {
127  if (!SafeAddInto(-entry.coefficient, &shift_due_to_fixed_variables)) {
128  return false;
129  }
130  } else {
131  // Nothing to do if the literal is false.
132  DCHECK_EQ(mapping[entry.literal.Index()], kFalseLiteralIndex);
133  }
134  }
135  cst->resize(index);
136  if (cst->empty()) {
137  *bound_shift = shift_due_to_fixed_variables;
138  *max_value = 0;
139  return true;
140  }
141  const bool result =
142  ComputeBooleanLinearExpressionCanonicalForm(cst, bound_shift, max_value);
143  if (!SafeAddInto(shift_due_to_fixed_variables, bound_shift)) return false;
144  return result;
145 }
146 
147 // TODO(user): Also check for no duplicates literals + unit tests.
149  const std::vector<LiteralWithCoeff>& cst) {
150  Coefficient previous(1);
151  for (LiteralWithCoeff term : cst) {
152  if (term.coefficient < previous) return false;
153  previous = term.coefficient;
154  }
155  return true;
156 }
157 
158 // TODO(user): Use more complex simplification like dividing by the gcd of
159 // everyone and using less different coefficients if possible.
161  std::vector<LiteralWithCoeff>* cst, Coefficient* rhs) {
162  // Replace all coefficient >= rhs by rhs + 1 (these literal must actually be
163  // false). Note that the linear sum of literals remains canonical.
164  //
165  // TODO(user): It is probably better to remove these literals and have other
166  // constraint setting them to false from the symmetry finder perspective.
167  for (LiteralWithCoeff& x : *cst) {
168  if (x.coefficient > *rhs) x.coefficient = *rhs + 1;
169  }
170 }
171 
173  Coefficient bound_shift,
174  Coefficient max_value) {
175  Coefficient rhs = upper_bound;
176  if (!SafeAddInto(bound_shift, &rhs)) {
177  if (bound_shift > 0) {
178  // Positive overflow. The constraint is trivially true.
179  // This is because the canonical linear expression is in [0, max_value].
180  return max_value;
181  } else {
182  // Negative overflow. The constraint is infeasible.
183  return Coefficient(-1);
184  }
185  }
186  if (rhs < 0) return Coefficient(-1);
187  return std::min(max_value, rhs);
188 }
189 
191  Coefficient bound_shift,
192  Coefficient max_value) {
193  // The new bound is "max_value - (lower_bound + bound_shift)", but we must
194  // pay attention to possible overflows.
195  Coefficient shifted_lb = lower_bound;
196  if (!SafeAddInto(bound_shift, &shifted_lb)) {
197  if (bound_shift > 0) {
198  // Positive overflow. The constraint is infeasible.
199  return Coefficient(-1);
200  } else {
201  // Negative overflow. The constraint is trivialy satisfiable.
202  return max_value;
203  }
204  }
205  if (shifted_lb <= 0) {
206  // If shifted_lb <= 0 then the constraint is trivialy satisfiable. We test
207  // this so we are sure that max_value - shifted_lb doesn't overflow below.
208  return max_value;
209  }
210  return max_value - shifted_lb;
211 }
212 
214  bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound,
215  Coefficient upper_bound, std::vector<LiteralWithCoeff>* cst) {
216  // Canonicalize the linear expression of the constraint.
217  Coefficient bound_shift;
218  Coefficient max_value;
219  if (!ComputeBooleanLinearExpressionCanonicalForm(cst, &bound_shift,
220  &max_value)) {
221  return false;
222  }
223  if (use_upper_bound) {
224  const Coefficient rhs =
225  ComputeCanonicalRhs(upper_bound, bound_shift, max_value);
226  if (!AddConstraint(*cst, max_value, rhs)) return false;
227  }
228  if (use_lower_bound) {
229  // We transform the constraint into an upper-bounded one.
230  for (int i = 0; i < cst->size(); ++i) {
231  (*cst)[i].literal = (*cst)[i].literal.Negated();
232  }
233  const Coefficient rhs =
234  ComputeNegatedCanonicalRhs(lower_bound, bound_shift, max_value);
235  if (!AddConstraint(*cst, max_value, rhs)) return false;
236  }
237  return true;
238 }
239 
240 bool CanonicalBooleanLinearProblem::AddConstraint(
241  const std::vector<LiteralWithCoeff>& cst, Coefficient max_value,
242  Coefficient rhs) {
243  if (rhs < 0) return false; // Trivially unsatisfiable.
244  if (rhs >= max_value) return true; // Trivially satisfiable.
245  constraints_.emplace_back(cst.begin(), cst.end());
246  rhs_.push_back(rhs);
247  SimplifyCanonicalBooleanLinearConstraint(&constraints_.back(), &rhs_.back());
248  return true;
249 }
250 
252  if (terms_.size() != num_variables) {
253  terms_.assign(num_variables, Coefficient(0));
254  non_zeros_.ClearAndResize(BooleanVariable(num_variables));
255  rhs_ = 0;
256  max_sum_ = 0;
257  } else {
258  ClearAll();
259  }
260 }
261 
263  // TODO(user): We could be more efficient and have only one loop here.
264  for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
265  terms_[var] = Coefficient(0);
266  }
267  non_zeros_.ClearAll();
268  rhs_ = 0;
269  max_sum_ = 0;
270 }
271 
272 // TODO(user): Also reduce the trivially false literal when coeff > rhs_ ?
274  CHECK_LT(rhs_, max_sum_) << "Trivially sat.";
275  Coefficient removed_sum(0);
276  const Coefficient bound = max_sum_ - rhs_;
277  for (BooleanVariable var : PossibleNonZeros()) {
278  const Coefficient diff = GetCoefficient(var) - bound;
279  if (diff > 0) {
280  removed_sum += diff;
281  terms_[var] = (terms_[var] > 0) ? bound : -bound;
282  }
283  }
284  rhs_ -= removed_sum;
285  max_sum_ -= removed_sum;
286  DCHECK_EQ(max_sum_, ComputeMaxSum());
287 }
288 
290  std::string result;
291  for (BooleanVariable var : PossibleNonZeros()) {
292  if (!result.empty()) result += " + ";
293  result += absl::StrFormat("%d[%s]", GetCoefficient(var).value(),
295  }
296  result += absl::StrFormat(" <= %d", rhs_.value());
297  return result;
298 }
299 
300 // TODO(user): Keep this for DCHECK(), but maintain the slack incrementally
301 // instead of recomputing it.
303  const Trail& trail, int trail_index) const {
304  Coefficient activity(0);
305  for (BooleanVariable var : PossibleNonZeros()) {
306  if (GetCoefficient(var) == 0) continue;
307  if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
308  trail.Info(var).trail_index < trail_index) {
309  activity += GetCoefficient(var);
310  }
311  }
312  return rhs_ - activity;
313 }
314 
317  int trail_index) {
318  Coefficient activity(0);
319  Coefficient removed_sum(0);
320  const Coefficient bound = max_sum_ - rhs_;
321  for (BooleanVariable var : PossibleNonZeros()) {
322  if (GetCoefficient(var) == 0) continue;
323  const Coefficient diff = GetCoefficient(var) - bound;
324  if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
325  trail.Info(var).trail_index < trail_index) {
326  if (diff > 0) {
327  removed_sum += diff;
328  terms_[var] = (terms_[var] > 0) ? bound : -bound;
329  }
330  activity += GetCoefficient(var);
331  } else {
332  // Because we assume the slack (rhs - activity) to be negative, we have
333  // coeff + rhs - max_sum_ <= coeff + rhs - (activity + coeff)
334  // <= slack
335  // < 0
336  CHECK_LE(diff, 0);
337  }
338  }
339  rhs_ -= removed_sum;
340  max_sum_ -= removed_sum;
341  DCHECK_EQ(max_sum_, ComputeMaxSum());
342  return rhs_ - activity;
343 }
344 
346  const Trail& trail, int trail_index, Coefficient initial_slack,
347  Coefficient target) {
348  // Positive slack.
349  const Coefficient slack = initial_slack;
350  DCHECK_EQ(slack, ComputeSlackForTrailPrefix(trail, trail_index));
351  CHECK_LE(target, slack);
352  CHECK_GE(target, 0);
353 
354  // This is not stricly needed, but true in our use case:
355  // The variable assigned at trail_index was causing a conflict.
356  const Coefficient coeff = GetCoefficient(trail[trail_index].Variable());
357  CHECK_LT(slack, coeff);
358 
359  // Nothing to do if the slack is already target.
360  if (slack == target) return;
361 
362  // Applies the algorithm described in the .h
363  const Coefficient diff = slack - target;
364  rhs_ -= diff;
365  for (BooleanVariable var : PossibleNonZeros()) {
366  if (GetCoefficient(var) == 0) continue;
367  if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
368  trail.Info(var).trail_index < trail_index) {
369  continue;
370  }
371  if (GetCoefficient(var) > diff) {
372  terms_[var] = (terms_[var] > 0) ? terms_[var] - diff : terms_[var] + diff;
373  max_sum_ -= diff;
374  } else {
375  max_sum_ -= GetCoefficient(var);
376  terms_[var] = 0;
377  }
378  }
379  DCHECK_EQ(max_sum_, ComputeMaxSum());
380 }
381 
383  std::vector<LiteralWithCoeff>* output) {
384  output->clear();
385  for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
386  const Coefficient coeff = GetCoefficient(var);
387  if (coeff != 0) {
388  output->push_back(LiteralWithCoeff(GetLiteral(var), GetCoefficient(var)));
389  }
390  }
391  std::sort(output->begin(), output->end(), CoeffComparator);
392 }
393 
394 Coefficient MutableUpperBoundedLinearConstraint::ComputeMaxSum() const {
395  Coefficient result(0);
396  for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
397  result += GetCoefficient(var);
398  }
399  return result;
400 }
401 
403  const std::vector<LiteralWithCoeff>& cst)
404  : is_marked_for_deletion_(false),
405  is_learned_(false),
406  first_reason_trail_index_(-1),
407  activity_(0.0) {
408  DCHECK(!cst.empty());
409  DCHECK(std::is_sorted(cst.begin(), cst.end(), CoeffComparator));
410  literals_.reserve(cst.size());
411 
412  // Reserve the space for coeffs_ and starts_ (it is slightly more efficient).
413  {
414  int size = 0;
415  Coefficient prev(0); // Ignore initial zeros.
416  for (LiteralWithCoeff term : cst) {
417  if (term.coefficient != prev) {
418  prev = term.coefficient;
419  ++size;
420  }
421  }
422  coeffs_.reserve(size);
423  starts_.reserve(size + 1);
424  }
425 
426  Coefficient prev(0);
427  for (LiteralWithCoeff term : cst) {
428  if (term.coefficient != prev) {
429  prev = term.coefficient;
430  coeffs_.push_back(term.coefficient);
431  starts_.push_back(literals_.size());
432  }
433  literals_.push_back(term.literal);
434  }
435 
436  // Sentinel.
437  starts_.push_back(literals_.size());
438 
439  hash_ = absl::Hash<std::vector<LiteralWithCoeff>>()(cst);
440 }
441 
444  int literal_index = 0;
445  int coeff_index = 0;
446  for (Literal literal : literals_) {
447  conflict->AddTerm(literal, coeffs_[coeff_index]);
448  ++literal_index;
449  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
450  }
451  conflict->AddToRhs(rhs_);
452 }
453 
455  const std::vector<LiteralWithCoeff>& cst) {
456  if (cst.size() != literals_.size()) return false;
457  int literal_index = 0;
458  int coeff_index = 0;
459  for (LiteralWithCoeff term : cst) {
460  if (literals_[literal_index] != term.literal) return false;
461  if (coeffs_[coeff_index] != term.coefficient) return false;
462  ++literal_index;
463  if (literal_index == starts_[coeff_index + 1]) {
464  ++coeff_index;
465  }
466  }
467  return true;
468 }
469 
471  Coefficient rhs, int trail_index, Coefficient* threshold, Trail* trail,
472  PbConstraintsEnqueueHelper* helper) {
473  // Compute the slack from the assigned variables with a trail index
474  // smaller than the given trail_index. The variable at trail_index has not
475  // yet been propagated.
476  rhs_ = rhs;
477  Coefficient slack = rhs;
478 
479  // sum_at_previous_level[i] is the sum of assigned literals with a level <
480  // i. Since we want the sums up to sum_at_previous_level[last_level + 1],
481  // the size of the vector must be last_level + 2.
482  const int last_level = trail->CurrentDecisionLevel();
483  std::vector<Coefficient> sum_at_previous_level(last_level + 2,
484  Coefficient(0));
485 
486  int max_relevant_trail_index = 0;
487  if (trail_index > 0) {
488  int literal_index = 0;
489  int coeff_index = 0;
490  for (Literal literal : literals_) {
491  const BooleanVariable var = literal.Variable();
492  const Coefficient coeff = coeffs_[coeff_index];
493  if (trail->Assignment().LiteralIsTrue(literal) &&
494  trail->Info(var).trail_index < trail_index) {
495  max_relevant_trail_index =
496  std::max(max_relevant_trail_index, trail->Info(var).trail_index);
497  slack -= coeff;
498  sum_at_previous_level[trail->Info(var).level + 1] += coeff;
499  }
500  ++literal_index;
501  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
502  }
503 
504  // The constraint is infeasible provided the current propagated trail.
505  if (slack < 0) return false;
506 
507  // Cumulative sum.
508  for (int i = 1; i < sum_at_previous_level.size(); ++i) {
509  sum_at_previous_level[i] += sum_at_previous_level[i - 1];
510  }
511  }
512 
513  // Check the no-propagation at earlier level precondition.
514  int literal_index = 0;
515  int coeff_index = 0;
516  for (Literal literal : literals_) {
517  const BooleanVariable var = literal.Variable();
518  const int level = trail->Assignment().VariableIsAssigned(var)
519  ? trail->Info(var).level
520  : last_level;
521  if (level > 0) {
522  CHECK_LE(coeffs_[coeff_index], rhs_ - sum_at_previous_level[level])
523  << "var should have been propagated at an earlier level !";
524  }
525  ++literal_index;
526  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
527  }
528 
529  // Initial propagation.
530  //
531  // TODO(user): The source trail index for the propagation reason (i.e.
532  // max_relevant_trail_index) may be higher than necessary (for some of the
533  // propagated literals). Currently this works with FillReason(), but it was a
534  // source of a really nasty bug (see CL 68906167) because of the (rhs == 1)
535  // optim. Find a good way to test the logic.
536  index_ = coeffs_.size() - 1;
537  already_propagated_end_ = literals_.size();
538  Update(slack, threshold);
539  return *threshold < 0
540  ? Propagate(max_relevant_trail_index, threshold, trail, helper)
541  : true;
542 }
543 
545  int trail_index, Coefficient* threshold, Trail* trail,
546  PbConstraintsEnqueueHelper* helper) {
547  DCHECK_LT(*threshold, 0);
548  const Coefficient slack = GetSlackFromThreshold(*threshold);
549  DCHECK_GE(slack, 0) << "The constraint is already a conflict!";
550  while (index_ >= 0 && coeffs_[index_] > slack) --index_;
551 
552  // Check propagation.
553  BooleanVariable first_propagated_variable(-1);
554  for (int i = starts_[index_ + 1]; i < already_propagated_end_; ++i) {
555  if (trail->Assignment().LiteralIsFalse(literals_[i])) continue;
556  if (trail->Assignment().LiteralIsTrue(literals_[i])) {
557  if (trail->Info(literals_[i].Variable()).trail_index > trail_index) {
558  // Conflict.
559  FillReason(*trail, trail_index, literals_[i].Variable(),
560  &helper->conflict);
561  helper->conflict.push_back(literals_[i].Negated());
562  Update(slack, threshold);
563  return false;
564  }
565  } else {
566  // Propagation.
567  if (first_propagated_variable < 0) {
568  if (first_reason_trail_index_ == -1) {
569  first_reason_trail_index_ = trail->Index();
570  }
571  helper->Enqueue(literals_[i].Negated(), trail_index, this, trail);
572  first_propagated_variable = literals_[i].Variable();
573  } else {
574  // Note that the reason for first_propagated_variable is always a
575  // valid reason for literals_[i].Variable() because we process the
576  // variable in increasing coefficient order.
577  trail->EnqueueWithSameReasonAs(literals_[i].Negated(),
578  first_propagated_variable);
579  }
580  }
581  }
582  Update(slack, threshold);
583  DCHECK_GE(*threshold, 0);
584  return true;
585 }
586 
588  const Trail& trail, int source_trail_index,
589  BooleanVariable propagated_variable, std::vector<Literal>* reason) {
590  reason->clear();
591 
592  // Optimization for an "at most one" constraint.
593  // Note that the source_trail_index set by InitializeRhs() is ok in this case.
594  if (rhs_ == 1) {
595  reason->push_back(trail[source_trail_index].Negated());
596  return;
597  }
598 
599  // Optimization: This will be set to the index of the last literal in the
600  // reason.
601  int last_i = 0;
602  int last_coeff_index = 0;
603 
604  // Compute the initial reason which is formed by all the literals of the
605  // constraint that were assigned to true at the time of the propagation.
606  // We remove literals with a level of 0 since they are not needed.
607  // We also compute the slack at the time.
608  Coefficient slack = rhs_;
609  Coefficient propagated_variable_coefficient(0);
610  int coeff_index = coeffs_.size() - 1;
611  for (int i = literals_.size() - 1; i >= 0; --i) {
612  const Literal literal = literals_[i];
613  if (literal.Variable() == propagated_variable) {
614  propagated_variable_coefficient = coeffs_[coeff_index];
615  } else {
616  if (trail.Assignment().LiteralIsTrue(literal) &&
617  trail.Info(literal.Variable()).trail_index <= source_trail_index) {
618  if (trail.Info(literal.Variable()).level > 0) {
619  reason->push_back(literal.Negated());
620  last_i = i;
621  last_coeff_index = coeff_index;
622  }
623  slack -= coeffs_[coeff_index];
624  }
625  }
626  if (i == starts_[coeff_index]) {
627  --coeff_index;
628  }
629  }
630  DCHECK_GT(propagated_variable_coefficient, slack);
631  DCHECK_GE(propagated_variable_coefficient, 0);
632 
633  // In both cases, we can't minimize the reason further.
634  if (reason->size() <= 1 || coeffs_.size() == 1) return;
635 
636  Coefficient limit = propagated_variable_coefficient - slack;
637  DCHECK_GE(limit, 1);
638 
639  // Remove literals with small coefficients from the reason as long as the
640  // limit is still stricly positive.
641  coeff_index = last_coeff_index;
642  if (coeffs_[coeff_index] >= limit) return;
643  for (int i = last_i; i < literals_.size(); ++i) {
644  const Literal literal = literals_[i];
645  if (i == starts_[coeff_index + 1]) {
646  ++coeff_index;
647  if (coeffs_[coeff_index] >= limit) break;
648  }
649  if (literal.Negated() != reason->back()) continue;
650  limit -= coeffs_[coeff_index];
651  reason->pop_back();
652  if (coeffs_[coeff_index] >= limit) break;
653  }
654  DCHECK(!reason->empty());
655  DCHECK_GE(limit, 1);
656 }
657 
659  const Trail& trail, int trail_index,
660  const MutableUpperBoundedLinearConstraint& conflict) {
661  Coefficient result(0);
662  int literal_index = 0;
663  int coeff_index = 0;
664  for (Literal literal : literals_) {
665  if (!trail.Assignment().VariableIsAssigned(literal.Variable()) ||
666  trail.Info(literal.Variable()).trail_index >= trail_index) {
667  result += conflict.CancelationAmount(literal, coeffs_[coeff_index]);
668  }
669  ++literal_index;
670  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
671  }
672  return result;
673 }
674 
676  const Trail& trail, BooleanVariable var,
678  Coefficient* conflict_slack) {
679  const int limit_trail_index = trail.Info(var).trail_index;
680 
681  // Compute the constraint activity at the time and the coefficient of the
682  // variable var.
684  Coefficient var_coeff(0);
685  int literal_index = 0;
686  int coeff_index = 0;
687  for (Literal literal : literals_) {
688  if (literal.Variable() == var) {
689  // The variable must be of the opposite sign in the current conflict.
690  CHECK_NE(literal, conflict->GetLiteral(var));
691  var_coeff = coeffs_[coeff_index];
692  } else if (trail.Assignment().LiteralIsTrue(literal) &&
693  trail.Info(literal.Variable()).trail_index < limit_trail_index) {
694  activity += coeffs_[coeff_index];
695  }
696  ++literal_index;
697  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
698  }
699 
700  // Special case.
701  if (activity > rhs_) {
702  // This constraint is already a conflict.
703  // Use this one instead to start the resolution.
704  //
705  // TODO(user): Investigate if this is a good idea. It doesn't happen often,
706  // but does happened. Maybe we can detect this before in Propagate()? The
707  // setup is:
708  // - At a given trail_index, var is propagated and added on the trail.
709  // - There is some constraint literals assigned to true with a trail index
710  // in (trail_index, var.trail_index).
711  // - Their sum is high enough to cause a conflict.
712  // - But individually, their coefficients are too small to be propagated, so
713  // the conflict is not yet detected. It will be when these variables are
714  // processed by PropagateNext().
715  conflict->ClearAll();
716  AddToConflict(conflict);
717  *conflict_slack = rhs_ - activity;
718  DCHECK_EQ(*conflict_slack,
719  conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
720  return;
721  }
722 
723  // This is the slack of *this for the trail prefix < limit_trail_index.
724  const Coefficient slack = rhs_ - activity;
725  CHECK_GE(slack, 0);
726 
727  // This is the slack of the conflict at the same level.
728  DCHECK_EQ(*conflict_slack,
729  conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
730 
731  // TODO(user): If there is more "cancelation" than the min_coeffs below when
732  // we add the two constraints, the resulting slack may be even lower. Taking
733  // that into account is probably good.
734  const Coefficient cancelation =
735  DEBUG_MODE ? ComputeCancelation(trail, limit_trail_index, *conflict)
736  : Coefficient(0);
737 
738  // When we add the two constraints together, the slack of the result for the
739  // trail < limit_trail_index - 1 must be negative. We know that its value is
740  // <= slack1 + slack2 - min(coeffs), so we have nothing to do if this bound is
741  // already negative.
742  const Coefficient conflict_var_coeff = conflict->GetCoefficient(var);
743  const Coefficient min_coeffs = std::min(var_coeff, conflict_var_coeff);
744  const Coefficient new_slack_ub = slack + *conflict_slack - min_coeffs;
745  CHECK_LT(*conflict_slack, conflict_var_coeff);
746  CHECK_LT(slack, var_coeff);
747  if (new_slack_ub < 0) {
748  AddToConflict(conflict);
749  DCHECK_EQ(*conflict_slack + slack - cancelation,
750  conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
751  return;
752  }
753 
754  // We need to relax one or both of the constraints so the new slack is < 0.
755  // Using the relaxation described in ReduceSlackTo(), we can have this new
756  // slack bound:
757  //
758  // (slack - diff) + (conflict_slack - conflict_diff)
759  // - min(var_coeff - diff, conflict_var_coeff - conflict_diff).
760  //
761  // For all diff in [0, slack)
762  // For all conflict_diff in [0, conflict_slack)
763  Coefficient diff(0);
764  Coefficient conflict_diff(0);
765 
766  // Is relaxing the constraint with the highest coeff enough?
767  if (new_slack_ub < std::max(var_coeff, conflict_var_coeff) - min_coeffs) {
768  const Coefficient reduc = new_slack_ub + 1;
769  if (var_coeff < conflict_var_coeff) {
770  conflict_diff += reduc;
771  } else {
772  diff += reduc;
773  }
774  } else {
775  // Just reduce the slack of both constraints to zero.
776  //
777  // TODO(user): The best will be to relax as little as possible.
778  diff = slack;
779  conflict_diff = *conflict_slack;
780  }
781 
782  // Relax the conflict.
783  CHECK_GE(conflict_diff, 0);
784  CHECK_LE(conflict_diff, *conflict_slack);
785  if (conflict_diff > 0) {
786  conflict->ReduceSlackTo(trail, limit_trail_index, *conflict_slack,
787  *conflict_slack - conflict_diff);
788  *conflict_slack -= conflict_diff;
789  }
790 
791  // We apply the same algorithm as the one in ReduceSlackTo() but on
792  // the non-mutable representation and add it on the fly into conflict.
793  CHECK_GE(diff, 0);
794  CHECK_LE(diff, slack);
795  if (diff == 0) {
796  // Special case if there if no relaxation is needed.
797  AddToConflict(conflict);
798  return;
799  }
800 
801  literal_index = 0;
802  coeff_index = 0;
803  for (Literal literal : literals_) {
804  if (trail.Assignment().LiteralIsTrue(literal) &&
805  trail.Info(literal.Variable()).trail_index < limit_trail_index) {
806  conflict->AddTerm(literal, coeffs_[coeff_index]);
807  } else {
808  const Coefficient new_coeff = coeffs_[coeff_index] - diff;
809  if (new_coeff > 0) {
810  // TODO(user): track the cancelation here so we can update
811  // *conflict_slack properly.
812  conflict->AddTerm(literal, new_coeff);
813  }
814  }
815  ++literal_index;
816  if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
817  }
818 
819  // And the rhs.
820  conflict->AddToRhs(rhs_ - diff);
821 }
822 
824  int trail_index) {
825  const Coefficient slack = GetSlackFromThreshold(*threshold);
826  while (index_ + 1 < coeffs_.size() && coeffs_[index_ + 1] <= slack) ++index_;
827  Update(slack, threshold);
828  if (first_reason_trail_index_ >= trail_index) {
829  first_reason_trail_index_ = -1;
830  }
831 }
832 
833 // TODO(user): This is relatively slow. Take the "transpose" all at once, and
834 // maybe put small constraints first on the to_update_ lists.
835 bool PbConstraints::AddConstraint(const std::vector<LiteralWithCoeff>& cst,
836  Coefficient rhs, Trail* trail) {
837  SCOPED_TIME_STAT(&stats_);
838  DCHECK(!cst.empty());
839  DCHECK(std::is_sorted(cst.begin(), cst.end(), CoeffComparator));
840 
841  // Special case if this is the first constraint.
842  if (constraints_.empty()) {
843  to_update_.resize(trail->NumVariables() << 1);
844  enqueue_helper_.propagator_id = propagator_id_;
845  enqueue_helper_.reasons.resize(trail->NumVariables());
846  propagation_trail_index_ = trail->Index();
847  }
848 
849  std::unique_ptr<UpperBoundedLinearConstraint> c(
851  std::vector<UpperBoundedLinearConstraint*>& duplicate_candidates =
852  possible_duplicates_[c->hash()];
853 
854  // Optimization if the constraint terms are duplicates.
855  for (UpperBoundedLinearConstraint* candidate : duplicate_candidates) {
856  if (candidate->HasIdenticalTerms(cst)) {
857  if (rhs < candidate->Rhs()) {
858  // TODO(user): the index is needed to give the correct thresholds_ entry
859  // to InitializeRhs() below, but this linear scan is not super
860  // efficient.
861  ConstraintIndex i(0);
862  while (i < constraints_.size() &&
863  constraints_[i.value()].get() != candidate) {
864  ++i;
865  }
866  CHECK_LT(i, constraints_.size());
867  return candidate->InitializeRhs(rhs, propagation_trail_index_,
868  &thresholds_[i], trail,
869  &enqueue_helper_);
870  } else {
871  // The constraint is redundant, so there is nothing to do.
872  return true;
873  }
874  }
875  }
876 
877  thresholds_.push_back(Coefficient(0));
878  if (!c->InitializeRhs(rhs, propagation_trail_index_, &thresholds_.back(),
879  trail, &enqueue_helper_)) {
880  thresholds_.pop_back();
881  return false;
882  }
883 
884  const ConstraintIndex cst_index(constraints_.size());
885  duplicate_candidates.push_back(c.get());
886  constraints_.emplace_back(c.release());
887  for (LiteralWithCoeff term : cst) {
888  DCHECK_LT(term.literal.Index(), to_update_.size());
889  to_update_[term.literal.Index()].push_back(ConstraintIndexWithCoeff(
890  trail->Assignment().VariableIsAssigned(term.literal.Variable()),
891  cst_index, term.coefficient));
892  }
893  return true;
894 }
895 
897  const std::vector<LiteralWithCoeff>& cst, Coefficient rhs, Trail* trail) {
898  DeleteSomeLearnedConstraintIfNeeded();
899  const int old_num_constraints = constraints_.size();
900  const bool result = AddConstraint(cst, rhs, trail);
901 
902  // The second test is to avoid marking a problem constraint as learned because
903  // of the "reuse last constraint" optimization.
904  if (result && constraints_.size() > old_num_constraints) {
905  constraints_.back()->set_is_learned(true);
906  }
907  return result;
908 }
909 
910 bool PbConstraints::PropagateNext(Trail* trail) {
911  SCOPED_TIME_STAT(&stats_);
912  const int source_trail_index = propagation_trail_index_;
913  const Literal true_literal = (*trail)[propagation_trail_index_];
915 
916  // We need to update ALL threshold, otherwise the Untrail() will not be
917  // synchronized.
918  bool conflict = false;
919  num_threshold_updates_ += to_update_[true_literal.Index()].size();
920  for (ConstraintIndexWithCoeff& update : to_update_[true_literal.Index()]) {
921  const Coefficient threshold =
922  thresholds_[update.index] - update.coefficient;
923  thresholds_[update.index] = threshold;
924  if (threshold < 0 && !conflict) {
925  UpperBoundedLinearConstraint* const cst =
926  constraints_[update.index.value()].get();
927  update.need_untrail_inspection = true;
928  ++num_constraint_lookups_;
929  const int old_value = cst->already_propagated_end();
930  if (!cst->Propagate(source_trail_index, &thresholds_[update.index], trail,
931  &enqueue_helper_)) {
932  trail->MutableConflict()->swap(enqueue_helper_.conflict);
933  conflicting_constraint_index_ = update.index;
934  conflict = true;
935 
936  // We bump the activity of the conflict.
937  BumpActivity(constraints_[update.index.value()].get());
938  }
939  num_inspected_constraint_literals_ +=
940  old_value - cst->already_propagated_end();
941  }
942  }
943  return !conflict;
944 }
945 
947  const int old_index = trail->Index();
948  while (trail->Index() == old_index && propagation_trail_index_ < old_index) {
949  if (!PropagateNext(trail)) return false;
950  }
951  return true;
952 }
953 
954 void PbConstraints::Untrail(const Trail& trail, int trail_index) {
955  SCOPED_TIME_STAT(&stats_);
956  to_untrail_.ClearAndResize(ConstraintIndex(constraints_.size()));
957  while (propagation_trail_index_ > trail_index) {
960  for (ConstraintIndexWithCoeff& update : to_update_[literal.Index()]) {
961  thresholds_[update.index] += update.coefficient;
962 
963  // Only the constraints which were inspected during Propagate() need
964  // inspection during Untrail().
965  if (update.need_untrail_inspection) {
966  update.need_untrail_inspection = false;
967  to_untrail_.Set(update.index);
968  }
969  }
970  }
971  for (ConstraintIndex cst_index : to_untrail_.PositionsSetAtLeastOnce()) {
972  constraints_[cst_index.value()]->Untrail(&(thresholds_[cst_index]),
973  trail_index);
974  }
975 }
976 
977 absl::Span<const Literal> PbConstraints::Reason(const Trail& trail,
978  int trail_index) const {
979  SCOPED_TIME_STAT(&stats_);
980  const PbConstraintsEnqueueHelper::ReasonInfo& reason_info =
981  enqueue_helper_.reasons[trail_index];
982  std::vector<Literal>* reason = trail.GetEmptyVectorToStoreReason(trail_index);
983  reason_info.pb_constraint->FillReason(trail, reason_info.source_trail_index,
984  trail[trail_index].Variable(), reason);
985  return *reason;
986 }
987 
989  int trail_index) const {
990  const PbConstraintsEnqueueHelper::ReasonInfo& reason_info =
991  enqueue_helper_.reasons[trail_index];
992  return reason_info.pb_constraint;
993 }
994 
995 // TODO(user): Because num_constraints also include problem constraints, the
996 // policy may not be what we want if there is a big number of problem
997 // constraints. Fix this.
998 void PbConstraints::ComputeNewLearnedConstraintLimit() {
999  const int num_constraints = constraints_.size();
1000  target_number_of_learned_constraint_ =
1001  num_constraints + parameters_->pb_cleanup_increment();
1002  num_learned_constraint_before_cleanup_ =
1003  static_cast<int>(target_number_of_learned_constraint_ /
1004  parameters_->pb_cleanup_ratio()) -
1005  num_constraints;
1006 }
1007 
1008 void PbConstraints::DeleteSomeLearnedConstraintIfNeeded() {
1009  if (num_learned_constraint_before_cleanup_ == 0) {
1010  // First time.
1011  ComputeNewLearnedConstraintLimit();
1012  return;
1013  }
1014  --num_learned_constraint_before_cleanup_;
1015  if (num_learned_constraint_before_cleanup_ > 0) return;
1016  SCOPED_TIME_STAT(&stats_);
1017 
1018  // Mark the constraint that needs to be deleted.
1019  // We do that in two pass, first we extract the activities.
1020  std::vector<double> activities;
1021  for (int i = 0; i < constraints_.size(); ++i) {
1022  const UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1023  if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1024  activities.push_back(constraint.activity());
1025  }
1026  }
1027 
1028  // Then we compute the cutoff threshold.
1029  // Note that we can't delete constraint used as a reason!!
1030  std::sort(activities.begin(), activities.end());
1031  const int num_constraints_to_delete =
1032  constraints_.size() - target_number_of_learned_constraint_;
1033  CHECK_GT(num_constraints_to_delete, 0);
1034  if (num_constraints_to_delete >= activities.size()) {
1035  // Unlikely, but may happen, so in this case, we just delete all the
1036  // constraint that can possibly be deleted
1037  for (int i = 0; i < constraints_.size(); ++i) {
1038  UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1039  if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1040  constraint.MarkForDeletion();
1041  }
1042  }
1043  } else {
1044  const double limit_activity = activities[num_constraints_to_delete];
1045  int num_constraint_at_limit_activity = 0;
1046  for (int i = num_constraints_to_delete; i >= 0; --i) {
1047  if (activities[i] == limit_activity) {
1048  ++num_constraint_at_limit_activity;
1049  } else {
1050  break;
1051  }
1052  }
1053 
1054  // Mark for deletion all the constraints under this threshold.
1055  // We only keep the most recent constraint amongst the one with the activity
1056  // exactly equal ot limit_activity, it is why the loop is in the reverse
1057  // order.
1058  for (int i = constraints_.size() - 1; i >= 0; --i) {
1059  UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1060  if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1061  if (constraint.activity() <= limit_activity) {
1062  if (constraint.activity() == limit_activity &&
1063  num_constraint_at_limit_activity > 0) {
1064  --num_constraint_at_limit_activity;
1065  } else {
1066  constraint.MarkForDeletion();
1067  }
1068  }
1069  }
1070  }
1071  }
1072 
1073  // Finally we delete the marked constraint and compute the next limit.
1074  DeleteConstraintMarkedForDeletion();
1075  ComputeNewLearnedConstraintLimit();
1076 }
1077 
1079  if (!constraint->is_learned()) return;
1080  const double max_activity = parameters_->max_clause_activity_value();
1081  constraint->set_activity(constraint->activity() +
1082  constraint_activity_increment_);
1083  if (constraint->activity() > max_activity) {
1084  RescaleActivities(1.0 / max_activity);
1085  }
1086 }
1087 
1088 void PbConstraints::RescaleActivities(double scaling_factor) {
1089  constraint_activity_increment_ *= scaling_factor;
1090  for (int i = 0; i < constraints_.size(); ++i) {
1091  constraints_[i]->set_activity(constraints_[i]->activity() * scaling_factor);
1092  }
1093 }
1094 
1096  const double decay = parameters_->clause_activity_decay();
1097  constraint_activity_increment_ *= 1.0 / decay;
1098 }
1099 
1100 void PbConstraints::DeleteConstraintMarkedForDeletion() {
1102  constraints_.size(), ConstraintIndex(-1));
1103  ConstraintIndex new_index(0);
1104  for (ConstraintIndex i(0); i < constraints_.size(); ++i) {
1105  if (!constraints_[i.value()]->is_marked_for_deletion()) {
1106  index_mapping[i] = new_index;
1107  if (new_index < i) {
1108  constraints_[new_index.value()] = std::move(constraints_[i.value()]);
1109  thresholds_[new_index] = thresholds_[i];
1110  }
1111  ++new_index;
1112  } else {
1113  // Remove it from possible_duplicates_.
1114  UpperBoundedLinearConstraint* c = constraints_[i.value()].get();
1115  std::vector<UpperBoundedLinearConstraint*>& ref =
1116  possible_duplicates_[c->hash()];
1117  for (int i = 0; i < ref.size(); ++i) {
1118  if (ref[i] == c) {
1119  std::swap(ref[i], ref.back());
1120  ref.pop_back();
1121  break;
1122  }
1123  }
1124  }
1125  }
1126  constraints_.resize(new_index.value());
1127  thresholds_.resize(new_index.value());
1128 
1129  // This is the slow part, we need to remap all the ConstraintIndex to the
1130  // new ones.
1131  for (LiteralIndex lit(0); lit < to_update_.size(); ++lit) {
1132  std::vector<ConstraintIndexWithCoeff>& updates = to_update_[lit];
1133  int new_index = 0;
1134  for (int i = 0; i < updates.size(); ++i) {
1135  const ConstraintIndex m = index_mapping[updates[i].index];
1136  if (m != -1) {
1137  updates[new_index] = updates[i];
1138  updates[new_index].index = m;
1139  ++new_index;
1140  }
1141  }
1142  updates.resize(new_index);
1143  }
1144 }
1145 
1146 } // namespace sat
1147 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
size_type size() const
void push_back(const value_type &x)
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:806
void Set(IntegerType index)
Definition: bitset.h:792
void ClearAndResize(IntegerType size)
Definition: bitset.h:767
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
LiteralIndex Index() const
Definition: sat_base.h:90
BooleanVariable Variable() const
Definition: sat_base.h:86
Coefficient ComputeSlackForTrailPrefix(const Trail &trail, int trail_index) const
Coefficient ReduceCoefficientsAndComputeSlackForTrailPrefix(const Trail &trail, int trail_index)
void ReduceSlackTo(const Trail &trail, int trail_index, Coefficient initial_slack, Coefficient target)
const std::vector< BooleanVariable > & PossibleNonZeros() const
Coefficient CancelationAmount(Literal literal, Coefficient coeff) const
void AddTerm(Literal literal, Coefficient coeff)
void CopyIntoVector(std::vector< LiteralWithCoeff > *output)
Coefficient GetCoefficient(BooleanVariable var) const
void RescaleActivities(double scaling_factor)
absl::Span< const Literal > Reason(const Trail &trail, int trail_index) const final
bool AddConstraint(const std::vector< LiteralWithCoeff > &cst, Coefficient rhs, Trail *trail)
UpperBoundedLinearConstraint * ReasonPbConstraint(int trail_index) const
void BumpActivity(UpperBoundedLinearConstraint *constraint)
void Untrail(const Trail &trail, int trail_index) final
bool AddLearnedConstraint(const std::vector< LiteralWithCoeff > &cst, Coefficient rhs, Trail *trail)
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:403
void EnqueueWithSameReasonAs(Literal true_literal, BooleanVariable reference_var)
Definition: sat_base.h:284
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition: sat_base.h:332
std::vector< Literal > * MutableConflict()
Definition: sat_base.h:373
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
Coefficient ComputeCancelation(const Trail &trail, int trail_index, const MutableUpperBoundedLinearConstraint &conflict)
bool Propagate(int trail_index, Coefficient *threshold, Trail *trail, PbConstraintsEnqueueHelper *helper)
void FillReason(const Trail &trail, int source_trail_index, BooleanVariable propagated_variable, std::vector< Literal > *reason)
bool HasIdenticalTerms(const std::vector< LiteralWithCoeff > &cst)
void ResolvePBConflict(const Trail &trail, BooleanVariable var, MutableUpperBoundedLinearConstraint *conflict, Coefficient *conflict_slack)
bool InitializeRhs(Coefficient rhs, int trail_index, Coefficient *threshold, Trail *trail, PbConstraintsEnqueueHelper *helper)
void Untrail(Coefficient *threshold, int trail_index)
void AddToConflict(MutableUpperBoundedLinearConstraint *conflict)
UpperBoundedLinearConstraint(const std::vector< LiteralWithCoeff > &cst)
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:172
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:164
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:161
int64_t b
int64_t a
int64_t value
IntVar * var
Definition: expr_array.cc:1874
int index
const bool DEBUG_MODE
Definition: macros.h:24
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
std::tuple< int64_t, int64_t, const double > Coefficient
Coefficient ComputeCanonicalRhs(Coefficient upper_bound, Coefficient bound_shift, Coefficient max_value)
bool ApplyLiteralMapping(const absl::StrongVector< LiteralIndex, LiteralIndex > &mapping, std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
Coefficient ComputeNegatedCanonicalRhs(Coefficient lower_bound, Coefficient bound_shift, Coefficient max_value)
void SimplifyCanonicalBooleanLinearConstraint(std::vector< LiteralWithCoeff > *cst, Coefficient *rhs)
const LiteralIndex kTrueLiteralIndex(-2)
bool ComputeBooleanLinearExpressionCanonicalForm(std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
const LiteralIndex kFalseLiteralIndex(-3)
bool BooleanLinearExpressionIsCanonical(const std::vector< LiteralWithCoeff > &cst)
Collection of objects used to extend the Constraint Solver library.
bool SafeAddInto(IntegerType a, IntegerType *b)
Literal literal
Definition: optimization.cc:88
ColIndex representative
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int64_t bound
int64_t coefficient
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:439
void Enqueue(Literal l, int source_trail_index, UpperBoundedLinearConstraint *ct, Trail *trail)