OR-Tools  9.6
linear_propagation.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 <functional>
18 #include <limits>
19 #include <ostream>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 namespace operations_research {
25 namespace sat {
26 
28  CHECK_GE(n, pos_.size());
29  CHECK_LE(left_, right_);
30  pos_.resize(n, -1);
31  tmp_positions_.resize(n, 0);
32 
33  // We need 1 more space since we can add at most n element.
34  queue_.resize(n + 1, 0);
35 }
36 
38  DCHECK(!empty());
39  const int id = queue_[left_];
40  pos_[id] = -1;
41  ++left_;
42  if (left_ == queue_.size()) left_ = 0;
43  return id;
44 }
45 
46 void CustomFifoQueue::Push(int id) {
47  DCHECK_GE(id, 0);
48  DCHECK_LE(id, pos_.size());
49  DCHECK(!Contains(id));
50  pos_[id] = right_;
51  queue_[right_] = id;
52  ++right_;
53  if (right_ == queue_.size()) right_ = 0;
54 }
55 
56 void CustomFifoQueue::Reorder(absl::Span<const int> order) {
57  if (order.size() <= 1) return;
58 
59  const int capacity = queue_.size();
60  const int size = left_ < right_ ? right_ - left_ : left_ + capacity - right_;
61  if (order.size() > size / 8) {
62  return ReorderDense(order);
63  }
64 
65  int index = 0;
66  for (const int id : order) {
67  const int p = pos_[id];
68  DCHECK_GE(p, 0);
69  tmp_positions_[index++] = p >= left_ ? p : p + capacity;
70  }
71  std::sort(&tmp_positions_[0], &tmp_positions_[index]);
72  DCHECK(std::unique(&tmp_positions_[0], &tmp_positions_[index]) ==
73  &tmp_positions_[index]);
74 
75  index = 0;
76  for (const int id : order) {
77  int p = tmp_positions_[index++];
78  if (p >= capacity) p -= capacity;
79  pos_[id] = p;
80  queue_[p] = id;
81  }
82 }
83 
84 void CustomFifoQueue::ReorderDense(absl::Span<const int> order) {
85  for (const int id : order) {
86  DCHECK_GE(pos_[id], 0);
87  queue_[pos_[id]] = -1;
88  }
89  int order_index = 0;
90  if (left_ <= right_) {
91  for (int i = left_; i < right_; ++i) {
92  if (queue_[i] == -1) {
93  queue_[i] = order[order_index++];
94  pos_[queue_[i]] = i;
95  }
96  }
97  } else {
98  const int size = queue_.size();
99  for (int i = left_; i < size; ++i) {
100  if (queue_[i] == -1) {
101  queue_[i] = order[order_index++];
102  pos_[queue_[i]] = i;
103  }
104  }
105  for (int i = 0; i < left_; ++i) {
106  if (queue_[i] == -1) {
107  queue_[i] = order[order_index++];
108  pos_[queue_[i]] = i;
109  }
110  }
111  }
112  DCHECK_EQ(order_index, order.size());
113 }
114 
115 void CustomFifoQueue::SortByPos(absl::Span<int> elements) {
116  std::sort(elements.begin(), elements.end(),
117  [this](const int id1, const int id2) {
118  const int p1 = pos_[id1];
119  const int p2 = pos_[id2];
120  if (p1 >= left_) {
121  if (p2 >= left_) return p1 < p2;
122  return true;
123  } else {
124  // p1 < left_.
125  if (p2 < left_) return p1 < p2;
126  return false;
127  }
128  });
129 }
130 
131 std::ostream& operator<<(std::ostream& os, const EnforcementStatus& e) {
132  switch (e) {
134  os << "IS_FALSE";
135  break;
137  os << "CANNOT_PROPAGATE";
138  break;
140  os << "CAN_PROPAGATE";
141  break;
143  os << "IS_ENFORCED";
144  break;
145  }
146  return os;
147 }
148 
149 EnforcementPropagator::EnforcementPropagator(Model* model)
150  : SatPropagator("EnforcementPropagator"),
151  trail_(*model->GetOrCreate<Trail>()),
152  assignment_(trail_.Assignment()),
153  integer_trail_(model->GetOrCreate<IntegerTrail>()),
154  rev_int_repository_(model->GetOrCreate<RevIntRepository>()) {
155  // Note that this will be after the integer trail since rev_int_repository_
156  // depends on IntegerTrail.
157  model->GetOrCreate<SatSolver>()->AddPropagator(this);
158 
159  // Sentinel - also start of next Register().
160  starts_.push_back(0);
161 }
162 
164  rev_int_repository_->SaveStateWithStamp(&rev_stack_size_, &rev_stamp_);
165  while (propagation_trail_index_ < trail_.Index()) {
166  const Literal literal = trail_[propagation_trail_index_++];
167  if (literal.Index() >= static_cast<int>(watcher_.size())) continue;
168 
169  int new_size = 0;
170  auto& watch_list = watcher_[literal.Index()];
171  for (const EnforcementId id : watch_list) {
172  const LiteralIndex index = ProcessIdOnTrue(literal, id);
173  if (index == kNoLiteralIndex) {
174  // We keep the same watcher.
175  watch_list[new_size++] = id;
176  } else {
177  // Change the watcher.
178  CHECK_NE(index, literal.Index());
179  watcher_[index].push_back(id);
180  }
181  }
182  watch_list.resize(new_size);
183 
184  // We also mark some constraint false.
185  for (const EnforcementId id : watcher_[literal.NegatedIndex()]) {
186  ChangeStatus(id, EnforcementStatus::IS_FALSE);
187  }
188  }
189  rev_stack_size_ = static_cast<int>(untrail_stack_.size());
190  return true;
191 }
192 
193 void EnforcementPropagator::Untrail(const Trail& /*trail*/, int trail_index) {
194  // Simply revert the status change.
195  const int size = static_cast<int>(untrail_stack_.size());
196  for (int i = size - 1; i >= rev_stack_size_; --i) {
197  const auto [id, status] = untrail_stack_[i];
198  statuses_[id] = status;
199  if (callbacks_[id] != nullptr) callbacks_[id](status);
200  }
201  untrail_stack_.resize(rev_stack_size_);
202  propagation_trail_index_ = trail_index;
203 }
204 
205 // Adds a new constraint to the class and returns the constraint id.
206 //
207 // Note that we accept empty enforcement list so that client code can be used
208 // regardless of the presence of enforcement or not. A negative id means the
209 // constraint is never enforced, and should be ignored.
211  absl::Span<const Literal> enforcement,
212  std::function<void(EnforcementStatus)> callback) {
213  CHECK_EQ(trail_.CurrentDecisionLevel(), 0);
214  int num_true = 0;
215  int num_false = 0;
216  temp_literals_.clear();
217  for (const Literal l : enforcement) {
218  // Make sure we always have enough room for the literal and its negation.
219  const int size = std::max(l.Index().value(), l.NegatedIndex().value()) + 1;
220  if (size > static_cast<int>(watcher_.size())) {
221  watcher_.resize(size);
222  }
223  if (assignment_.LiteralIsTrue(l)) {
224  ++num_true;
225  continue;
226  }
227  if (assignment_.LiteralIsFalse(l)) {
228  ++num_false;
229  continue;
230  }
231  temp_literals_.push_back(l);
232  }
233  gtl::STLSortAndRemoveDuplicates(&temp_literals_);
234 
235  // Return special indices if never/always enforced.
236  if (num_false > 0) {
238  return EnforcementId(-1);
239  }
240  if (num_true == enforcement.size()) {
242  return EnforcementId(-1);
243  }
244 
245  const EnforcementId id(static_cast<int>(callbacks_.size()));
246  callbacks_.push_back(std::move(callback));
247 
248  CHECK(!temp_literals_.empty());
249  buffer_.insert(buffer_.end(), temp_literals_.begin(), temp_literals_.end());
250  starts_.push_back(buffer_.size()); // Sentinel.
252 
253  if (temp_literals_.size() == 1) {
254  watcher_[temp_literals_[0].Index()].push_back(id);
255  ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
256  } else {
257  watcher_[temp_literals_[0].Index()].push_back(id);
258  watcher_[temp_literals_[1].Index()].push_back(id);
259  }
260  return id;
261 }
262 
263 // Add the enforcement reason to the given vector.
265  EnforcementId id, std::vector<Literal>* reason) const {
266  for (const Literal l : GetSpan(id)) {
267  reason->push_back(l.Negated());
268  }
269 }
270 
271 // Try to propagate when the enforced constraint is not satisfiable.
272 // This is currently in O(enforcement_size);
274  EnforcementId id, absl::Span<const Literal> literal_reason,
275  absl::Span<const IntegerLiteral> integer_reason) {
276  temp_reason_.clear();
277  LiteralIndex unique_unassigned = kNoLiteralIndex;
278  for (const Literal l : GetSpan(id)) {
279  if (assignment_.LiteralIsFalse(l)) return true;
280  if (assignment_.LiteralIsTrue(l)) {
281  temp_reason_.push_back(l.Negated());
282  continue;
283  }
284  if (unique_unassigned != kNoLiteralIndex) return true;
285  unique_unassigned = l.Index();
286  }
287 
288  temp_reason_.insert(temp_reason_.end(), literal_reason.begin(),
289  literal_reason.end());
290  if (unique_unassigned == kNoLiteralIndex) {
291  return integer_trail_->ReportConflict(temp_reason_, integer_reason);
292  }
293 
294  integer_trail_->EnqueueLiteral(Literal(unique_unassigned).Negated(),
295  temp_reason_, integer_reason);
296  return true;
297 }
298 
299 absl::Span<Literal> EnforcementPropagator::GetSpan(EnforcementId id) {
300  if (id < 0) return {};
301  DCHECK_LE(id + 1, starts_.size());
302  const int size = starts_[id + 1] - starts_[id];
303  DCHECK_NE(size, 0);
304  return absl::MakeSpan(&buffer_[starts_[id]], size);
305 }
306 
307 absl::Span<const Literal> EnforcementPropagator::GetSpan(
308  EnforcementId id) const {
309  if (id < 0) return {};
310  DCHECK_LE(id + 1, starts_.size());
311  const int size = starts_[id + 1] - starts_[id];
312  DCHECK_NE(size, 0);
313  return absl::MakeSpan(&buffer_[starts_[id]], size);
314 }
315 
316 LiteralIndex EnforcementPropagator::ProcessIdOnTrue(Literal watched,
317  EnforcementId id) {
318  const EnforcementStatus status = statuses_[id];
320 
321  const auto span = GetSpan(id);
322  if (span.size() == 1) {
324  ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
325  return kNoLiteralIndex;
326  }
327 
328  const int watched_pos = (span[0] == watched) ? 0 : 1;
329  CHECK_EQ(span[watched_pos], watched);
330  if (assignment_.LiteralIsFalse(span[watched_pos ^ 1])) {
331  ChangeStatus(id, EnforcementStatus::IS_FALSE);
332  return kNoLiteralIndex;
333  }
334 
335  for (int i = 2; i < span.size(); ++i) {
336  const Literal l = span[i];
337  if (assignment_.LiteralIsFalse(l)) {
338  ChangeStatus(id, EnforcementStatus::IS_FALSE);
339  return kNoLiteralIndex;
340  }
341  if (!assignment_.LiteralIsAssigned(l)) {
342  // Replace the watched literal. Note that if the other watched literal is
343  // true, it should be processed afterwards. We do not change the status
344  std::swap(span[watched_pos], span[i]);
345  return span[watched_pos].Index();
346  }
347  }
348 
349  // All literal with index > 1 are true. Two case.
350  if (assignment_.LiteralIsTrue(span[watched_pos ^ 1])) {
351  // All literals are true.
352  ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
353  return kNoLiteralIndex;
354  } else {
355  // The other watched literal is the last unassigned
357  ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
358  return kNoLiteralIndex;
359  }
360 }
361 
362 void EnforcementPropagator::ChangeStatus(EnforcementId id,
363  EnforcementStatus new_status) {
364  const EnforcementStatus old_status = statuses_[id];
365  if (old_status == new_status) return;
366  if (trail_.CurrentDecisionLevel() != 0) {
367  untrail_stack_.push_back({id, old_status});
368  }
369  statuses_[id] = new_status;
370  if (callbacks_[id] != nullptr) callbacks_[id](new_status);
371 }
372 
374  : trail_(model->GetOrCreate<Trail>()),
375  integer_trail_(model->GetOrCreate<IntegerTrail>()),
376  enforcement_propagator_(model->GetOrCreate<EnforcementPropagator>()),
377  watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
378  time_limit_(model->GetOrCreate<TimeLimit>()),
379  rev_int_repository_(model->GetOrCreate<RevIntRepository>()),
380  rev_integer_value_repository_(
381  model->GetOrCreate<RevIntegerValueRepository>()),
382  shared_stats_(model->GetOrCreate<SharedStatistics>()),
383  watcher_id_(watcher_->Register(this)) {
384  // Note that we need this class always in sync.
385  integer_trail_->RegisterWatcher(&modified_vars_);
386  integer_trail_->RegisterReversibleClass(this);
387 
388  // TODO(user): When we start to push too much (Cycle?) we should see what
389  // other propagator says before repropagating this one, system for call
390  // later?
391  watcher_->SetPropagatorPriority(watcher_id_, 0);
392 }
393 
395  if (!VLOG_IS_ON(1)) return;
396  if (shared_stats_ == nullptr) return;
397  std::vector<std::pair<std::string, int64_t>> stats;
398  stats.push_back({"linear_propag/num_pushes", num_pushes_});
399  stats.push_back(
400  {"linear_propag/num_enforcement_pushes", num_enforcement_pushes_});
401  stats.push_back({"linear_propag/num_simple_cycles", num_simple_cycles_});
402  stats.push_back({"linear_propag/num_complex_cycles", num_complex_cycles_});
403  stats.push_back({"linear_propag/num_scanned", num_scanned_});
404  stats.push_back({"linear_propag/num_extra_scan", num_extra_scans_});
405  stats.push_back({"linear_propag/num_explored_in_disassemble",
406  num_explored_in_disassemble_});
407  stats.push_back({"linear_propag/num_bool_aborts", num_bool_aborts_});
408  stats.push_back({"linear_propag/num_ignored", num_ignored_});
409  stats.push_back({"linear_propag/num_reordered", num_reordered_});
410  shared_stats_->AddStats(stats);
411 }
412 
413 void LinearPropagator::SetLevel(int level) {
414  if (level < previous_level_) {
415  // If the solver backtracked at any point, we invalidate all our queue
416  // and propagated_by information.
417  ClearPropagatedBy();
418  while (!propagation_queue_.empty()) {
419  in_queue_[propagation_queue_.Pop()] = false;
420  }
421  for (int i = rev_at_false_size_; i < in_queue_and_at_false_.size(); ++i) {
422  in_queue_[in_queue_and_at_false_[i]] = false;
423  }
424  in_queue_and_at_false_.resize(rev_at_false_size_);
425  } else if (level > previous_level_) {
426  rev_at_false_size_ = in_queue_and_at_false_.size();
427  rev_int_repository_->SaveState(&rev_at_false_size_);
428  }
429  previous_level_ = level;
430 
431  // Tricky: if we aborted the current propagation because we pushed a Boolean,
432  // by default, the GenericLiteralWatcher will only call Propagate() again if
433  // one of the watched variable changed. With this, it is guaranteed to call
434  // it again if it wasn't in the queue already.
435  if (!propagation_queue_.empty()) {
436  watcher_->CallOnNextPropagate(watcher_id_);
437  }
438 }
439 
441  id_scanned_at_least_once_.ClearAndResize(in_queue_.size());
442 
443  // Initial addition.
444  // We will clear modified_vars_ on exit.
445  for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
446  if (var >= var_to_constraint_ids_.size()) continue;
447  SetPropagatedBy(var, -1);
448  AddWatchedToQueue(var);
449  }
450 
451  // TODO(user): Abort this propagator as soon as a Boolean is propagated ? so
452  // that we always finish the Boolean propagation first. This can happen when
453  // we push a bound that has associated Booleans. The idea is to resume from
454  // our current state when we are called again. Note however that we have to
455  // clear the propagated_by_ info has other propagator might have pushed the
456  // same variable further.
457  //
458  // Empty FIFO queue.
459  const int saved_index = trail_->Index();
460  while (!propagation_queue_.empty()) {
461  const int id = propagation_queue_.Pop();
462  in_queue_[id] = false;
463  if (!PropagateOneConstraint(id)) {
464  modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
465  return false;
466  }
467 
468  if (trail_->Index() > saved_index) {
469  ++num_bool_aborts_;
470  break;
471  }
472  }
473 
474  // Clean-up modified_vars_ to do as little as possible on the next call.
475  modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
476  return true;
477 }
478 
479 // Adds a new constraint to the propagator.
481  absl::Span<const Literal> enforcement_literals,
482  absl::Span<const IntegerVariable> vars,
483  absl::Span<const IntegerValue> coeffs, IntegerValue upper_bound) {
484  if (vars.empty()) return;
485  for (const Literal l : enforcement_literals) {
486  if (trail_->Assignment().LiteralIsFalse(l)) return;
487  }
488 
489  // Make sure max_variations_ is of correct size.
490  // Note that we also have a hard limit of 1 << 29 on the size.
491  CHECK_LT(vars.size(), 1 << 29);
492  if (vars.size() > max_variations_.size()) {
493  max_variations_.resize(vars.size(), 0);
494  buffer_of_ones_.resize(vars.size(), IntegerValue(1));
495  }
496 
497  // Initialize constraint data.
498  CHECK_EQ(vars.size(), coeffs.size());
499  const int id = infos_.size();
500  {
501  ConstraintInfo info;
502  info.all_coeffs_are_one = false;
503  info.start = variables_buffer_.size();
504  info.initial_size = vars.size();
505  info.rev_rhs = upper_bound;
506  info.rev_size = vars.size();
507  infos_.push_back(std::move(info));
508  }
509 
510  id_to_propagation_count_.push_back(0);
511  variables_buffer_.insert(variables_buffer_.end(), vars.begin(), vars.end());
512  coeffs_buffer_.insert(coeffs_buffer_.end(), coeffs.begin(), coeffs.end());
513  CanonicalizeConstraint(id);
514 
515  bool all_at_one = true;
516  for (const IntegerValue coeff : GetCoeffs(infos_.back())) {
517  if (coeff != 1) {
518  all_at_one = false;
519  break;
520  }
521  }
522  if (all_at_one) {
523  // TODO(user): we still waste the space in coeffs_buffer_ so that the
524  // start are aligned with the variables_buffer_.
525  infos_.back().all_coeffs_are_one = true;
526  }
527 
528  // Initialize watchers.
529  // Initialy we want everything to be propagated at least once.
530  in_queue_.push_back(false);
531  propagation_queue_.IncreaseSize(in_queue_.size());
532 
533  if (!enforcement_literals.empty()) {
534  infos_.back().enf_status = EnforcementStatus::CANNOT_PROPAGATE;
535  infos_.back().enf_id = enforcement_propagator_->Register(
536  enforcement_literals, [this, id](EnforcementStatus status) {
537  infos_[id].enf_status = status;
538  // TODO(user): With some care, when we cannot propagate or the
539  // constraint is not enforced, we could live in_queue_[] at true but
540  // not put the constraint in the queue.
543  AddToQueueIfNeeded(id);
544  watcher_->CallOnNextPropagate(watcher_id_);
545  }
546  });
547  } else {
548  AddToQueueIfNeeded(id);
549  infos_.back().enf_id = -1;
550  infos_.back().enf_status = EnforcementStatus::IS_ENFORCED;
551  }
552 
553  for (const IntegerVariable var : GetVariables(infos_[id])) {
554  // Transposed graph to know which constraint to wake up.
555  if (var >= var_to_constraint_ids_.size()) {
556  // We need both the var entry and its negation to be allocated.
557  const int size = std::max(var, NegationOf(var)).value() + 1;
558  var_to_constraint_ids_.resize(size);
559  propagated_by_.resize(size, -1);
560  propagated_by_was_set_.Resize(IntegerVariable(size));
561  is_watched_.resize(size, false);
562  }
563 
564  // TODO(user): Shall we decide on some ordering here? maybe big coeff first
565  // so that we get the largest change in slack? the idea being to propagate
566  // large change first in case of cycles.
567  var_to_constraint_ids_[var].push_back(id);
568 
569  // We need to be registered to the watcher so Propagate() is called at
570  // the proper priority. But then we rely on modified_vars_.
571  if (!is_watched_[var]) {
572  is_watched_[var] = true;
573  watcher_->WatchLowerBound(var, watcher_id_);
574  }
575  }
576 }
577 
578 absl::Span<IntegerValue> LinearPropagator::GetCoeffs(
579  const ConstraintInfo& info) {
580  if (info.all_coeffs_are_one) {
581  return absl::MakeSpan(&buffer_of_ones_[0], info.initial_size);
582  }
583  return absl::MakeSpan(&coeffs_buffer_[info.start], info.initial_size);
584 }
585 
586 absl::Span<IntegerVariable> LinearPropagator::GetVariables(
587  const ConstraintInfo& info) {
588  return absl::MakeSpan(&variables_buffer_[info.start], info.initial_size);
589 }
590 
591 void LinearPropagator::CanonicalizeConstraint(int id) {
592  const ConstraintInfo& info = infos_[id];
593  auto coeffs = GetCoeffs(info);
594  auto vars = GetVariables(info);
595  for (int i = 0; i < vars.size(); ++i) {
596  if (coeffs[i] < 0) {
597  coeffs[i] = -coeffs[i];
598  vars[i] = NegationOf(vars[i]);
599  }
600  }
601 }
602 
603 // TODO(user): template everything for the case info.all_coeffs_are_one ?
604 bool LinearPropagator::PropagateOneConstraint(int id) {
605  // This is here for development purpose, it is a bit too slow to check by
606  // default though, even VLOG_IS_ON(1) so we disable it.
607  if (/* DISABLES CODE */ (false)) {
608  ++num_scanned_;
609  if (id_scanned_at_least_once_[id]) {
610  ++num_extra_scans_;
611  } else {
612  id_scanned_at_least_once_.Set(id);
613  }
614  }
615 
616  // Skip constraint not enforced or that cannot propagate if false.
617  ConstraintInfo& info = infos_[id];
618  if (info.enf_status == EnforcementStatus::IS_FALSE ||
619  info.enf_status == EnforcementStatus::CANNOT_PROPAGATE) {
620  DCHECK(!in_queue_[id]);
621  if (info.enf_status == EnforcementStatus::IS_FALSE) {
622  // We mark this constraint as in the queue but will never inspect it
623  // again until we backtrack over this time.
624  in_queue_[id] = true;
625  in_queue_and_at_false_.push_back(id);
626  }
627  ++num_ignored_;
628  return true;
629  }
630 
631  // Compute the slack and max_variations_ of each variables.
632  // We also filter out fixed variables in a reversible way.
633  IntegerValue implied_lb(0);
634  auto vars = GetVariables(info);
635  auto coeffs = GetCoeffs(info);
636  IntegerValue max_variation(0);
637  bool first_change = true;
638  time_limit_->AdvanceDeterministicTime(static_cast<double>(info.rev_size) *
639  1e-9);
640  for (int i = 0; i < info.rev_size;) {
641  const IntegerVariable var = vars[i];
642  const IntegerValue coeff = coeffs[i];
643  const IntegerValue lb = integer_trail_->LowerBound(var);
644  const IntegerValue ub = integer_trail_->UpperBound(var);
645  if (lb == ub) {
646  if (first_change) {
647  // Note that we can save at most one state per fixed var. Also at
648  // level zero we don't save anything.
649  rev_int_repository_->SaveState(&info.rev_size);
650  rev_integer_value_repository_->SaveState(&info.rev_rhs);
651  first_change = false;
652  }
653  info.rev_size--;
654  std::swap(vars[i], vars[info.rev_size]);
655  std::swap(coeffs[i], coeffs[info.rev_size]);
656  info.rev_rhs -= coeff * lb;
657  } else {
658  implied_lb += coeff * lb;
659  max_variations_[i] = (ub - lb) * coeff;
660  max_variation = std::max(max_variation, max_variations_[i]);
661  ++i;
662  }
663  }
664  const IntegerValue slack = info.rev_rhs - implied_lb;
665 
666  // Negative slack means the constraint is false.
667  if (max_variation <= slack) return true;
668  if (slack < 0) {
669  // Fill integer reason.
670  integer_reason_.clear();
671  reason_coeffs_.clear();
672  for (int i = 0; i < info.initial_size; ++i) {
673  const IntegerVariable var = vars[i];
674  if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
675  integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
676  reason_coeffs_.push_back(coeffs[i]);
677  }
678  }
679 
680  // Relax it.
681  integer_trail_->RelaxLinearReason(-slack - 1, reason_coeffs_,
682  &integer_reason_);
683  ++num_enforcement_pushes_;
684  return enforcement_propagator_->PropagateWhenFalse(info.enf_id, {},
685  integer_reason_);
686  }
687 
688  // We can only propagate more if all the enforcement literals are true.
689  if (info.enf_status != EnforcementStatus::IS_ENFORCED) return true;
690 
691  // The lower bound of all the variables except one can be used to update the
692  // upper bound of the last one.
693  int num_pushed = 0;
694  for (int i = 0; i < info.rev_size; ++i) {
695  if (max_variations_[i] <= slack) continue;
696 
697  // TODO(user): If the new ub fall into an hole of the variable, we can
698  // actually relax the reason more by computing a better slack.
699  ++num_pushes_;
700  const IntegerVariable var = vars[i];
701  const IntegerValue coeff = coeffs[i];
702  const IntegerValue div = slack / coeff;
703  const IntegerValue new_ub = integer_trail_->LowerBound(var) + div;
704  const IntegerValue propagation_slack = (div + 1) * coeff - slack - 1;
705  if (!integer_trail_->Enqueue(
707  /*lazy_reason=*/[this, info, propagation_slack](
708  IntegerLiteral i_lit, int trail_index,
709  std::vector<Literal>* literal_reason,
710  std::vector<int>* trail_indices_reason) {
711  literal_reason->clear();
712  trail_indices_reason->clear();
713  enforcement_propagator_->AddEnforcementReason(info.enf_id,
714  literal_reason);
715  reason_coeffs_.clear();
716 
717  auto coeffs = GetCoeffs(info);
718  auto vars = GetVariables(info);
719  for (int i = 0; i < info.initial_size; ++i) {
720  const IntegerVariable var = vars[i];
721  if (PositiveVariable(var) == PositiveVariable(i_lit.var)) {
722  continue;
723  }
724  const int index =
725  integer_trail_->FindTrailIndexOfVarBefore(var, trail_index);
726  if (index >= 0) {
727  trail_indices_reason->push_back(index);
728  if (propagation_slack > 0) {
729  reason_coeffs_.push_back(coeffs[i]);
730  }
731  }
732  }
733  if (propagation_slack > 0) {
734  integer_trail_->RelaxLinearReason(
735  propagation_slack, reason_coeffs_, trail_indices_reason);
736  }
737  })) {
738  return false;
739  }
740 
741  // Add to the queue all touched constraint.
742  const IntegerValue actual_ub = integer_trail_->UpperBound(var);
743  const IntegerVariable next_var = NegationOf(var);
744  if (actual_ub < new_ub) {
745  // Was pushed further due to hole. We clear it.
746  SetPropagatedBy(next_var, -1);
747  AddWatchedToQueue(next_var);
748  } else if (actual_ub == new_ub) {
749  SetPropagatedBy(next_var, id);
750  AddWatchedToQueue(next_var);
751 
752  // We reorder them first.
753  std::swap(vars[i], vars[num_pushed]);
754  std::swap(coeffs[i], coeffs[num_pushed]);
755  ++num_pushed;
756  }
757 
758  // Explore the subtree and detect cycles greedily.
759  // Also postpone some propagation.
760  if (num_pushed > 0) {
761  if (!DisassembleSubtree(id, num_pushed)) {
762  return false;
763  }
764  }
765  }
766 
767  return true;
768 }
769 
770 std::string LinearPropagator::ConstraintDebugString(int id) {
771  std::string result;
772  const ConstraintInfo& info = infos_[id];
773  auto coeffs = GetCoeffs(info);
774  auto vars = GetVariables(info);
775  IntegerValue implied_lb(0);
776  IntegerValue rhs_correction(0);
777  for (int i = 0; i < info.initial_size; ++i) {
778  const IntegerValue term = coeffs[i] * integer_trail_->LowerBound(vars[i]);
779  if (i >= info.rev_size) {
780  rhs_correction += term;
781  }
782  implied_lb += term;
783  absl::StrAppend(&result, " +", coeffs[i].value(), "*X", vars[i].value());
784  }
785  const IntegerValue original_rhs = info.rev_rhs + rhs_correction;
786  absl::StrAppend(&result, " <= ", original_rhs.value(),
787  " slack=", original_rhs.value() - implied_lb.value());
788  absl::StrAppend(&result, " enf=", info.enf_status);
789  return result;
790 }
791 
792 bool LinearPropagator::ReportConflictingCycle() {
793  // Often, all coefficients of the variable involved in the cycle are the same
794  // and if we sum all constraint, we get an infeasible one. If this is the
795  // case, we simplify the reason.
796  //
797  // TODO(user): We could relax if the coefficient of the sum do not overflow.
798  // TODO(user): Sum constraints with eventual factor in more cases.
799  {
800  literal_reason_.clear();
801  integer_reason_.clear();
802  absl::int128 rhs_sum = 0;
803  absl::flat_hash_map<IntegerVariable, absl::int128> map_sum;
804  for (const auto [id, next_var] : disassemble_branch_) {
805  const ConstraintInfo& info = infos_[id];
806  enforcement_propagator_->AddEnforcementReason(info.enf_id,
807  &literal_reason_);
808  auto coeffs = GetCoeffs(info);
809  auto vars = GetVariables(info);
810  IntegerValue rhs_correction(0);
811  for (int i = 0; i < info.initial_size; ++i) {
812  if (i >= info.rev_size) {
813  rhs_correction += coeffs[i] * integer_trail_->LowerBound(vars[i]);
814  }
815  if (VariableIsPositive(vars[i])) {
816  map_sum[vars[i]] += coeffs[i].value();
817  } else {
818  map_sum[PositiveVariable(vars[i])] -= coeffs[i].value();
819  }
820  }
821  rhs_sum += (info.rev_rhs + rhs_correction).value();
822  }
823 
824  // We shouldn't have overflow since each component do not overflow an
825  // int64_t and we sum a small amount of them.
826  absl::int128 implied_lb = 0;
827  for (const auto [var, coeff] : map_sum) {
828  if (coeff > 0) {
829  if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
830  integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
831  }
832  implied_lb +=
833  coeff * absl::int128{integer_trail_->LowerBound(var).value()};
834  } else if (coeff < 0) {
835  if (!integer_trail_->VariableLowerBoundIsFromLevelZero(
836  NegationOf(var))) {
837  integer_reason_.push_back(integer_trail_->UpperBoundAsLiteral(var));
838  }
839  implied_lb +=
840  coeff * absl::int128{integer_trail_->UpperBound(var).value()};
841  }
842  }
843  if (implied_lb > rhs_sum) {
844  // We sort for determinism.
845  std::sort(integer_reason_.begin(), integer_reason_.end(),
846  [](const IntegerLiteral& a, const IntegerLiteral& b) {
847  return a.var < b.var;
848  });
849 
850  // Relax the linear reason if everything fit on an int64_t.
851  const absl::int128 limit{std::numeric_limits<int64_t>::max()};
852  const absl::int128 slack = implied_lb - rhs_sum;
853  if (slack > 1) {
854  reason_coeffs_.clear();
855  bool abort = false;
856  for (const IntegerLiteral i_lit : integer_reason_) {
857  absl::int128 c = map_sum.at(PositiveVariable(i_lit.var));
858  if (c < 0) c = -c; // No std::abs() for int128.
859  if (c >= limit) {
860  abort = true;
861  break;
862  }
863  reason_coeffs_.push_back(static_cast<int64_t>(c));
864  }
865  if (!abort) {
866  const IntegerValue slack64(
867  static_cast<int64_t>(std::min(limit, slack)));
868  integer_trail_->RelaxLinearReason(slack64 - 1, reason_coeffs_,
869  &integer_reason_);
870  }
871  }
872 
873  ++num_simple_cycles_;
874  VLOG(2) << "Simplified " << integer_reason_.size() << " slack "
875  << implied_lb - rhs_sum;
876  return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
877  }
878  }
879 
880  // For the complex reason, we just use the bound of every variable.
881  // We do some basic simplification for the variable involved in the cycle.
882  //
883  // TODO(user): Can we simplify more?
884  VLOG(2) << "Cycle";
885  literal_reason_.clear();
886  integer_reason_.clear();
887  IntegerVariable previous_var = kNoIntegerVariable;
888  for (const auto [id, next_var] : disassemble_branch_) {
889  const ConstraintInfo& info = infos_[id];
890  enforcement_propagator_->AddEnforcementReason(info.enf_id,
891  &literal_reason_);
892  for (const IntegerVariable var : GetVariables(infos_[id])) {
893  // The lower bound of this variable is implied by the previous constraint,
894  // so we do not need to include it.
895  if (var == previous_var) continue;
896 
897  // We do not need the lower bound of var to propagate its upper bound.
898  if (var == NegationOf(next_var)) continue;
899 
900  if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
901  integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
902  }
903  }
904  previous_var = next_var;
905 
906  VLOG(2) << next_var << " [" << integer_trail_->LowerBound(next_var) << ","
907  << integer_trail_->UpperBound(next_var)
908  << "] : " << ConstraintDebugString(id);
909  }
910  ++num_complex_cycles_;
911  return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
912 }
913 
914 // Note that if there is a loop in the propagated_by_ graph, it must be
915 // from root_id -> root_var, because each time we add an edge, we do
916 // disassemble.
917 //
918 // TODO(user): If one of the var coeff is > previous slack we push an id again,
919 // we can stop early with a conflict by propagating the ids in sequence.
920 bool LinearPropagator::DisassembleSubtree(int root_id, int num_pushed) {
921  disassemble_to_reorder_.ClearAndResize(in_queue_.size());
922  disassemble_reverse_topo_order_.clear();
923 
924  // The variable was just pushed, we explore the set of variable that will
925  // be pushed further due to this push. Basically, if a constraint propagated
926  // before and its slack will reduce due to the push, then any previously
927  // propagated variable with a coefficient NOT GREATER than the one of the
928  // variable reducing the slack will be pushed further.
929  disassemble_queue_.clear();
930  disassemble_branch_.clear();
931  {
932  const ConstraintInfo& info = infos_[root_id];
933  auto vars = GetVariables(info);
934  for (int i = 0; i < num_pushed; ++i) {
935  disassemble_queue_.push_back({root_id, NegationOf(vars[i])});
936  }
937  }
938 
939  // Note that all var should be unique since there is only one propagated_by_
940  // for each one. And each time we explore an id, we disassemble the tree.
941  while (!disassemble_queue_.empty()) {
942  const auto [prev_id, var] = disassemble_queue_.back();
943  if (!disassemble_branch_.empty() &&
944  disassemble_branch_.back().first == prev_id &&
945  disassemble_branch_.back().second == var) {
946  disassemble_branch_.pop_back();
947  disassemble_reverse_topo_order_.push_back(prev_id);
948  disassemble_queue_.pop_back();
949  continue;
950  }
951 
952  disassemble_branch_.push_back({prev_id, var});
953  time_limit_->AdvanceDeterministicTime(
954  static_cast<double>(var_to_constraint_ids_[var].size()) * 1e-9);
955  for (const int id : var_to_constraint_ids_[var]) {
956  if (prev_id == root_id) {
957  // Root id was just propagated, so there is no need to reorder what
958  // it pushes.
959  DCHECK_NE(id, root_id);
960  if (disassemble_to_reorder_[id]) continue;
961  disassemble_to_reorder_.Set(id);
962  } else if (id == root_id) {
963  // TODO(user): Check previous slack vs var coeff?
964  // TODO(user): Make sure there are none or detect cycle not going back
965  // to the root.
966  CHECK(!disassemble_branch_.empty());
967 
968  // This is a corner case in which there is actually no cycle.
969  const IntegerVariable root_var = disassemble_branch_[0].second;
970  CHECK_EQ(disassemble_branch_[0].first, root_id);
971  CHECK_NE(var, root_var);
972  if (var == NegationOf(root_var)) continue;
973 
974  // Tricky: We have a cycle here only if coeff of var >= root_coeff.
975  // If there is no cycle, we will just finish the branch here.
976  //
977  // TODO(user): Can we be more precise? if one coeff is big, the
978  // variation in slack might be big enough to push a variable twice and
979  // thus push a lower coeff.
980  const ConstraintInfo& info = infos_[id];
981  auto coeffs = GetCoeffs(info);
982  auto vars = GetVariables(info);
983  IntegerValue root_coeff(0);
984  IntegerValue var_coeff(0);
985  for (int i = 0; i < info.initial_size; ++i) {
986  if (vars[i] == var) var_coeff = coeffs[i];
987  if (vars[i] == NegationOf(root_var)) root_coeff = coeffs[i];
988  }
989  CHECK_NE(root_coeff, 0);
990  CHECK_NE(var_coeff, 0);
991  if (var_coeff >= root_coeff) {
992  return ReportConflictingCycle();
993  } else {
994  // We don't want to continue the search from root_id.
995  continue;
996  }
997  }
998 
999  if (id_to_propagation_count_[id] == 0) continue; // Didn't push.
1000  disassemble_to_reorder_.Set(id);
1001 
1002  // The constraint pushed some variable. Identify which ones will be pushed
1003  // further. Disassemble the whole info since we are about to propagate
1004  // this constraint again. Any pushed variable must be before the rev_size.
1005  const ConstraintInfo& info = infos_[id];
1006  auto coeffs = GetCoeffs(info);
1007  auto vars = GetVariables(info);
1008  IntegerValue var_coeff(0);
1009  disassemble_candidates_.clear();
1010  ++num_explored_in_disassemble_;
1011  time_limit_->AdvanceDeterministicTime(static_cast<double>(info.rev_size) *
1012  1e-9);
1013  for (int i = 0; i < info.rev_size; ++i) {
1014  if (vars[i] == var) {
1015  var_coeff = coeffs[i];
1016  continue;
1017  }
1018  const IntegerVariable next_var = NegationOf(vars[i]);
1019  if (propagated_by_[next_var] == id) {
1020  disassemble_candidates_.push_back({next_var, coeffs[i]});
1021 
1022  // We will propagate var again later, so clear all this for now.
1023  propagated_by_[next_var] = -1;
1024  id_to_propagation_count_[id]--;
1025  }
1026  }
1027  for (const auto [next_var, coeff] : disassemble_candidates_) {
1028  if (coeff <= var_coeff) {
1029  // We are guaranteed to push next_var only if var_coeff will move
1030  // the slack enough.
1031  //
1032  // TODO(user): Keep current delta in term of the DFS so we detect
1033  // cycle and depedendences in more cases.
1034  disassemble_queue_.push_back({id, next_var});
1035  }
1036  }
1037  }
1038  }
1039 
1040  CHECK(!disassemble_to_reorder_[root_id]);
1041  tmp_to_reorder_.clear();
1042  std::reverse(disassemble_reverse_topo_order_.begin(),
1043  disassemble_reverse_topo_order_.end()); // !! not unique
1044  for (const int id : disassemble_reverse_topo_order_) {
1045  if (!disassemble_to_reorder_[id]) continue;
1046  disassemble_to_reorder_.Clear(id);
1047  AddToQueueIfNeeded(id);
1048  if (!propagation_queue_.Contains(id)) continue;
1049  tmp_to_reorder_.push_back(id);
1050  }
1051 
1052  // TODO(user): Reordering can be sloe since require sort and can touch many
1053  // entries. Investigate alternatives. We could probably optimize this a bit
1054  // more.
1055  if (tmp_to_reorder_.empty()) return true;
1056  const int important_size = static_cast<int>(tmp_to_reorder_.size());
1057 
1058  for (const int id : disassemble_to_reorder_.PositionsSetAtLeastOnce()) {
1059  if (!disassemble_to_reorder_[id]) continue;
1060  disassemble_to_reorder_.Clear(id);
1061  if (!propagation_queue_.Contains(id)) continue;
1062  tmp_to_reorder_.push_back(id);
1063  }
1064  disassemble_to_reorder_.NotifyAllClear();
1065 
1066  // We try to keep the same order as before for the elements not in the
1067  // topological order.
1068  propagation_queue_.SortByPos(
1069  absl::MakeSpan(&tmp_to_reorder_[important_size],
1070  tmp_to_reorder_.size() - important_size));
1071 
1072  num_reordered_ += tmp_to_reorder_.size();
1073  propagation_queue_.Reorder(tmp_to_reorder_);
1074  return true;
1075 }
1076 
1077 void LinearPropagator::AddToQueueIfNeeded(int id) {
1078  DCHECK_LT(id, in_queue_.size());
1079  DCHECK_LT(id, infos_.size());
1080 
1081  if (in_queue_[id]) return;
1082  in_queue_[id] = true;
1083  propagation_queue_.Push(id);
1084 }
1085 
1086 void LinearPropagator::AddWatchedToQueue(IntegerVariable var) {
1087  if (var >= static_cast<int>(var_to_constraint_ids_.size())) return;
1088  time_limit_->AdvanceDeterministicTime(
1089  static_cast<double>(var_to_constraint_ids_[var].size()) * 1e-9);
1090  for (const int id : var_to_constraint_ids_[var]) {
1091  AddToQueueIfNeeded(id);
1092  }
1093 }
1094 
1095 void LinearPropagator::SetPropagatedBy(IntegerVariable var, int id) {
1096  int& ref_id = propagated_by_[var];
1097  if (ref_id == id) return;
1098 
1099  propagated_by_was_set_.Set(var);
1100 
1101  DCHECK_GE(var, 0);
1102  DCHECK_LT(var, propagated_by_.size());
1103  if (ref_id != -1) {
1104  DCHECK_GE(ref_id, 0);
1105  DCHECK_LT(ref_id, id_to_propagation_count_.size());
1106  id_to_propagation_count_[ref_id]--;
1107  }
1108  ref_id = id;
1109  if (id != -1) id_to_propagation_count_[id]++;
1110 }
1111 
1112 void LinearPropagator::ClearPropagatedBy() {
1113  // To be sparse, we use the fact that each node with a parent must be in
1114  // modified_vars_.
1115  for (const IntegerVariable var :
1116  propagated_by_was_set_.PositionsSetAtLeastOnce()) {
1117  int& id = propagated_by_[var];
1118  if (id != -1) --id_to_propagation_count_[id];
1119  propagated_by_[var] = -1;
1120  }
1121  propagated_by_was_set_.ClearAndResize(propagated_by_was_set_.size());
1122  DCHECK(std::all_of(propagated_by_.begin(), propagated_by_.end(),
1123  [](int id) { return id == -1; }));
1124  DCHECK(std::all_of(id_to_propagation_count_.begin(),
1125  id_to_propagation_count_.end(),
1126  [](int count) { return count == 0; }));
1127 }
1128 
1129 } // namespace sat
1130 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void resize(size_type new_size)
size_type size() const
void push_back(const value_type &x)
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void SaveState(T *object)
Definition: rev.h:60
void SaveStateWithStamp(T *object, int64_t *stamp)
Definition: rev.h:70
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:806
void Set(IntegerType index)
Definition: bitset.h:792
void Resize(IntegerType size)
Definition: bitset.h:778
void ClearAndResize(IntegerType size)
Definition: bitset.h:767
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:226
void ReorderDense(absl::Span< const int > order)
void SortByPos(absl::Span< int > elements)
void Reorder(absl::Span< const int > order)
EnforcementId Register(absl::Span< const Literal > enforcement, std::function< void(EnforcementStatus)> callback=nullptr)
ABSL_MUST_USE_RESULT bool PropagateWhenFalse(EnforcementId id, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
void AddEnforcementReason(EnforcementId id, std::vector< Literal > *reason) const
void Untrail(const Trail &trail, int trail_index) final
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1681
void SetPropagatorPriority(int id, int priority)
Definition: integer.cc:2309
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1228
void RegisterWatcher(SparseBitset< IntegerVariable > *p)
Definition: integer.h:997
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1589
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.h:1004
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1387
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
bool VariableLowerBoundIsFromLevelZero(IntegerVariable var) const
Definition: integer.h:1021
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition: integer.cc:984
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
void RegisterReversibleClass(ReversibleInterface *rev)
Definition: integer.h:1027
IntegerVariable NumIntegerVariables() const
Definition: integer.h:715
void AddConstraint(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coeffs, IntegerValue upper_bound)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void AddStats(absl::Span< const std::pair< std::string, int64_t >> stats)
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:167
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
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
MPCallback * callback
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
const LiteralIndex kNoLiteralIndex(-1)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:145
Collection of objects used to extend the Constraint Solver library.
std::ostream & operator<<(std::ostream &out, const Assignment &assignment)
Literal literal
Definition: optimization.cc:88
if(!yyg->yy_init)
Definition: parser.yy.cc:965
IntVar * upper_bound
Definition: routing.cc:1087
int64_t capacity
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47