OR-Tools  9.3
timetable_edgefinding.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
15
16#include <algorithm>
17#include <cstdint>
18#include <vector>
19
22#include "ortools/sat/integer.h"
25
26namespace operations_research {
27namespace sat {
28
30 const std::vector<AffineExpression>& demands, AffineExpression capacity,
31 SchedulingConstraintHelper* helper, IntegerTrail* integer_trail)
32 : num_tasks_(helper->NumTasks()),
33 demands_(demands),
34 capacity_(capacity),
35 helper_(helper),
36 integer_trail_(integer_trail) {
37 // Edge finding structures.
38 mandatory_energy_before_end_max_.resize(num_tasks_);
39 mandatory_energy_before_start_min_.resize(num_tasks_);
40
41 // Energy of free parts.
42 size_free_.resize(num_tasks_);
43 energy_free_.resize(num_tasks_);
44}
45
47 const int id = watcher->Register(this);
48 watcher->WatchUpperBound(capacity_.var, id);
49 helper_->WatchAllTasks(id, watcher);
50 for (int t = 0; t < num_tasks_; t++) {
51 watcher->WatchLowerBound(demands_[t].var, id);
52 }
53}
54
56 while (true) {
57 const int64_t old_timestamp = integer_trail_->num_enqueues();
58
59 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
60 if (!TimeTableEdgeFindingPass()) return false;
61
62 if (!helper_->SynchronizeAndSetTimeDirection(false)) return false;
63 if (!TimeTableEdgeFindingPass()) return false;
64
65 // Stop if no propagation.
66 if (old_timestamp == integer_trail_->num_enqueues()) break;
67 }
68 return true;
69}
70
71void TimeTableEdgeFinding::BuildTimeTable() {
72 scp_.clear();
73 ecp_.clear();
74
75 // Build start of compulsory part events.
76 for (const auto task_time :
78 const int t = task_time.task_index;
79 if (!helper_->IsPresent(t)) continue;
80 if (task_time.time < helper_->EndMin(t)) {
81 scp_.push_back(task_time);
82 }
83 }
84
85 // Build end of compulsory part events.
86 for (const auto task_time : helper_->TaskByIncreasingEndMin()) {
87 const int t = task_time.task_index;
88 if (!helper_->IsPresent(t)) continue;
89 if (helper_->StartMax(t) < task_time.time) {
90 ecp_.push_back(task_time);
91 }
92 }
93
94 DCHECK_EQ(scp_.size(), ecp_.size());
95
96 const std::vector<TaskTime>& by_decreasing_end_max =
97 helper_->TaskByDecreasingEndMax();
98 const std::vector<TaskTime>& by_start_min =
99 helper_->TaskByIncreasingStartMin();
100
101 IntegerValue height = IntegerValue(0);
102 IntegerValue energy = IntegerValue(0);
103
104 // We don't care since at the beginning heigh is zero, and previous_time will
105 // be correct after the first iteration.
106 IntegerValue previous_time = IntegerValue(0);
107
108 int index_scp = 0; // index of the next value in scp
109 int index_ecp = 0; // index of the next value in ecp
110 int index_smin = 0; // index of the next value in by_start_min_
111 int index_emax = num_tasks_ - 1; // index of the next value in by_end_max_
112
113 while (index_emax >= 0) {
114 // Next time point.
115 // TODO(user): could be simplified with a sentinel.
116 IntegerValue time = by_decreasing_end_max[index_emax].time;
117 if (index_smin < num_tasks_) {
118 time = std::min(time, by_start_min[index_smin].time);
119 }
120 if (index_scp < scp_.size()) {
121 time = std::min(time, scp_[index_scp].time);
122 }
123 if (index_ecp < ecp_.size()) {
124 time = std::min(time, ecp_[index_ecp].time);
125 }
126
127 // Total amount of energy contained in the timetable until time.
128 energy += (time - previous_time) * height;
129 previous_time = time;
130
131 // Store the energy contained in the timetable just before those events.
132 while (index_smin < num_tasks_ && by_start_min[index_smin].time == time) {
133 mandatory_energy_before_start_min_[by_start_min[index_smin].task_index] =
134 energy;
135 index_smin++;
136 }
137
138 // Store the energy contained in the timetable just before those events.
139 while (index_emax >= 0 && by_decreasing_end_max[index_emax].time == time) {
140 mandatory_energy_before_end_max_[by_decreasing_end_max[index_emax]
141 .task_index] = energy;
142 index_emax--;
143 }
144
145 // Process the starting compulsory parts.
146 while (index_scp < scp_.size() && scp_[index_scp].time == time) {
147 height += DemandMin(scp_[index_scp].task_index);
148 index_scp++;
149 }
150
151 // Process the ending compulsory parts.
152 while (index_ecp < ecp_.size() && ecp_[index_ecp].time == time) {
153 height -= DemandMin(ecp_[index_ecp].task_index);
154 index_ecp++;
155 }
156 }
157}
158
159bool TimeTableEdgeFinding::TimeTableEdgeFindingPass() {
160 // Initialize the data structures and build the free parts.
161 // --------------------------------------------------------
162 for (int t = 0; t < num_tasks_; ++t) {
163 // If the task has no mandatory part, then its free part is the task itself.
164 const IntegerValue start_max = helper_->StartMax(t);
165 const IntegerValue end_min = helper_->EndMin(t);
166 if (start_max >= end_min) {
167 size_free_[t] = helper_->SizeMin(t);
168 } else {
169 size_free_[t] = helper_->SizeMin(t) + start_max - end_min;
170 }
171 energy_free_[t] = size_free_[t] * DemandMin(t);
172 }
173
174 BuildTimeTable();
175 const auto& by_start_min = helper_->TaskByIncreasingStartMin();
176
177 IntegerValue previous_end = kMaxIntegerValue;
178
179 // Apply the Timetabling Edge Finding filtering rule.
180 // --------------------------------------------------
181 // The loop order is not important for correctness.
182 for (const TaskTime end_task_time : helper_->TaskByDecreasingEndMax()) {
183 const int end_task = end_task_time.task_index;
184
185 // TODO(user): consider optional tasks for additional propagation.
186 if (!helper_->IsPresent(end_task)) continue;
187 if (energy_free_[end_task] == 0) continue;
188
189 // We only need to consider each time point once.
190 if (end_task_time.time == previous_end) continue;
191 previous_end = end_task_time.time;
192
193 // Energy of the free parts contained in the interval [begin, end).
194 IntegerValue energy_free_parts = IntegerValue(0);
195
196 // Task that requires the biggest additional amount of energy to be
197 // scheduled at its minimum start time in the task interval [begin, end).
198 int max_task = -1;
199 IntegerValue free_energy_of_max_task_in_window(0);
200 IntegerValue extra_energy_required_by_max_task = kMinIntegerValue;
201
202 // Process task by decreasing start min.
203 for (const TaskTime begin_task_time : gtl::reversed_view(by_start_min)) {
204 const int begin_task = begin_task_time.task_index;
205
206 // TODO(user): consider optional tasks for additional propagation.
207 if (!helper_->IsPresent(begin_task)) continue;
208 if (energy_free_[begin_task] == 0) continue;
209
210 // The considered time window. Note that we use the "cached" values so
211 // that our mandatory energy before computation is correct.
212 const IntegerValue begin = begin_task_time.time; // Start min.
213 const IntegerValue end = end_task_time.time; // End max.
214
215 // Not a valid time window.
216 if (end <= begin) continue;
217
218 // We consider two different cases: either the free part overlaps the
219 // end of the interval (right) or it does not (inside).
220 //
221 // begin end
222 // v v
223 // right: ======|===
224 //
225 // begin end
226 // v v
227 // inside: ========== |
228 //
229 // In the inside case, the additional amount of energy required to
230 // schedule the task at its minimum start time is equal to the whole
231 // energy of the free part. In the right case, the additional energy is
232 // equal to the largest part of the free part that can fit in the task
233 // interval.
234 const IntegerValue end_max = helper_->EndMax(begin_task);
235 if (end_max <= end) {
236 // The whole task energy is contained in the task interval.
237 energy_free_parts += energy_free_[begin_task];
238 } else {
239 const IntegerValue demand_min = DemandMin(begin_task);
240 const IntegerValue extra_energy =
241 std::min(size_free_[begin_task], (end - begin)) * demand_min;
242
243 // This is not in the paper, but it is almost free for us to account for
244 // the free energy of this task that must be present in the window.
245 const IntegerValue free_energy_in_window =
246 std::max(IntegerValue(0),
247 size_free_[begin_task] - (end_max - end)) *
248 demand_min;
249
250 if (extra_energy > extra_energy_required_by_max_task) {
251 max_task = begin_task;
252 extra_energy_required_by_max_task = extra_energy;
253
254 // Account for the free energy of the old max task, and cache the
255 // new one for later.
256 energy_free_parts += free_energy_of_max_task_in_window;
257 free_energy_of_max_task_in_window = free_energy_in_window;
258 } else {
259 energy_free_parts += free_energy_in_window;
260 }
261 }
262
263 // No task to push. This happens if all the tasks that overlap the task
264 // interval are entirely contained in it.
265 // TODO(user): check that we should not fail if the interval is
266 // overloaded, i.e., available_energy < 0.
267 if (max_task == -1) continue;
268
269 // Compute the amount of energy available to schedule max_task.
270 const IntegerValue interval_energy = CapacityMax() * (end - begin);
271 const IntegerValue energy_mandatory =
272 mandatory_energy_before_end_max_[end_task] -
273 mandatory_energy_before_start_min_[begin_task];
274 const IntegerValue available_energy =
275 interval_energy - energy_free_parts - energy_mandatory;
276
277 // Enough energy to schedule max_task at its minimum start time.
278 if (extra_energy_required_by_max_task <= available_energy) continue;
279
280 // Compute the length of the mandatory subpart of max_task that should be
281 // considered as available.
282 //
283 // TODO(user): Because this use updated bounds, it might be more than what
284 // we accounted for in the precomputation. This is correct but could be
285 // improved uppon.
286 const IntegerValue mandatory_in = std::max(
287 IntegerValue(0), std::min(end, helper_->EndMin(max_task)) -
288 std::max(begin, helper_->StartMax(max_task)));
289
290 // Compute the new minimum start time of max_task.
291 const IntegerValue new_start =
292 end - mandatory_in - (available_energy / DemandMin(max_task));
293
294 // Push and explain only if the new start is bigger than the current one.
295 if (helper_->StartMin(max_task) < new_start) {
296 if (!IncreaseStartMin(begin, end, max_task, new_start)) return false;
297 }
298 }
299 }
300
301 return true;
302}
303
304bool TimeTableEdgeFinding::IncreaseStartMin(IntegerValue begin,
305 IntegerValue end, int task_index,
306 IntegerValue new_start) {
307 helper_->ClearReason();
308 std::vector<IntegerLiteral>* mutable_reason = helper_->MutableIntegerReason();
309
310 // Capacity of the resource.
311 if (capacity_.var != kNoIntegerVariable) {
312 mutable_reason->push_back(
313 integer_trail_->UpperBoundAsLiteral(capacity_.var));
314 }
315
316 // Variables of the task to be pushed. We do not need the end max for this
317 // task and we only need for it to begin in the time window.
318 if (demands_[task_index].var != kNoIntegerVariable) {
319 mutable_reason->push_back(
320 integer_trail_->LowerBoundAsLiteral(demands_[task_index].var));
321 }
322 helper_->AddStartMinReason(task_index, begin);
323 helper_->AddSizeMinReason(task_index);
324
325 // Task contributing to the energy in the interval.
326 for (int t = 0; t < num_tasks_; ++t) {
327 if (t == task_index) continue;
328 if (!helper_->IsPresent(t)) continue;
329 if (helper_->EndMax(t) <= begin) continue;
330 if (helper_->StartMin(t) >= end) continue;
331
332 if (demands_[t].var != kNoIntegerVariable) {
333 mutable_reason->push_back(
334 integer_trail_->LowerBoundAsLiteral(demands_[t].var));
335 }
336
337 // We need the reason for the energy contribution of this interval into
338 // [begin, end].
339 //
340 // TODO(user): Since we actually do not account fully for this energy, we
341 // could relax the reason more.
342 //
343 // TODO(user): This reason might not be enough in the presence of variable
344 // size intervals where StartMax and EndMin give rise to more energy
345 // that just using size min and these bounds. Fix.
346 helper_->AddStartMinReason(t, std::min(begin, helper_->StartMin(t)));
347 helper_->AddEndMaxReason(t, std::max(end, helper_->EndMax(t)));
348 helper_->AddSizeMinReason(t);
349 helper_->AddPresenceReason(t);
350 }
351
352 return helper_->IncreaseStartMin(task_index, new_start);
353}
354
355} // namespace sat
356} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1569
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1587
int Register(PropagatorInterface *propagator)
Definition: integer.cc:2028
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1477
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1482
const std::vector< TaskTime > & TaskByDecreasingEndMax()
Definition: intervals.cc:362
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:316
const std::vector< TaskTime > & TaskByIncreasingStartMin()
Definition: intervals.cc:325
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:564
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:520
const std::vector< TaskTime > & TaskByIncreasingEndMin()
Definition: intervals.cc:337
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue new_start_min)
Definition: intervals.cc:465
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:307
const std::vector< TaskTime > & TaskByDecreasingStartMax()
Definition: intervals.cc:349
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:586
void RegisterWith(GenericLiteralWatcher *watcher)
TimeTableEdgeFinding(const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper, IntegerTrail *integer_trail)
IntVar * var
Definition: expr_array.cc:1874
ReverseView< Container > reversed_view(const Container &c)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
Collection of objects used to extend the Constraint Solver library.
int64_t energy
Definition: resource.cc:354
int64_t time
Definition: resource.cc:1693
int64_t capacity
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > end_min
std::optional< int64_t > end