OR-Tools  9.0
timetable.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 #include "ortools/sat/timetable.h"
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <functional>
19 #include <memory>
20 
21 #include "ortools/base/int_type.h"
22 #include "ortools/base/logging.h"
23 #include "ortools/util/sort.h"
24 
25 namespace operations_research {
26 namespace sat {
27 
28 void AddReservoirConstraint(std::vector<AffineExpression> times,
29  std::vector<IntegerValue> deltas,
30  std::vector<Literal> presences, int64_t min_level,
31  int64_t max_level, Model* model) {
32  // We only create a side if it can fail.
33  IntegerValue min_possible(0);
34  IntegerValue max_possible(0);
35  for (const IntegerValue d : deltas) {
36  if (d > 0) {
37  max_possible += d;
38  } else {
39  min_possible += d;
40  }
41  }
42  if (max_possible > max_level) {
43  model->TakeOwnership(new ReservoirTimeTabling(
44  times, deltas, presences, IntegerValue(max_level), model));
45  }
46  if (min_possible < min_level) {
47  for (IntegerValue& ref : deltas) ref = -ref;
48  model->TakeOwnership(new ReservoirTimeTabling(
49  times, deltas, presences, IntegerValue(-min_level), model));
50  }
51 }
52 
54  const std::vector<AffineExpression>& times,
55  const std::vector<IntegerValue>& deltas,
56  const std::vector<Literal>& presences, IntegerValue capacity, Model* model)
57  : times_(times),
58  deltas_(deltas),
59  presences_(presences),
60  capacity_(capacity),
61  assignment_(model->GetOrCreate<Trail>()->Assignment()),
62  integer_trail_(model->GetOrCreate<IntegerTrail>()) {
63  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
64  const int id = watcher->Register(this);
65  const int num_events = times.size();
66  for (int e = 0; e < num_events; e++) {
67  if (deltas_[e] > 0) {
68  watcher->WatchUpperBound(times_[e].var, id);
69  watcher->WatchLiteral(presences_[e], id);
70  }
71  if (deltas_[e] < 0) {
72  watcher->WatchLowerBound(times_[e].var, id);
73  watcher->WatchLiteral(presences_[e].Negated(), id);
74  }
75  }
76  watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(id);
77 }
78 
80  const int num_events = times_.size();
81  if (!BuildProfile()) return false;
82  for (int e = 0; e < num_events; e++) {
83  if (assignment_.LiteralIsFalse(presences_[e])) continue;
84 
85  // For positive delta, we can maybe increase the min.
86  if (deltas_[e] > 0 && !TryToIncreaseMin(e)) return false;
87 
88  // For negative delta, we can maybe decrease the max.
89  if (deltas_[e] < 0 && !TryToDecreaseMax(e)) return false;
90  }
91  return true;
92 }
93 
94 // We compute the lowest possible profile at time t.
95 //
96 // TODO(user): If we have precedences between events, we should be able to do
97 // more.
98 bool ReservoirTimeTabling::BuildProfile() {
99  // Starts by copying the "events" in the profile and sort them by time.
100  profile_.clear();
101  const int num_events = times_.size();
102  profile_.emplace_back(kMinIntegerValue, IntegerValue(0)); // Sentinel.
103  for (int e = 0; e < num_events; e++) {
104  if (deltas_[e] > 0) {
105  // Only consider present event for positive delta.
106  if (!assignment_.LiteralIsTrue(presences_[e])) continue;
107  const IntegerValue ub = integer_trail_->UpperBound(times_[e]);
108  profile_.push_back({ub, deltas_[e]});
109  } else if (deltas_[e] < 0) {
110  // Only consider non-absent event for negative delta.
111  if (assignment_.LiteralIsFalse(presences_[e])) continue;
112  profile_.push_back({integer_trail_->LowerBound(times_[e]), deltas_[e]});
113  }
114  }
115  profile_.emplace_back(kMaxIntegerValue, IntegerValue(0)); // Sentinel.
116  std::sort(profile_.begin(), profile_.end());
117 
118  // Accumulate delta and collapse entries.
119  int last = 0;
120  for (const ProfileRectangle& rect : profile_) {
121  if (rect.start == profile_[last].start) {
122  profile_[last].height += rect.height;
123  } else {
124  ++last;
125  profile_[last].start = rect.start;
126  profile_[last].height = rect.height + profile_[last - 1].height;
127  }
128  }
129  profile_.resize(last + 1);
130 
131  // Conflict?
132  for (const ProfileRectangle& rect : profile_) {
133  if (rect.height <= capacity_) continue;
134  FillReasonForProfileAtGivenTime(rect.start);
135  return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
136  }
137 
138  return true;
139 }
140 
141 // TODO(user): Minimize with how high the profile needs to be. We can also
142 // remove from the reason the absence of a negative event provided that the
143 // level zero min of the event is greater than t anyway.
144 //
145 // TODO(user): Make sure the code work with fixed time since pushing always
146 // true/false literal to the reason is not completely supported.
147 void ReservoirTimeTabling::FillReasonForProfileAtGivenTime(
148  IntegerValue t, int event_to_ignore) {
149  integer_reason_.clear();
150  literal_reason_.clear();
151  const int num_events = times_.size();
152  for (int e = 0; e < num_events; e++) {
153  if (e == event_to_ignore) continue;
154  if (deltas_[e] > 0) {
155  if (!assignment_.LiteralIsTrue(presences_[e])) continue;
156  if (integer_trail_->UpperBound(times_[e]) > t) continue;
157  integer_reason_.push_back(times_[e].LowerOrEqual(t));
158  literal_reason_.push_back(presences_[e].Negated());
159  } else if (deltas_[e] < 0) {
160  if (assignment_.LiteralIsFalse(presences_[e])) {
161  literal_reason_.push_back(presences_[e]);
162  } else if (integer_trail_->LowerBound(times_[e]) > t) {
163  integer_reason_.push_back(times_[e].GreaterOrEqual(t + 1));
164  }
165  }
166  }
167 }
168 
169 // Note that a negative event will always be in the profile, even if its
170 // presence is still not settled.
171 bool ReservoirTimeTabling::TryToDecreaseMax(int event) {
172  CHECK_LT(deltas_[event], 0);
173  const IntegerValue start = integer_trail_->LowerBound(times_[event]);
174  const IntegerValue end = integer_trail_->UpperBound(times_[event]);
175 
176  // We already tested for conflict in BuildProfile().
177  if (start == end) return true;
178 
179  // Find the profile rectangle that overlaps the start of the given event.
180  // The sentinel prevents out of bound exceptions.
181  DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
182  int rec_id =
183  std::upper_bound(profile_.begin(), profile_.end(), start,
184  [&](IntegerValue value, const ProfileRectangle& rect) {
185  return value < rect.start;
186  }) -
187  profile_.begin();
188  --rec_id;
189 
190  bool push = false;
191  IntegerValue new_end = end;
192  for (; profile_[rec_id].start < end; ++rec_id) {
193  if (profile_[rec_id].height - deltas_[event] > capacity_) {
194  new_end = profile_[rec_id].start;
195  push = true;
196  break;
197  }
198  }
199  if (!push) return true;
200 
201  // The reason is simply why the capacity at new_end (without the event)
202  // would overflow.
203  FillReasonForProfileAtGivenTime(new_end, event);
204 
205  // Note(user): I don't think this is possible since it would have been
206  // detected at profile construction, but then, since the bound might have been
207  // updated, better be defensive.
208  if (new_end < start) {
209  integer_reason_.push_back(times_[event].GreaterOrEqual(new_end + 1));
210  return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
211  }
212 
213  // First, the task MUST be present, otherwise we have a conflict.
214  //
215  // TODO(user): We actually need to look after 'end' to potentially push the
216  // presence in more situation.
217  if (!assignment_.LiteralIsTrue(presences_[event])) {
218  integer_trail_->EnqueueLiteral(presences_[event], literal_reason_,
219  integer_reason_);
220  }
221 
222  // Push new_end too. Note that we don't need the presence reason.
223  return integer_trail_->Enqueue(times_[event].LowerOrEqual(new_end),
224  literal_reason_, integer_reason_);
225 }
226 
227 bool ReservoirTimeTabling::TryToIncreaseMin(int event) {
228  CHECK_GT(deltas_[event], 0);
229  const IntegerValue start = integer_trail_->LowerBound(times_[event]);
230  const IntegerValue end = integer_trail_->UpperBound(times_[event]);
231 
232  // We already tested for conflict in BuildProfile().
233  if (start == end) return true;
234 
235  // Find the profile rectangle containing the end of the given event.
236  // The sentinel prevents out of bound exceptions.
237  //
238  // TODO(user): If the task is no present, we should actually look at the
239  // maximum profile after end to maybe push its absence.
240  DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
241  int rec_id =
242  std::upper_bound(profile_.begin(), profile_.end(), end,
243  [&](IntegerValue value, const ProfileRectangle& rect) {
244  return value < rect.start;
245  }) -
246  profile_.begin();
247  --rec_id;
248 
249  bool push = false;
250  IntegerValue new_start = start;
251  if (profile_[rec_id].height + deltas_[event] > capacity_) {
252  if (!assignment_.LiteralIsTrue(presences_[event])) {
253  // Push to false since it wasn't part of the profile and cannot fit.
254  push = true;
255  new_start = end + 1;
256  } else if (profile_[rec_id].start < end) {
257  // It must be at end in this case.
258  push = true;
259  new_start = end;
260  }
261  }
262  if (!push) {
263  for (; profile_[rec_id].start > start; --rec_id) {
264  if (profile_[rec_id - 1].height + deltas_[event] > capacity_) {
265  push = true;
266  new_start = profile_[rec_id].start;
267  break;
268  }
269  }
270  }
271  if (!push) return true;
272 
273  // The reason is simply the capacity at new_start - 1;
274  FillReasonForProfileAtGivenTime(new_start - 1, event);
275  return integer_trail_->ConditionalEnqueue(
276  presences_[event], times_[event].GreaterOrEqual(new_start),
277  &literal_reason_, &integer_reason_);
278 }
279 
281  const std::vector<AffineExpression>& demands, AffineExpression capacity,
282  IntegerTrail* integer_trail, SchedulingConstraintHelper* helper)
283  : num_tasks_(helper->NumTasks()),
284  demands_(demands),
285  capacity_(capacity),
286  integer_trail_(integer_trail),
287  helper_(helper) {
288  // Each task may create at most two profile rectangles. Such pattern appear if
289  // the profile is shaped like the Hanoi tower. The additional space is for
290  // both extremities and the sentinels.
291  profile_.reserve(2 * num_tasks_ + 4);
292 
293  // Reversible set of tasks to consider for propagation.
294  forward_num_tasks_to_sweep_ = num_tasks_;
295  forward_tasks_to_sweep_.resize(num_tasks_);
296  backward_num_tasks_to_sweep_ = num_tasks_;
297  backward_tasks_to_sweep_.resize(num_tasks_);
298 
299  num_profile_tasks_ = 0;
300  profile_tasks_.resize(num_tasks_);
301  positions_in_profile_tasks_.resize(num_tasks_);
302 
303  // Reversible bounds and starting height of the profile.
304  starting_profile_height_ = IntegerValue(0);
305 
306  for (int t = 0; t < num_tasks_; ++t) {
307  forward_tasks_to_sweep_[t] = t;
308  backward_tasks_to_sweep_[t] = t;
309  profile_tasks_[t] = t;
310  positions_in_profile_tasks_[t] = t;
311  }
312 }
313 
315  const int id = watcher->Register(this);
316  helper_->WatchAllTasks(id, watcher);
317  watcher->WatchUpperBound(capacity_.var, id);
318  for (int t = 0; t < num_tasks_; t++) {
319  watcher->WatchLowerBound(demands_[t].var, id);
320  }
321  watcher->RegisterReversibleInt(id, &forward_num_tasks_to_sweep_);
322  watcher->RegisterReversibleInt(id, &backward_num_tasks_to_sweep_);
323  watcher->RegisterReversibleInt(id, &num_profile_tasks_);
324 
325  // Changing the times or pushing task absence migth have side effects on the
326  // other intervals, so we would need to be called again in this case.
328 }
329 
330 // Note that we relly on being called again to reach a fixed point.
332  // This can fail if the profile exceeds the resource capacity.
333  if (!BuildProfile()) return false;
334 
335  // Update the minimum start times.
336  if (!SweepAllTasks(/*is_forward=*/true)) return false;
337 
338  // We reuse the same profile, but reversed, to update the maximum end times.
339  ReverseProfile();
340 
341  // Update the maximum end times (reversed problem).
342  if (!SweepAllTasks(/*is_forward=*/false)) return false;
343 
344  return true;
345 }
346 
347 bool TimeTablingPerTask::BuildProfile() {
348  helper_->SynchronizeAndSetTimeDirection(true); // forward
349 
350  // Update the set of tasks that contribute to the profile. Tasks that were
351  // contributing are still part of the profile so we only need to check the
352  // other tasks.
353  for (int i = num_profile_tasks_; i < num_tasks_; ++i) {
354  const int t1 = profile_tasks_[i];
355  if (helper_->IsPresent(t1) && helper_->StartMax(t1) < helper_->EndMin(t1)) {
356  // Swap values and positions.
357  const int t2 = profile_tasks_[num_profile_tasks_];
358  profile_tasks_[i] = t2;
359  profile_tasks_[num_profile_tasks_] = t1;
360  positions_in_profile_tasks_[t1] = num_profile_tasks_;
361  positions_in_profile_tasks_[t2] = i;
362  num_profile_tasks_++;
363  }
364  }
365 
366  const auto& by_decreasing_start_max = helper_->TaskByDecreasingStartMax();
367  const auto& by_end_min = helper_->TaskByIncreasingEndMin();
368 
369  // Build the profile.
370  // ------------------
371  profile_.clear();
372 
373  // Start and height of the highest profile rectangle.
374  profile_max_height_ = kMinIntegerValue;
375  IntegerValue max_height_start = kMinIntegerValue;
376 
377  // Add a sentinel to simplify the algorithm.
378  profile_.emplace_back(kMinIntegerValue, IntegerValue(0));
379 
380  // Start and height of the currently built profile rectange.
381  IntegerValue current_start = kMinIntegerValue;
382  IntegerValue current_height = starting_profile_height_;
383 
384  // Next start/end of the compulsory parts to be processed. Note that only the
385  // task for which IsInProfile() is true must be considered.
386  int next_start = num_tasks_ - 1;
387  int next_end = 0;
388  while (next_end < num_tasks_) {
389  const IntegerValue old_height = current_height;
390 
391  IntegerValue t = by_end_min[next_end].time;
392  if (next_start >= 0) {
393  t = std::min(t, by_decreasing_start_max[next_start].time);
394  }
395 
396  // Process the starting compulsory parts.
397  while (next_start >= 0 && by_decreasing_start_max[next_start].time == t) {
398  const int task_index = by_decreasing_start_max[next_start].task_index;
399  if (IsInProfile(task_index)) current_height += DemandMin(task_index);
400  --next_start;
401  }
402 
403  // Process the ending compulsory parts.
404  while (next_end < num_tasks_ && by_end_min[next_end].time == t) {
405  const int task_index = by_end_min[next_end].task_index;
406  if (IsInProfile(task_index)) current_height -= DemandMin(task_index);
407  ++next_end;
408  }
409 
410  // Insert a new profile rectangle if any.
411  if (current_height != old_height) {
412  profile_.emplace_back(current_start, old_height);
413  if (current_height > profile_max_height_) {
414  profile_max_height_ = current_height;
415  max_height_start = t;
416  }
417  current_start = t;
418  }
419  }
420 
421  // Build the last profile rectangle.
422  DCHECK_GE(current_height, 0);
423  profile_.emplace_back(current_start, IntegerValue(0));
424 
425  // Add a sentinel to simplify the algorithm.
426  profile_.emplace_back(kMaxIntegerValue, IntegerValue(0));
427 
428  // Increase the capacity variable if required.
429  return IncreaseCapacity(max_height_start, profile_max_height_);
430 }
431 
432 void TimeTablingPerTask::ReverseProfile() {
433  helper_->SynchronizeAndSetTimeDirection(false); // backward
434 
435  // We keep the sentinels inchanged.
436  for (int i = 1; i + 1 < profile_.size(); ++i) {
437  profile_[i].start = -profile_[i + 1].start;
438  }
439  std::reverse(profile_.begin() + 1, profile_.end() - 1);
440 }
441 
442 bool TimeTablingPerTask::SweepAllTasks(bool is_forward) {
443  // Tasks with a lower or equal demand will not be pushed.
444  const IntegerValue demand_threshold(
445  CapSub(CapacityMax().value(), profile_max_height_.value()));
446 
447  // Select the correct members depending on the direction.
448  int& num_tasks =
449  is_forward ? forward_num_tasks_to_sweep_ : backward_num_tasks_to_sweep_;
450  std::vector<int>& tasks =
451  is_forward ? forward_tasks_to_sweep_ : backward_tasks_to_sweep_;
452 
453  // TODO(user): On some problem, a big chunk of the time is spend just checking
454  // these conditions below because it requires indirect memory access to fetch
455  // the demand/size/presence/start ...
456  for (int i = num_tasks - 1; i >= 0; --i) {
457  const int t = tasks[i];
458  if (helper_->IsAbsent(t) ||
459  (helper_->IsPresent(t) && helper_->StartIsFixed(t))) {
460  // This tasks does not have to be considered for propagation in the rest
461  // of the sub-tree. Note that StartIsFixed() depends on the time
462  // direction, it is why we use two lists.
463  std::swap(tasks[i], tasks[--num_tasks]);
464  continue;
465  }
466 
467  // Skip if demand is too low.
468  if (DemandMin(t) <= demand_threshold) {
469  if (DemandMax(t) == 0) {
470  // We can ignore this task for the rest of the subtree like above.
471  std::swap(tasks[i], tasks[--num_tasks]);
472  }
473 
474  // This task does not have to be considered for propagation in this
475  // particular iteration, but maybe it does later.
476  continue;
477  }
478 
479  // Skip if size is zero.
480  if (helper_->SizeMin(t) == 0) {
481  if (helper_->SizeMax(t) == 0) {
482  std::swap(tasks[i], tasks[--num_tasks]);
483  }
484  continue;
485  }
486 
487  if (!SweepTask(t)) return false;
488  }
489 
490  return true;
491 }
492 
493 bool TimeTablingPerTask::SweepTask(int task_id) {
494  const IntegerValue start_max = helper_->StartMax(task_id);
495  const IntegerValue size_min = helper_->SizeMin(task_id);
496  const IntegerValue initial_start_min = helper_->StartMin(task_id);
497  const IntegerValue initial_end_min = helper_->EndMin(task_id);
498 
499  IntegerValue new_start_min = initial_start_min;
500  IntegerValue new_end_min = initial_end_min;
501 
502  // Find the profile rectangle that overlaps the minimum start time of task_id.
503  // The sentinel prevents out of bound exceptions.
504  DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
505  int rec_id =
506  std::upper_bound(profile_.begin(), profile_.end(), new_start_min,
507  [&](IntegerValue value, const ProfileRectangle& rect) {
508  return value < rect.start;
509  }) -
510  profile_.begin();
511  --rec_id;
512 
513  // A profile rectangle is in conflict with the task if its height exceeds
514  // conflict_height.
515  const IntegerValue conflict_height = CapacityMax() - DemandMin(task_id);
516 
517  // True if the task is in conflict with at least one profile rectangle.
518  bool conflict_found = false;
519 
520  // Last time point during which task_id was in conflict with a profile
521  // rectangle before being pushed.
522  IntegerValue last_initial_conflict = kMinIntegerValue;
523 
524  // Push the task from left to right until it does not overlap any conflicting
525  // rectangle. Pushing the task may push the end of its compulsory part on the
526  // right but will not change its start. The main loop of the propagator will
527  // take care of rebuilding the profile with these possible changes and to
528  // propagate again in order to reach the timetabling consistency or to fail if
529  // the profile exceeds the resource capacity.
530  IntegerValue limit = std::min(start_max, new_end_min);
531  for (; profile_[rec_id].start < limit; ++rec_id) {
532  // If the profile rectangle is not conflicting, go to the next rectangle.
533  if (profile_[rec_id].height <= conflict_height) continue;
534 
535  conflict_found = true;
536 
537  // Compute the next minimum start and end times of task_id. The variables
538  // are not updated yet.
539  new_start_min = profile_[rec_id + 1].start; // i.e. profile_[rec_id].end
540  if (start_max < new_start_min) {
541  if (IsInProfile(task_id)) {
542  // Because the task is part of the profile, we cannot push it further.
543  new_start_min = start_max;
544  } else {
545  // We have a conflict or we can push the task absence. In both cases
546  // we don't need more than start_max + 1 in the explanation below.
547  new_start_min = start_max + 1;
548  }
549  }
550 
551  new_end_min = std::max(new_end_min, new_start_min + size_min);
552  limit = std::min(start_max, new_end_min);
553 
554  if (profile_[rec_id].start < initial_end_min) {
555  last_initial_conflict = std::min(new_start_min, initial_end_min) - 1;
556  }
557  }
558 
559  if (!conflict_found) return true;
560 
561  if (initial_start_min != new_start_min &&
562  !UpdateStartingTime(task_id, last_initial_conflict, new_start_min)) {
563  return false;
564  }
565 
566  return true;
567 }
568 
569 bool TimeTablingPerTask::UpdateStartingTime(int task_id, IntegerValue left,
570  IntegerValue right) {
571  helper_->ClearReason();
572 
573  AddProfileReason(left, right);
574  if (capacity_.var != kNoIntegerVariable) {
575  helper_->MutableIntegerReason()->push_back(
576  integer_trail_->UpperBoundAsLiteral(capacity_.var));
577  }
578 
579  // State of the task to be pushed.
580  helper_->AddEndMinReason(task_id, left + 1);
581  helper_->AddSizeMinReason(task_id, IntegerValue(1));
582  if (demands_[task_id].var != kNoIntegerVariable) {
583  helper_->MutableIntegerReason()->push_back(
584  integer_trail_->LowerBoundAsLiteral(demands_[task_id].var));
585  }
586 
587  // Explain the increase of the minimum start and end times.
588  return helper_->IncreaseStartMin(task_id, right);
589 }
590 
591 void TimeTablingPerTask::AddProfileReason(IntegerValue left,
592  IntegerValue right) {
593  for (int i = 0; i < num_profile_tasks_; ++i) {
594  const int t = profile_tasks_[i];
595 
596  // Do not consider the task if it does not overlap for sure (left, right).
597  const IntegerValue start_max = helper_->StartMax(t);
598  if (right <= start_max) continue;
599  const IntegerValue end_min = helper_->EndMin(t);
600  if (end_min <= left) continue;
601 
602  helper_->AddPresenceReason(t);
603  helper_->AddStartMaxReason(t, std::max(left, start_max));
604  helper_->AddEndMinReason(t, std::min(right, end_min));
605  if (demands_[t].var != kNoIntegerVariable) {
606  helper_->MutableIntegerReason()->push_back(
607  integer_trail_->LowerBoundAsLiteral(demands_[t].var));
608  }
609  }
610 }
611 
612 bool TimeTablingPerTask::IncreaseCapacity(IntegerValue time,
613  IntegerValue new_min) {
614  if (new_min <= CapacityMin()) return true;
615 
616  helper_->ClearReason();
617  AddProfileReason(time, time + 1);
618  if (capacity_.var == kNoIntegerVariable) {
619  return helper_->ReportConflict();
620  }
621 
622  helper_->MutableIntegerReason()->push_back(
623  integer_trail_->UpperBoundAsLiteral(capacity_.var));
624  return helper_->PushIntegerLiteral(capacity_.GreaterOrEqual(new_min));
625 }
626 
627 } // namespace sat
628 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_GT(val1, val2)
Definition: base/logging.h:710
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define DCHECK(condition)
Definition: base/logging.h:892
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1378
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1396
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1945
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:993
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1335
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.h:815
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1091
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1309
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1305
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1340
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
Definition: integer.cc:1000
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
ReservoirTimeTabling(const std::vector< AffineExpression > &times, const std::vector< IntegerValue > &deltas, const std::vector< Literal > &presences, IntegerValue capacity, Model *model)
Definition: timetable.cc:53
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:378
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:460
const std::vector< TaskTime > & TaskByIncreasingEndMin()
Definition: intervals.cc:277
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:297
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:536
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue new_start_min)
Definition: intervals.cc:405
const std::vector< TaskTime > & TaskByDecreasingStartMax()
Definition: intervals.cc:289
void AddStartMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:514
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:314
TimeTablingPerTask(const std::vector< AffineExpression > &demands, AffineExpression capacity, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
Definition: timetable.cc:280
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:151
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:148
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
GRBmodel * model
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< IntegerValue > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
Definition: timetable.cc:28
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1500
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition: integer.h:1515
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
int64_t time
Definition: resource.cc:1691
int64_t capacity
Rev< int64_t > start_max
Rev< int64_t > end_min
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
Definition: integer.h:1290