OR-Tools  8.0
timetable.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <functional>
18 #include <memory>
19 
20 #include "ortools/base/int_type.h"
21 #include "ortools/base/logging.h"
22 #include "ortools/util/sort.h"
23 
24 namespace operations_research {
25 namespace sat {
26 
28  const std::vector<AffineExpression>& demands, AffineExpression capacity,
29  IntegerTrail* integer_trail, SchedulingConstraintHelper* helper)
30  : num_tasks_(helper->NumTasks()),
31  demands_(demands),
32  capacity_(capacity),
33  integer_trail_(integer_trail),
34  helper_(helper) {
35  // Each task may create at most two profile rectangles. Such pattern appear if
36  // the profile is shaped like the Hanoi tower. The additional space is for
37  // both extremities and the sentinels.
38  profile_.reserve(2 * num_tasks_ + 4);
39 
40  // Reversible set of tasks to consider for propagation.
41  forward_num_tasks_to_sweep_ = num_tasks_;
42  forward_tasks_to_sweep_.resize(num_tasks_);
43  backward_num_tasks_to_sweep_ = num_tasks_;
44  backward_tasks_to_sweep_.resize(num_tasks_);
45 
46  num_profile_tasks_ = 0;
47  profile_tasks_.resize(num_tasks_);
48  positions_in_profile_tasks_.resize(num_tasks_);
49 
50  // Reversible bounds and starting height of the profile.
51  starting_profile_height_ = IntegerValue(0);
52 
53  for (int t = 0; t < num_tasks_; ++t) {
54  forward_tasks_to_sweep_[t] = t;
55  backward_tasks_to_sweep_[t] = t;
56  profile_tasks_[t] = t;
57  positions_in_profile_tasks_[t] = t;
58  }
59 }
60 
62  const int id = watcher->Register(this);
63  helper_->WatchAllTasks(id, watcher);
64  watcher->WatchUpperBound(capacity_.var, id);
65  for (int t = 0; t < num_tasks_; t++) {
66  watcher->WatchLowerBound(demands_[t].var, id);
67  }
68  watcher->RegisterReversibleInt(id, &forward_num_tasks_to_sweep_);
69  watcher->RegisterReversibleInt(id, &backward_num_tasks_to_sweep_);
70  watcher->RegisterReversibleInt(id, &num_profile_tasks_);
71 }
72 
74  // Repeat until the propagator does not filter anymore.
75  profile_changed_ = true;
76  while (profile_changed_) {
77  profile_changed_ = false;
78  // This can fail if the profile exceeds the resource capacity.
79  if (!BuildProfile()) return false;
80  // Update the minimum start times.
81  if (!SweepAllTasks(/*is_forward=*/true)) return false;
82  // We reuse the same profile, but reversed, to update the maximum end times.
83  ReverseProfile();
84  // Update the maximum end times (reversed problem).
85  if (!SweepAllTasks(/*is_forward=*/false)) return false;
86  }
87 
88  return true;
89 }
90 
91 bool TimeTablingPerTask::BuildProfile() {
92  helper_->SetTimeDirection(true); // forward
93 
94  // Update the set of tasks that contribute to the profile. Tasks that were
95  // contributing are still part of the profile so we only need to check the
96  // other tasks.
97  for (int i = num_profile_tasks_; i < num_tasks_; ++i) {
98  const int t1 = profile_tasks_[i];
99  if (helper_->IsPresent(t1) && helper_->StartMax(t1) < helper_->EndMin(t1)) {
100  // Swap values and positions.
101  const int t2 = profile_tasks_[num_profile_tasks_];
102  profile_tasks_[i] = t2;
103  profile_tasks_[num_profile_tasks_] = t1;
104  positions_in_profile_tasks_[t1] = num_profile_tasks_;
105  positions_in_profile_tasks_[t2] = i;
106  num_profile_tasks_++;
107  }
108  }
109 
110  const auto& by_decreasing_start_max = helper_->TaskByDecreasingStartMax();
111  const auto& by_end_min = helper_->TaskByIncreasingEndMin();
112 
113  // Build the profile.
114  // ------------------
115  profile_.clear();
116 
117  // Start and height of the highest profile rectangle.
118  profile_max_height_ = kMinIntegerValue;
119  IntegerValue max_height_start = kMinIntegerValue;
120 
121  // Add a sentinel to simplify the algorithm.
122  profile_.emplace_back(kMinIntegerValue, IntegerValue(0));
123 
124  // Start and height of the currently built profile rectange.
125  IntegerValue current_start = kMinIntegerValue;
126  IntegerValue current_height = starting_profile_height_;
127 
128  // Next start/end of the compulsory parts to be processed. Note that only the
129  // task for which IsInProfile() is true must be considered.
130  int next_start = num_tasks_ - 1;
131  int next_end = 0;
132  while (next_end < num_tasks_) {
133  const IntegerValue old_height = current_height;
134 
135  IntegerValue t = by_end_min[next_end].time;
136  if (next_start >= 0) {
137  t = std::min(t, by_decreasing_start_max[next_start].time);
138  }
139 
140  // Process the starting compulsory parts.
141  while (next_start >= 0 && by_decreasing_start_max[next_start].time == t) {
142  const int task_index = by_decreasing_start_max[next_start].task_index;
143  if (IsInProfile(task_index)) current_height += DemandMin(task_index);
144  --next_start;
145  }
146 
147  // Process the ending compulsory parts.
148  while (next_end < num_tasks_ && by_end_min[next_end].time == t) {
149  const int task_index = by_end_min[next_end].task_index;
150  if (IsInProfile(task_index)) current_height -= DemandMin(task_index);
151  ++next_end;
152  }
153 
154  // Insert a new profile rectangle if any.
155  if (current_height != old_height) {
156  profile_.emplace_back(current_start, old_height);
157  if (current_height > profile_max_height_) {
158  profile_max_height_ = current_height;
159  max_height_start = t;
160  }
161  current_start = t;
162  }
163  }
164 
165  // Build the last profile rectangle.
166  DCHECK_GE(current_height, 0);
167  profile_.emplace_back(current_start, IntegerValue(0));
168 
169  // Add a sentinel to simplify the algorithm.
170  profile_.emplace_back(kMaxIntegerValue, IntegerValue(0));
171 
172  // Increase the capacity variable if required.
173  return IncreaseCapacity(max_height_start, profile_max_height_);
174 }
175 
176 void TimeTablingPerTask::ReverseProfile() {
177  helper_->SetTimeDirection(false); // backward
178 
179  // We keep the sentinels inchanged.
180  for (int i = 1; i + 1 < profile_.size(); ++i) {
181  profile_[i].start = -profile_[i + 1].start;
182  }
183  std::reverse(profile_.begin() + 1, profile_.end() - 1);
184 }
185 
186 bool TimeTablingPerTask::SweepAllTasks(bool is_forward) {
187  // Tasks with a lower or equal demand will not be pushed.
188  const IntegerValue demand_threshold(
189  CapSub(CapacityMax().value(), profile_max_height_.value()));
190 
191  // Select the correct members depending on the direction.
192  int& num_tasks =
193  is_forward ? forward_num_tasks_to_sweep_ : backward_num_tasks_to_sweep_;
194  std::vector<int>& tasks =
195  is_forward ? forward_tasks_to_sweep_ : backward_tasks_to_sweep_;
196 
197  // TODO(user): On some problem, a big chunk of the time is spend just checking
198  // these conditions below because it requires indirect memory access to fetch
199  // the demand/duration/presence/start ...
200  for (int i = num_tasks - 1; i >= 0; --i) {
201  const int t = tasks[i];
202  if (helper_->IsAbsent(t) ||
203  (helper_->IsPresent(t) && helper_->StartIsFixed(t))) {
204  // This tasks does not have to be considered for propagation in the rest
205  // of the sub-tree. Note that StartIsFixed() depends on the time
206  // direction, it is why we use two lists.
207  std::swap(tasks[i], tasks[--num_tasks]);
208  continue;
209  }
210 
211  // Skip if demand is too low.
212  if (DemandMin(t) <= demand_threshold) {
213  if (DemandMax(t) == 0) {
214  // We can ignore this task for the rest of the subtree like above.
215  std::swap(tasks[i], tasks[--num_tasks]);
216  }
217 
218  // This task does not have to be considered for propagation in this
219  // particular iteration, but maybe it does later.
220  continue;
221  }
222 
223  // Skip if duration is zero.
224  if (helper_->DurationMin(t) == 0) {
225  if (helper_->DurationMax(t) == 0) {
226  std::swap(tasks[i], tasks[--num_tasks]);
227  }
228  continue;
229  }
230 
231  if (!SweepTask(t)) return false;
232  }
233 
234  return true;
235 }
236 
237 bool TimeTablingPerTask::SweepTask(int task_id) {
238  const IntegerValue start_max = helper_->StartMax(task_id);
239  const IntegerValue duration_min = helper_->DurationMin(task_id);
240  const IntegerValue initial_start_min = helper_->StartMin(task_id);
241  const IntegerValue initial_end_min = helper_->EndMin(task_id);
242 
243  IntegerValue new_start_min = initial_start_min;
244  IntegerValue new_end_min = initial_end_min;
245 
246  // Find the profile rectangle that overlaps the minimum start time of task_id.
247  // The sentinel prevents out of bound exceptions.
248  DCHECK(is_sorted(profile_.begin(), profile_.end()));
249  int rec_id =
250  std::upper_bound(profile_.begin(), profile_.end(), new_start_min,
251  [&](IntegerValue value, const ProfileRectangle& rect) {
252  return value < rect.start;
253  }) -
254  profile_.begin();
255  --rec_id;
256 
257  // A profile rectangle is in conflict with the task if its height exceeds
258  // conflict_height.
259  const IntegerValue conflict_height = CapacityMax() - DemandMin(task_id);
260 
261  // True if the task is in conflict with at least one profile rectangle.
262  bool conflict_found = false;
263 
264  // Last time point during which task_id was in conflict with a profile
265  // rectangle before being pushed.
266  IntegerValue last_initial_conflict = kMinIntegerValue;
267 
268  // Push the task from left to right until it does not overlap any conflicting
269  // rectangle. Pushing the task may push the end of its compulsory part on the
270  // right but will not change its start. The main loop of the propagator will
271  // take care of rebuilding the profile with these possible changes and to
272  // propagate again in order to reach the timetabling consistency or to fail if
273  // the profile exceeds the resource capacity.
274  IntegerValue limit = std::min(start_max, new_end_min);
275  for (; profile_[rec_id].start < limit; ++rec_id) {
276  // If the profile rectangle is not conflicting, go to the next rectangle.
277  if (profile_[rec_id].height <= conflict_height) continue;
278 
279  conflict_found = true;
280 
281  // Compute the next minimum start and end times of task_id. The variables
282  // are not updated yet.
283  new_start_min = profile_[rec_id + 1].start; // i.e. profile_[rec_id].end
284  if (start_max < new_start_min) {
285  if (IsInProfile(task_id)) {
286  // Because the task is part of the profile, we cannot push it further.
287  new_start_min = start_max;
288  } else {
289  // We have a conflict or we can push the task absence. In both cases
290  // we don't need more than start_max + 1 in the explanation below.
291  new_start_min = start_max + 1;
292  }
293  }
294 
295  new_end_min = std::max(new_end_min, new_start_min + duration_min);
296  limit = std::min(start_max, new_end_min);
297 
298  if (profile_[rec_id].start < initial_end_min) {
299  last_initial_conflict = std::min(new_start_min, initial_end_min) - 1;
300  }
301  }
302 
303  if (!conflict_found) return true;
304 
305  if (initial_start_min != new_start_min &&
306  !UpdateStartingTime(task_id, last_initial_conflict, new_start_min)) {
307  return false;
308  }
309 
310  // The profile needs to be recomputed if we pushed something (because it can
311  // have side effects). Note that for the case where the interval is optional
312  // but not its start, it is possible that UpdateStartingTime() didn't change
313  // the start, so we need to test this in order to avoid an infinite loop.
314  //
315  // TODO(user): find an efficient way to keep the start_max < new_end_min
316  // condition. The problem is that ReduceProfile() assumes that by_end_min and
317  // by_start_max are up to date (this is not necessarily the case if we use
318  // the old condition). A solution is to update those vector before calling
319  // ReduceProfile() or to ReduceProfile() directly after BuildProfile() in the
320  // main loop.
321  if (helper_->StartMin(task_id) != initial_start_min) {
322  profile_changed_ = true;
323  }
324 
325  return true;
326 }
327 
328 bool TimeTablingPerTask::UpdateStartingTime(int task_id, IntegerValue left,
329  IntegerValue right) {
330  helper_->ClearReason();
331 
332  AddProfileReason(left, right);
333  if (capacity_.var != kNoIntegerVariable) {
334  helper_->MutableIntegerReason()->push_back(
335  integer_trail_->UpperBoundAsLiteral(capacity_.var));
336  }
337 
338  // State of the task to be pushed.
339  helper_->AddEndMinReason(task_id, left + 1);
340  helper_->AddDurationMinReason(task_id, IntegerValue(1));
341  if (demands_[task_id].var != kNoIntegerVariable) {
342  helper_->MutableIntegerReason()->push_back(
343  integer_trail_->LowerBoundAsLiteral(demands_[task_id].var));
344  }
345 
346  // Explain the increase of the minimum start and end times.
347  return helper_->IncreaseStartMin(task_id, right);
348 }
349 
350 void TimeTablingPerTask::AddProfileReason(IntegerValue left,
351  IntegerValue right) {
352  for (int i = 0; i < num_profile_tasks_; ++i) {
353  const int t = profile_tasks_[i];
354 
355  // Do not consider the task if it does not overlap for sure (left, right).
356  const IntegerValue start_max = helper_->StartMax(t);
357  if (right <= start_max) continue;
358  const IntegerValue end_min = helper_->EndMin(t);
359  if (end_min <= left) continue;
360 
361  helper_->AddPresenceReason(t);
362  helper_->AddStartMaxReason(t, std::max(left, start_max));
363  helper_->AddEndMinReason(t, std::min(right, end_min));
364  if (demands_[t].var != kNoIntegerVariable) {
365  helper_->MutableIntegerReason()->push_back(
366  integer_trail_->LowerBoundAsLiteral(demands_[t].var));
367  }
368  }
369 }
370 
371 bool TimeTablingPerTask::IncreaseCapacity(IntegerValue time,
372  IntegerValue new_min) {
373  if (new_min <= CapacityMin()) return true;
374 
375  helper_->ClearReason();
376  AddProfileReason(time, time + 1);
377  if (capacity_.var == kNoIntegerVariable) {
378  return helper_->ReportConflict();
379  }
380 
381  helper_->MutableIntegerReason()->push_back(
382  integer_trail_->UpperBoundAsLiteral(capacity_.var));
383  return helper_->PushIntegerLiteral(capacity_.GreaterOrEqual(new_min));
384 }
385 
386 } // namespace sat
387 } // namespace operations_research
var
IntVar * var
Definition: expr_array.cc:1858
operations_research::sat::SchedulingConstraintHelper::AddDurationMinReason
void AddDurationMinReason(int t)
Definition: intervals.h:419
min
int64 min
Definition: alldiff_cst.cc:138
operations_research::CapSub
int64 CapSub(int64 x, int64 y)
Definition: saturated_arithmetic.h:154
operations_research::sat::kNoIntegerVariable
const IntegerVariable kNoIntegerVariable(-1)
max
int64 max
Definition: alldiff_cst.cc:139
operations_research::sat::SchedulingConstraintHelper::IsAbsent
bool IsAbsent(int t) const
Definition: intervals.h:397
operations_research::sat::GenericLiteralWatcher::WatchUpperBound
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1294
operations_research::sat::SchedulingConstraintHelper::EndMin
IntegerValue EndMin(int t) const
Definition: intervals.h:367
operations_research::sat::TimeTablingPerTask::TimeTablingPerTask
TimeTablingPerTask(const std::vector< AffineExpression > &demands, AffineExpression capacity, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
Definition: timetable.cc:27
logging.h
operations_research::sat::SchedulingConstraintHelper::SetTimeDirection
void SetTimeDirection(bool is_forward)
Definition: intervals.cc:142
operations_research::sat::SchedulingConstraintHelper::TaskByIncreasingEndMin
const std::vector< TaskTime > & TaskByIncreasingEndMin()
Definition: intervals.cc:168
value
int64 value
Definition: demon_profiler.cc:43
operations_research::sat::SchedulingConstraintHelper
Definition: intervals.h:137
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
operations_research::sat::SchedulingConstraintHelper::ClearReason
void ClearReason()
Definition: intervals.h:402
operations_research::sat::GenericLiteralWatcher::Register
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1798
operations_research::sat::SchedulingConstraintHelper::StartIsFixed
bool StartIsFixed(int t) const
Definition: intervals.h:380
operations_research::sat::IntegerTrail
Definition: integer.h:534
operations_research::sat::TimeTablingPerTask::RegisterWith
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:61
timetable.h
operations_research::sat::SchedulingConstraintHelper::DurationMax
IntegerValue DurationMax(int t) const
Definition: intervals.h:353
operations_research::sat::SchedulingConstraintHelper::TaskByDecreasingStartMax
const std::vector< TaskTime > & TaskByDecreasingStartMax()
Definition: intervals.cc:180
operations_research::sat::SchedulingConstraintHelper::StartMin
IntegerValue StartMin(int t) const
Definition: intervals.h:359
operations_research::sat::GenericLiteralWatcher
Definition: integer.h:1056
operations_research::sat::SchedulingConstraintHelper::AddEndMinReason
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:453
operations_research::sat::SchedulingConstraintHelper::StartMax
IntegerValue StartMax(int t) const
Definition: intervals.h:363
int_type.h
operations_research::sat::TimeTablingPerTask::Propagate
bool Propagate() final
Definition: timetable.cc:73
operations_research::sat::SchedulingConstraintHelper::WatchAllTasks
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:343
operations_research::sat::AffineExpression
Definition: integer.h:214
operations_research::sat::kMaxIntegerValue
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
operations_research::sat::IntegerTrail::UpperBoundAsLiteral
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1252
operations_research::sat::SchedulingConstraintHelper::IncreaseStartMin
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue new_min_start)
Definition: intervals.cc:310
start_max
Rev< int64 > start_max
Definition: sched_constraints.cc:242
operations_research::sat::SchedulingConstraintHelper::IsPresent
bool IsPresent(int t) const
Definition: intervals.h:392
operations_research::sat::IntegerTrail::LowerBoundAsLiteral
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1247
operations_research::sat::SchedulingConstraintHelper::AddStartMaxReason
void AddStartMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:445
operations_research::sat::SchedulingConstraintHelper::PushIntegerLiteral
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral bound)
Definition: intervals.cc:266
operations_research::sat::SchedulingConstraintHelper::MutableIntegerReason
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:233
sort.h
operations_research::sat::kMinIntegerValue
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
end_min
Rev< int64 > end_min
Definition: sched_constraints.cc:243
operations_research::sat::AffineExpression::var
IntegerVariable var
Definition: integer.h:236
operations_research::sat::AffineExpression::GreaterOrEqual
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
Definition: integer.cc:28
operations_research::sat::SchedulingConstraintHelper::ReportConflict
ABSL_MUST_USE_RESULT bool ReportConflict()
Definition: intervals.cc:338
capacity
int64 capacity
Definition: routing_flow.cc:129
operations_research::sat::SchedulingConstraintHelper::DurationMin
IntegerValue DurationMin(int t) const
Definition: intervals.h:347
operations_research::sat::GenericLiteralWatcher::WatchLowerBound
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1285
operations_research::sat::SchedulingConstraintHelper::AddPresenceReason
void AddPresenceReason(int t)
Definition: intervals.h:411
time
int64 time
Definition: resource.cc:1683
operations_research::sat::GenericLiteralWatcher::RegisterReversibleInt
void RegisterReversibleInt(int id, int *rev)
Definition: integer.cc:1842