OR-Tools  9.1
sched_constraints.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 
14 // This file contains implementations of several scheduling constraints.
15 // The implemented constraints are:
16 //
17 // * Cover constraints: ensure that an interval is the convex hull of
18 // a set of interval variables. This includes the performed status
19 // (one interval performed implies the cover var performed, all
20 // intervals unperformed implies the cover var unperformed, cover
21 // var unperformed implies all intervals unperformed, cover var
22 // performed implis at least one interval performed).
23 
24 #include <cstdint>
25 #include <limits>
26 #include <string>
27 #include <vector>
28 
29 #include "absl/strings/str_format.h"
31 #include "ortools/base/logging.h"
32 #include "ortools/base/macros.h"
36 
37 namespace operations_research {
38 namespace {
39 class TreeArrayConstraint : public Constraint {
40  public:
41  enum PerformedStatus { UNPERFORMED, PERFORMED, UNDECIDED };
42 
43  TreeArrayConstraint(Solver* const solver,
44  const std::vector<IntervalVar*>& vars,
45  IntervalVar* const target_var)
46  : Constraint(solver),
47  vars_(vars),
48  target_var_(target_var),
49  block_size_(solver->parameters().array_split_size()) {
50  std::vector<int> lengths;
51  lengths.push_back(vars_.size());
52  while (lengths.back() > 1) {
53  const int current = lengths.back();
54  lengths.push_back((current + block_size_ - 1) / block_size_);
55  }
56  tree_.resize(lengths.size());
57  for (int i = 0; i < lengths.size(); ++i) {
58  tree_[i].resize(lengths[lengths.size() - i - 1]);
59  }
60  DCHECK_GE(tree_.size(), 1);
61  DCHECK_EQ(1, tree_[0].size());
62  root_node_ = &tree_[0][0];
63  }
64 
65  std::string DebugStringInternal(const std::string& name) const {
66  return absl::StrFormat("Cover(%s) == %s", JoinDebugStringPtr(vars_, ", "),
67  target_var_->DebugString());
68  }
69 
70  void AcceptInternal(const std::string& name,
71  ModelVisitor* const visitor) const {
72  visitor->BeginVisitConstraint(name, this);
73  visitor->VisitIntervalArrayArgument(ModelVisitor::kIntervalsArgument,
74  vars_);
75  visitor->VisitIntervalArgument(ModelVisitor::kTargetArgument, target_var_);
76  visitor->EndVisitConstraint(name, this);
77  }
78 
79  // Reduce the range of a given node (interval, state).
80  void ReduceDomain(int depth, int position, int64_t new_start_min,
81  int64_t new_start_max, int64_t new_end_min,
82  int64_t new_end_max, PerformedStatus performed) {
83  NodeInfo* const info = &tree_[depth][position];
84  if (new_start_min > info->start_min.Value()) {
85  info->start_min.SetValue(solver(), new_start_min);
86  }
87  if (new_start_max < info->start_max.Value()) {
88  info->start_max.SetValue(solver(), new_start_max);
89  }
90  if (new_end_min > info->end_min.Value()) {
91  info->end_min.SetValue(solver(), new_end_min);
92  }
93  if (new_end_max < info->end_max.Value()) {
94  info->end_max.SetValue(solver(), new_end_max);
95  }
96  if (performed != UNDECIDED) {
97  CHECK(performed == info->performed.Value() ||
98  info->performed.Value() == UNDECIDED);
99  info->performed.SetValue(solver(), performed);
100  }
101  }
102 
103  void InitLeaf(int position, int64_t start_min, int64_t start_max,
104  int64_t end_min, int64_t end_max, PerformedStatus performed) {
105  InitNode(MaxDepth(), position, start_min, start_max, end_min, end_max,
106  performed);
107  }
108 
109  void InitNode(int depth, int position, int64_t start_min, int64_t start_max,
110  int64_t end_min, int64_t end_max, PerformedStatus performed) {
111  tree_[depth][position].start_min.SetValue(solver(), start_min);
112  tree_[depth][position].start_max.SetValue(solver(), start_max);
113  tree_[depth][position].end_min.SetValue(solver(), end_min);
114  tree_[depth][position].end_max.SetValue(solver(), end_max);
115  tree_[depth][position].performed.SetValue(solver(),
116  static_cast<int>(performed));
117  }
118 
119  int64_t StartMin(int depth, int position) const {
120  return tree_[depth][position].start_min.Value();
121  }
122 
123  int64_t StartMax(int depth, int position) const {
124  return tree_[depth][position].start_max.Value();
125  }
126 
127  int64_t EndMax(int depth, int position) const {
128  return tree_[depth][position].end_max.Value();
129  }
130 
131  int64_t EndMin(int depth, int position) const {
132  return tree_[depth][position].end_min.Value();
133  }
134 
135  PerformedStatus Performed(int depth, int position) const {
136  const int p = tree_[depth][position].performed.Value();
137  CHECK_GE(p, UNPERFORMED);
138  CHECK_LE(p, UNDECIDED);
139  return static_cast<PerformedStatus>(p);
140  }
141 
142  int64_t RootStartMin() const { return root_node_->start_min.Value(); }
143 
144  int64_t RootStartMax() const { return root_node_->start_max.Value(); }
145 
146  int64_t RootEndMin() const { return root_node_->end_min.Value(); }
147 
148  int64_t RootEndMax() const { return root_node_->end_max.Value(); }
149 
150  PerformedStatus RootPerformed() const { return Performed(0, 0); }
151 
152  // This getters query first if the var can be performed, and will
153  // return a default value if not.
154  int64_t VarStartMin(int position) const {
155  return vars_[position]->MayBePerformed() ? vars_[position]->StartMin() : 0;
156  }
157 
158  int64_t VarStartMax(int position) const {
159  return vars_[position]->MayBePerformed() ? vars_[position]->StartMax() : 0;
160  }
161 
162  int64_t VarEndMin(int position) const {
163  return vars_[position]->MayBePerformed() ? vars_[position]->EndMin() : 0;
164  }
165 
166  int64_t VarEndMax(int position) const {
167  return vars_[position]->MayBePerformed() ? vars_[position]->EndMax() : 0;
168  }
169 
170  int64_t TargetVarStartMin() const {
171  return target_var_->MayBePerformed() ? target_var_->StartMin() : 0;
172  }
173 
174  int64_t TargetVarStartMax() const {
175  return target_var_->MayBePerformed() ? target_var_->StartMax() : 0;
176  }
177 
178  int64_t TargetVarEndMin() const {
179  return target_var_->MayBePerformed() ? target_var_->EndMin() : 0;
180  }
181 
182  int64_t TargetVarEndMax() const {
183  return target_var_->MayBePerformed() ? target_var_->EndMax() : 0;
184  }
185 
186  // Returns the performed status of the 'position' nth interval
187  // var of the problem.
188  PerformedStatus VarPerformed(int position) const {
189  IntervalVar* const var = vars_[position];
190  if (var->MustBePerformed()) {
191  return PERFORMED;
192  } else if (var->MayBePerformed()) {
193  return UNDECIDED;
194  } else {
195  return UNPERFORMED;
196  }
197  }
198 
199  // Returns the performed status of the target var.
200  PerformedStatus TargetVarPerformed() const {
201  if (target_var_->MustBePerformed()) {
202  return PERFORMED;
203  } else if (target_var_->MayBePerformed()) {
204  return UNDECIDED;
205  } else {
206  return UNPERFORMED;
207  }
208  }
209 
210  // Returns the position of the parent of a node with a given position.
211  int Parent(int position) const { return position / block_size_; }
212 
213  // Returns the index of the first child of a node at a given 'position'.
214  int ChildStart(int position) const { return position * block_size_; }
215 
216  // Returns the index of the last child of a node at a given
217  // 'position'. The depth is needed to make sure that do not overlap
218  // the width of the tree at a given depth.
219  int ChildEnd(int depth, int position) const {
220  DCHECK_LT(depth + 1, tree_.size());
221  return std::min((position + 1) * block_size_ - 1, Width(depth + 1) - 1);
222  }
223 
224  bool IsLeaf(int depth) const { return depth == MaxDepth(); }
225 
226  int MaxDepth() const { return tree_.size() - 1; }
227 
228  int Width(int depth) const { return tree_[depth].size(); }
229 
230  protected:
231  const std::vector<IntervalVar*> vars_;
232  IntervalVar* const target_var_;
233 
234  private:
235  struct NodeInfo {
236  NodeInfo()
237  : start_min(0),
238  start_max(0),
239  end_min(0),
240  end_max(0),
241  performed(UNDECIDED) {}
242 
243  Rev<int64_t> start_min;
244  Rev<int64_t> start_max;
245  Rev<int64_t> end_min;
246  Rev<int64_t> end_max;
247  Rev<int> performed;
248  };
249 
250  std::vector<std::vector<NodeInfo> > tree_;
251  const int block_size_;
252  NodeInfo* root_node_;
253 };
254 
255 // This constraint implements cover(vars) == cover_var.
256 class CoverConstraint : public TreeArrayConstraint {
257  public:
258  CoverConstraint(Solver* const solver, const std::vector<IntervalVar*>& vars,
259  IntervalVar* const cover_var)
260  : TreeArrayConstraint(solver, vars, cover_var), cover_demon_(nullptr) {}
261 
262  ~CoverConstraint() override {}
263 
264  void Post() override {
265  for (int i = 0; i < vars_.size(); ++i) {
266  Demon* const demon = MakeConstraintDemon1(
267  solver(), this, &CoverConstraint::LeafChanged, "LeafChanged", i);
268  vars_[i]->WhenStartRange(demon);
269  vars_[i]->WhenEndRange(demon);
270  vars_[i]->WhenPerformedBound(demon);
271  }
272  cover_demon_ = solver()->RegisterDemon(MakeDelayedConstraintDemon0(
273  solver(), this, &CoverConstraint::CoverVarChanged, "CoverVarChanged"));
274  target_var_->WhenStartRange(cover_demon_);
275  target_var_->WhenEndRange(cover_demon_);
276  target_var_->WhenPerformedBound(cover_demon_);
277  }
278 
279  void InitialPropagate() override {
280  // Copy vars to leaf nodes.
281  for (int i = 0; i < vars_.size(); ++i) {
282  InitLeaf(i, VarStartMin(i), VarStartMax(i), VarEndMin(i), VarEndMax(i),
283  VarPerformed(i));
284  }
285 
286  // Compute up.
287  for (int i = MaxDepth() - 1; i >= 0; --i) {
288  for (int j = 0; j < Width(i); ++j) {
289  int64_t bucket_start_min = std::numeric_limits<int64_t>::max();
290  int64_t bucket_start_max = std::numeric_limits<int64_t>::max();
291  int64_t bucket_end_min = std::numeric_limits<int64_t>::min();
292  int64_t bucket_end_max = std::numeric_limits<int64_t>::min();
293  bool one_undecided = false;
294  const PerformedStatus up_performed = ComputePropagationUp(
295  i, j, &bucket_start_min, &bucket_start_max, &bucket_end_min,
296  &bucket_end_max, &one_undecided);
297  InitNode(i, j, bucket_start_min, bucket_start_max, bucket_end_min,
298  bucket_end_max, up_performed);
299  }
300  }
301  // Compute down.
302  PropagateRoot();
303  }
304 
305  void PropagateRoot() {
306  // Propagate from the root of the tree to the target var.
307  switch (RootPerformed()) {
308  case UNPERFORMED:
309  target_var_->SetPerformed(false);
310  break;
311  case PERFORMED:
312  target_var_->SetPerformed(true);
313  ABSL_FALLTHROUGH_INTENDED;
314  case UNDECIDED:
315  target_var_->SetStartRange(RootStartMin(), RootStartMax());
316  target_var_->SetEndRange(RootEndMin(), RootEndMax());
317  break;
318  }
319  // Check if we need to propagate back. This is useful in case the
320  // target var is performed and only one last interval var may be
321  // performed, and thus needs to change is status to performed.
322  CoverVarChanged();
323  }
324 
325  // Propagates from top to bottom.
326  void CoverVarChanged() {
327  PushDown(0, 0, TargetVarStartMin(), TargetVarStartMax(), TargetVarEndMin(),
328  TargetVarEndMax(), TargetVarPerformed());
329  }
330 
331  void PushDown(int depth, int position, int64_t new_start_min,
332  int64_t new_start_max, int64_t new_end_min, int64_t new_end_max,
333  PerformedStatus performed) {
334  // TODO(user): Propagate start_max and end_min going down.
335  // Nothing to do?
336  if (new_start_min <= StartMin(depth, position) &&
337  new_start_max >= StartMax(depth, position) &&
338  new_end_min <= EndMin(depth, position) &&
339  new_end_max >= EndMax(depth, position) &&
340  (performed == UNDECIDED || performed == Performed(depth, position))) {
341  return;
342  }
343  // Leaf node -> push to leaf var.
344  if (IsLeaf(depth)) {
345  switch (performed) {
346  case UNPERFORMED:
347  vars_[position]->SetPerformed(false);
348  break;
349  case PERFORMED:
350  vars_[position]->SetPerformed(true);
351  ABSL_FALLTHROUGH_INTENDED;
352  case UNDECIDED:
353  vars_[position]->SetStartRange(new_start_min, new_start_max);
354  vars_[position]->SetEndRange(new_end_min, new_end_max);
355  }
356  return;
357  }
358 
359  const int block_start = ChildStart(position);
360  const int block_end = ChildEnd(depth, position);
361 
362  switch (performed) {
363  case UNPERFORMED: { // Mark all node unperformed.
364  for (int i = block_start; i <= block_end; ++i) {
365  PushDown(depth + 1, i, new_start_min, new_start_max, new_end_min,
366  new_end_max, UNPERFORMED);
367  }
368  break;
369  }
370  case PERFORMED: { // Count number of undecided or performed;
371  int candidate = -1;
372  int may_be_performed_count = 0;
373  int must_be_performed_count = 0;
374  for (int i = block_start; i <= block_end; ++i) {
375  switch (Performed(depth + 1, i)) {
376  case UNPERFORMED:
377  break;
378  case PERFORMED:
379  must_be_performed_count++;
380  ABSL_FALLTHROUGH_INTENDED;
381  case UNDECIDED:
382  may_be_performed_count++;
383  candidate = i;
384  }
385  }
386  if (may_be_performed_count == 0) {
387  solver()->Fail();
388  } else if (may_be_performed_count == 1) {
389  PushDown(depth + 1, candidate, new_start_min, new_start_max,
390  new_end_min, new_end_max, PERFORMED);
391  } else {
392  for (int i = block_start; i <= block_end; ++i) {
393  // Since there are more than 1 active child node, we
394  // cannot propagate on new_start_max and new_end_min. Thus
395  // we substitute them with safe bounds e.g. new_end_max
396  // and new_start_min.
397  PushDown(depth + 1, i, new_start_min, new_end_max, new_start_min,
398  new_end_max, UNDECIDED);
399  }
400  }
401  break;
402  }
403  case UNDECIDED: {
404  for (int i = block_start; i <= block_end; ++i) {
405  // Since there are more than 1 active child node, we
406  // cannot propagate on new_start_max and new_end_min. Thus
407  // we substitute them with safe bounds e.g. new_end_max
408  // and new_start_min.
409  PushDown(depth + 1, i, new_start_min, new_end_max, new_start_min,
410  new_end_max, UNDECIDED);
411  }
412  }
413  }
414  }
415 
416  void LeafChanged(int term_index) {
417  ReduceDomain(MaxDepth(), term_index, VarStartMin(term_index),
418  VarStartMax(term_index), VarEndMin(term_index),
419  VarEndMax(term_index), VarPerformed(term_index));
420  // Do we need to propagate up?
421  const int parent = Parent(term_index);
422  const int parent_depth = MaxDepth() - 1;
423  const int64_t parent_start_min = StartMin(parent_depth, parent);
424  const int64_t parent_start_max = StartMax(parent_depth, parent);
425  const int64_t parent_end_min = EndMin(parent_depth, parent);
426  const int64_t parent_end_max = EndMax(parent_depth, parent);
427  IntervalVar* const var = vars_[term_index];
428  const bool performed_bound = var->IsPerformedBound();
429  const bool was_performed_bound = var->WasPerformedBound();
430  if (performed_bound == was_performed_bound && var->MayBePerformed() &&
431  var->OldStartMin() != parent_start_min &&
432  var->OldStartMax() != parent_start_max &&
433  var->OldEndMin() != parent_end_min &&
434  var->OldEndMax() != parent_end_max) {
435  // We were not a support of the parent bounds, and the performed
436  // status has not changed. There is no need to propagate up.
437  return;
438  }
439  PushUp(term_index);
440  }
441 
442  void PushUp(int position) {
443  int depth = MaxDepth();
444  while (depth > 0) {
445  const int parent = Parent(position);
446  const int parent_depth = depth - 1;
447  int64_t bucket_start_min = std::numeric_limits<int64_t>::max();
448  int64_t bucket_start_max = std::numeric_limits<int64_t>::max();
449  int64_t bucket_end_min = std::numeric_limits<int64_t>::min();
450  int64_t bucket_end_max = std::numeric_limits<int64_t>::min();
451  bool one_undecided = false;
452  const PerformedStatus status_up = ComputePropagationUp(
453  parent_depth, parent, &bucket_start_min, &bucket_start_max,
454  &bucket_end_min, &bucket_end_max, &one_undecided);
455  if (bucket_start_min > StartMin(parent_depth, parent) ||
456  bucket_start_max < StartMax(parent_depth, parent) ||
457  bucket_end_min > EndMin(parent_depth, parent) ||
458  bucket_end_max < EndMax(parent_depth, parent) ||
459  status_up != Performed(parent_depth, parent)) {
460  ReduceDomain(parent_depth, parent, bucket_start_min, bucket_start_max,
461  bucket_end_min, bucket_end_max, status_up);
462  } else {
463  if (one_undecided && TargetVarPerformed() == PERFORMED) {
464  // This may be the last possible interval that can and
465  // should be performed.
466  PropagateRoot();
467  }
468  // There is nothing more to propagate up. We can stop now.
469  return;
470  }
471  depth = parent_depth;
472  position = parent;
473  }
474  DCHECK_EQ(0, depth);
475  PropagateRoot();
476  }
477 
478  std::string DebugString() const override {
479  return DebugStringInternal(ModelVisitor::kCover);
480  }
481 
482  void Accept(ModelVisitor* const visitor) const override {
483  AcceptInternal(ModelVisitor::kCover, visitor);
484  }
485 
486  private:
487  PerformedStatus ComputePropagationUp(int parent_depth, int parent_position,
488  int64_t* const bucket_start_min,
489  int64_t* const bucket_start_max,
490  int64_t* const bucket_end_min,
491  int64_t* const bucket_end_max,
492  bool* one_undecided) {
493  *bucket_start_min = std::numeric_limits<int64_t>::max();
494  *bucket_start_max = std::numeric_limits<int64_t>::max();
495  *bucket_end_min = std::numeric_limits<int64_t>::min();
496  *bucket_end_max = std::numeric_limits<int64_t>::min();
497 
498  int may_be_performed_count = 0;
499  int must_be_performed_count = 0;
500  const int block_start = ChildStart(parent_position);
501  const int block_end = ChildEnd(parent_depth, parent_position);
502  for (int k = block_start; k <= block_end; ++k) {
503  const PerformedStatus performed = Performed(parent_depth + 1, k);
504  if (performed != UNPERFORMED) {
505  *bucket_start_min =
506  std::min(*bucket_start_min, StartMin(parent_depth + 1, k));
507  *bucket_end_max =
508  std::max(*bucket_end_max, EndMax(parent_depth + 1, k));
509  may_be_performed_count++;
510  if (performed == PERFORMED) {
511  *bucket_start_max =
512  std::min(*bucket_start_max, StartMax(parent_depth + 1, k));
513  *bucket_end_min =
514  std::max(*bucket_end_min, EndMin(parent_depth + 1, k));
515  must_be_performed_count++;
516  }
517  }
518  }
519  const PerformedStatus up_performed =
520  must_be_performed_count > 0
521  ? PERFORMED
522  : (may_be_performed_count > 0 ? UNDECIDED : UNPERFORMED);
523  *one_undecided =
524  (may_be_performed_count == 1) && (must_be_performed_count == 0);
525  return up_performed;
526  }
527 
528  Demon* cover_demon_;
529 };
530 
531 class IntervalEquality : public Constraint {
532  public:
533  IntervalEquality(Solver* const solver, IntervalVar* const var1,
534  IntervalVar* const var2)
535  : Constraint(solver), var1_(var1), var2_(var2) {}
536 
537  ~IntervalEquality() override {}
538 
539  void Post() override {
540  Demon* const demon = solver()->MakeConstraintInitialPropagateCallback(this);
541  var1_->WhenAnything(demon);
542  var2_->WhenAnything(demon);
543  }
544 
545  void InitialPropagate() override {
546  // Naive code. Can be split by property (performed, start...).
547  if (!var1_->MayBePerformed()) {
548  var2_->SetPerformed(false);
549  } else {
550  if (var1_->MustBePerformed()) {
551  var2_->SetPerformed(true);
552  }
553  var2_->SetStartRange(var1_->StartMin(), var1_->StartMax());
554  var2_->SetDurationRange(var1_->DurationMin(), var1_->DurationMax());
555  var2_->SetEndRange(var1_->EndMin(), var1_->EndMax());
556  }
557  if (!var2_->MayBePerformed()) {
558  var1_->SetPerformed(false);
559  } else {
560  if (var2_->MustBePerformed()) {
561  var1_->SetPerformed(true);
562  }
563  var1_->SetStartRange(var2_->StartMin(), var2_->StartMax());
564  var1_->SetDurationRange(var2_->DurationMin(), var2_->DurationMax());
565  var1_->SetEndRange(var2_->EndMin(), var2_->EndMax());
566  }
567  }
568 
569  std::string DebugString() const override {
570  return absl::StrFormat("Equality(%s, %s)", var1_->DebugString(),
571  var2_->DebugString());
572  }
573 
574  void Accept(ModelVisitor* const visitor) const override {
575  visitor->BeginVisitConstraint(ModelVisitor::kEquality, this);
576  visitor->VisitIntervalArgument(ModelVisitor::kLeftArgument, var1_);
577  visitor->VisitIntervalArgument(ModelVisitor::kRightArgument, var2_);
578  visitor->EndVisitConstraint(ModelVisitor::kEquality, this);
579  }
580 
581  private:
582  IntervalVar* const var1_;
583  IntervalVar* const var2_;
584 };
585 } // namespace
586 
587 Constraint* Solver::MakeCover(const std::vector<IntervalVar*>& vars,
588  IntervalVar* const target_var) {
589  CHECK(!vars.empty());
590  if (vars.size() == 1) {
591  return MakeEquality(vars[0], target_var);
592  } else {
593  return RevAlloc(new CoverConstraint(this, vars, target_var));
594  }
595 }
596 
598  IntervalVar* const var2) {
599  return RevAlloc(new IntervalEquality(this, var1, var2));
600 }
601 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
int64_t min
Definition: alldiff_cst.cc:139
Rev< int > performed
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
const std::string name
Rev< int64_t > start_min
A constraint is the main modeling object.
IntervalVar *const target_var_
Interval variables are often used in scheduling.
int64_t max
Definition: alldiff_cst.cc:140
Rev< int64_t > start_max
Rev< int64_t > end_max
#define CHECK_LE(val1, val2)
Definition: base/logging.h:700
Rev< int64_t > end_min
const std::vector< IntervalVar * > vars_
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
Constraint * MakeCover(const std::vector< IntervalVar * > &vars, IntervalVar *const target_var)
This constraint states that the target_var is the convex hull of the intervals.
T * RevAlloc(T *object)
Registers the given object as being reversible.
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
Collection of objects used to extend the Constraint Solver library.
std::string JoinDebugStringPtr(const std::vector< T > &v, const std::string &separator)
Definition: string_array.h:45
Constraint * MakeEquality(IntExpr *const left, IntExpr *const right)
left == right
Definition: range_cst.cc:512
SatParameters parameters
Demon * MakeDelayedConstraintDemon0(Solver *const s, T *const ct, void(T::*method)(), const std::string &name)
IntVar * var
Definition: expr_array.cc:1874
Demon * MakeConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889