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