20#include "absl/log/check.h"
21#include "absl/types/span.h"
33 std::vector<AffineExpression> deltas,
34 std::vector<Literal> presences, int64_t min_level,
35 int64_t max_level,
Model* model) {
37 IntegerValue min_possible(0);
38 IntegerValue max_possible(0);
41 min_possible += std::min(IntegerValue(0), integer_trail->LowerBound(d));
42 max_possible += std::max(IntegerValue(0), integer_trail->UpperBound(d));
44 if (max_possible > max_level) {
46 times, deltas, presences, IntegerValue(max_level), model));
48 if (min_possible < min_level) {
51 times, deltas, presences, IntegerValue(-min_level), model));
56 const std::vector<AffineExpression>& times,
57 const std::vector<AffineExpression>& deltas,
58 const std::vector<Literal>& presences, IntegerValue capacity,
Model* model)
61 presences_(presences),
66 const int id = watcher->
Register(
this);
67 const int num_events = times.size();
68 for (
int e = 0; e < num_events; e++) {
69 watcher->WatchLowerBound(deltas_[e],
id);
70 if (integer_trail_->UpperBound(deltas_[e]) > 0) {
71 watcher->WatchUpperBound(times_[e].var,
id);
72 watcher->WatchLiteral(presences_[e],
id);
74 if (integer_trail_->LowerBound(deltas_[e]) < 0) {
75 watcher->WatchLowerBound(times_[e].var,
id);
76 watcher->WatchLiteral(presences_[e].Negated(),
id);
79 watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(
id);
83 const int num_events = times_.size();
84 if (!BuildProfile())
return false;
85 for (
int e = 0; e < num_events; e++) {
86 if (assignment_.LiteralIsFalse(presences_[e]))
continue;
89 const IntegerValue min_d = integer_trail_->LowerBound(deltas_[e]);
90 if (min_d > 0 && !TryToIncreaseMin(e))
return false;
93 if (min_d < 0 && !TryToDecreaseMax(e))
return false;
102bool ReservoirTimeTabling::BuildProfile() {
105 const int num_events = times_.size();
107 for (
int e = 0; e < num_events; e++) {
108 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[e]);
112 const IntegerValue ub = integer_trail_->
UpperBound(times_[e]);
113 profile_.push_back({ub, min_d});
114 }
else if (min_d < 0) {
117 profile_.push_back({integer_trail_->
LowerBound(times_[e]), min_d});
121 std::sort(profile_.begin(), profile_.end());
125 for (
const ProfileRectangle& rect : profile_) {
126 if (rect.start == profile_[last].start) {
127 profile_[last].height += rect.height;
130 profile_[last].start = rect.start;
131 profile_[last].height = rect.height + profile_[last - 1].height;
134 profile_.resize(last + 1);
137 for (
const ProfileRectangle& rect : profile_) {
138 if (rect.height <= capacity_)
continue;
140 FillReasonForProfileAtGivenTime(rect.start);
141 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
150 std::vector<IntegerLiteral>* reason) {
151 if (expr.IsConstant())
return;
152 reason->push_back(expr.LowerOrEqual(bound));
156 std::vector<IntegerLiteral>* reason) {
157 if (expr.IsConstant())
return;
158 reason->push_back(expr.GreaterOrEqual(bound));
169void ReservoirTimeTabling::FillReasonForProfileAtGivenTime(
170 IntegerValue t,
int event_to_ignore) {
171 integer_reason_.clear();
172 literal_reason_.clear();
173 const int num_events = times_.size();
174 for (
int e = 0; e < num_events; e++) {
175 if (e == event_to_ignore)
continue;
176 const IntegerValue min_d = integer_trail_->LowerBound(deltas_[e]);
178 if (!assignment_.LiteralIsTrue(presences_[e]))
continue;
179 if (integer_trail_->UpperBound(times_[e]) > t)
continue;
180 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
181 AddLowerOrEqual(times_[e], t, &integer_reason_);
182 literal_reason_.push_back(presences_[e].Negated());
183 }
else if (min_d <= 0) {
184 if (assignment_.LiteralIsFalse(presences_[e])) {
185 literal_reason_.push_back(presences_[e]);
188 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
189 if (min_d < 0 && integer_trail_->
LowerBound(times_[e]) > t) {
190 AddGreaterOrEqual(times_[e], t + 1, &integer_reason_);
198bool ReservoirTimeTabling::TryToDecreaseMax(
int event) {
199 const IntegerValue min_d = integer_trail_->LowerBound(deltas_[event]);
201 const IntegerValue start = integer_trail_->LowerBound(times_[event]);
202 const IntegerValue end = integer_trail_->UpperBound(times_[event]);
205 if (start == end)
return true;
209 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
211 std::upper_bound(profile_.begin(), profile_.end(), start,
212 [&](IntegerValue value,
const ProfileRectangle& rect) {
213 return value < rect.start;
219 IntegerValue new_end = end;
220 for (; profile_[rec_id].start < end; ++rec_id) {
221 if (profile_[rec_id].height - min_d > capacity_) {
222 new_end = profile_[rec_id].start;
227 if (!push)
return true;
231 FillReasonForProfileAtGivenTime(new_end, event);
236 if (new_end < start) {
237 AddGreaterOrEqual(times_[event], new_end + 1, &integer_reason_);
238 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
245 if (!assignment_.LiteralIsTrue(presences_[event])) {
246 integer_trail_->EnqueueLiteral(presences_[event], literal_reason_,
251 return integer_trail_->Enqueue(times_[event].
LowerOrEqual(new_end),
252 literal_reason_, integer_reason_);
255bool ReservoirTimeTabling::TryToIncreaseMin(
int event) {
256 const IntegerValue min_d = integer_trail_->LowerBound(deltas_[event]);
258 const IntegerValue start = integer_trail_->LowerBound(times_[event]);
259 const IntegerValue end = integer_trail_->UpperBound(times_[event]);
262 if (start == end)
return true;
269 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
271 std::upper_bound(profile_.begin(), profile_.end(), end,
272 [&](IntegerValue value,
const ProfileRectangle& rect) {
273 return value < rect.start;
279 IntegerValue new_start = start;
280 if (profile_[rec_id].height + min_d > capacity_) {
281 if (!assignment_.LiteralIsTrue(presences_[event])) {
285 }
else if (profile_[rec_id].start < end) {
292 for (; profile_[rec_id].start > start; --rec_id) {
293 if (profile_[rec_id - 1].height + min_d > capacity_) {
295 new_start = profile_[rec_id].start;
300 if (!push)
return true;
303 FillReasonForProfileAtGivenTime(new_start - 1, event);
304 AddGreaterOrEqual(deltas_[event], min_d, &integer_reason_);
305 return integer_trail_->ConditionalEnqueue(
307 &literal_reason_, &integer_reason_);
314 : num_tasks_(helper->NumTasks()),
322 profile_.reserve(2 * num_tasks_ + 4);
324 num_profile_tasks_ = 0;
325 profile_tasks_.resize(num_tasks_);
327 initial_max_demand_ = IntegerValue(0);
328 const bool capa_is_fixed = integer_trail_->IsFixed(capacity_);
329 const IntegerValue capa_min = integer_trail_->LowerBound(capacity_);
330 for (
int t = 0; t < num_tasks_; ++t) {
331 profile_tasks_[t] = t;
333 if (capa_is_fixed && demands_->DemandMin(t) >= capa_min) {
337 has_demand_equal_to_capacity_ =
true;
340 initial_max_demand_ = std::max(initial_max_demand_, demands_->DemandMax(t));
345 const int id = watcher->
Register(
this);
346 helper_->WatchAllTasks(
id);
348 for (
int t = 0; t < num_tasks_; t++) {
361 if (!BuildProfile())
return false;
364 if (!SweepAllTasks())
return false;
367 if (!helper_->SynchronizeAndSetTimeDirection(
false))
return false;
371 if (!SweepAllTasks())
return false;
376bool TimeTablingPerTask::BuildProfile() {
382 for (
int i = num_profile_tasks_;
i < num_tasks_; ++
i) {
383 const int t = profile_tasks_[
i];
385 std::swap(profile_tasks_[
i], profile_tasks_[num_profile_tasks_]);
386 num_profile_tasks_++;
391 cached_demands_min_.assign(num_tasks_, 0);
392 absl::Span<IntegerValue> demands_min = absl::MakeSpan(cached_demands_min_);
393 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
394 const int t = profile_tasks_[
i];
403 IntegerValue max_height = 0;
408 IntegerValue height_at_start = IntegerValue(0);
409 IntegerValue current_height = IntegerValue(0);
415 const IntegerValue relevant_height =
416 integer_trail_->UpperBound(capacity_) - initial_max_demand_;
417 const IntegerValue default_non_relevant_height =
418 has_demand_equal_to_capacity_ ? 1 : 0;
420 const auto& by_negated_start_max = helper_->TaskByIncreasingNegatedStartMax();
421 const auto& by_end_min = helper_->TaskByIncreasingEndMin();
425 int next_start = num_tasks_ - 1;
427 const int num_tasks = num_tasks_;
428 while (next_end < num_tasks) {
429 IntegerValue time = by_end_min[next_end].time;
430 if (next_start >= 0) {
431 time = std::min(time, -by_negated_start_max[next_start].time);
435 while (next_start >= 0 && -by_negated_start_max[next_start].time == time) {
436 const int t = by_negated_start_max[next_start].task_index;
437 current_height += demands_min[t];
442 while (next_end < num_tasks && by_end_min[next_end].time == time) {
443 const int t = by_end_min[next_end].task_index;
444 current_height -= demands_min[t];
448 if (current_height > max_height) {
449 max_height = current_height;
450 max_height_start = time;
453 IntegerValue effective_height = current_height;
454 if (effective_height > 0 && effective_height <= relevant_height) {
455 effective_height = default_non_relevant_height;
459 if (effective_height != height_at_start) {
460 profile_.emplace_back(current_start, height_at_start);
461 current_start = time;
462 height_at_start = effective_height;
467 DCHECK_GE(current_height, 0);
468 profile_.emplace_back(current_start, IntegerValue(0));
474 profile_max_height_ = max_height;
477 return IncreaseCapacity(max_height_start, profile_max_height_);
480void TimeTablingPerTask::ReverseProfile() {
482 std::reverse(profile_.begin() + 1, profile_.end() - 1);
483 for (
int i = 1;
i + 1 < profile_.size(); ++
i) {
484 profile_[
i].start = -profile_[
i].start;
485 profile_[
i].height = profile_[
i + 1].height;
489bool TimeTablingPerTask::SweepAllTasks() {
491 int profile_index = 1;
492 const IntegerValue capa_max = CapacityMax();
493 for (
const auto& [t, time] : helper_->TaskByIncreasingStartMin()) {
497 if (helper_->IsAbsent(t))
continue;
498 if (helper_->SizeMin(t) == 0)
continue;
502 const IntegerValue conflict_height = capa_max - demands_->DemandMin(t);
507 if (conflict_height >= profile_max_height_)
continue;
509 if (!SweepTask(t, time, conflict_height, &profile_index))
return false;
515bool TimeTablingPerTask::SweepTask(
int task_id, IntegerValue initial_start_min,
516 IntegerValue conflict_height,
517 int* profile_index) {
518 const IntegerValue start_max = helper_->StartMax(task_id);
519 const IntegerValue initial_end_min = helper_->EndMin(task_id);
523 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
524 while (profile_[*profile_index].start <= initial_start_min) {
527 int rec_id = *profile_index - 1;
542 IntegerValue new_start_min = initial_start_min;
543 if (IsInProfile(task_id)) {
544 DCHECK_LE(start_max, initial_end_min);
545 for (; profile_[rec_id].start < start_max; ++rec_id) {
547 if (profile_[rec_id].height <= conflict_height)
continue;
551 new_start_min = profile_[rec_id + 1].start;
552 if (start_max < new_start_min) {
554 new_start_min = start_max;
555 explanation_start_time = start_max - 1;
558 explanation_start_time = new_start_min - 1;
563 const IntegerValue delta =
564 demands_->DemandMax(task_id) - demands_->DemandMin(task_id);
566 const IntegerValue threshold = CapacityMax() - delta;
567 if (profile_[rec_id].start > start_max) --rec_id;
568 for (; profile_[rec_id].start < initial_end_min; ++rec_id) {
569 DCHECK_GT(profile_[rec_id + 1].start, start_max);
570 if (profile_[rec_id].height <= threshold)
continue;
571 const IntegerValue new_max = CapacityMax() - profile_[rec_id].height +
572 demands_->DemandMin(task_id);
576 helper_->ClearReason();
577 const IntegerValue time = std::max(start_max, profile_[rec_id].start);
578 AddProfileReason(task_id, time, time + 1, CapacityMax());
579 if (!helper_->PushIntegerLiteralIfTaskPresent(
580 task_id, demands_->Demands()[task_id].LowerOrEqual(new_max))) {
586 IntegerValue limit = initial_end_min;
587 const IntegerValue size_min = helper_->SizeMin(task_id);
588 for (; profile_[rec_id].start < limit; ++rec_id) {
590 if (profile_[rec_id].height <= conflict_height)
continue;
594 new_start_min = profile_[rec_id + 1].start;
595 limit = std::max(limit, new_start_min + size_min);
596 if (profile_[rec_id].start < initial_end_min) {
597 explanation_start_time = std::min(new_start_min, initial_end_min) - 1;
600 if (new_start_min > start_max) {
602 new_start_min = std::max(profile_[rec_id].start + 1, start_max + 1);
603 explanation_start_time =
604 std::min(explanation_start_time, new_start_min - 1);
610 if (new_start_min == initial_start_min)
return true;
611 return UpdateStartingTime(task_id, explanation_start_time, new_start_min);
614bool TimeTablingPerTask::UpdateStartingTime(
int task_id, IntegerValue left,
615 IntegerValue right) {
616 DCHECK_LT(left, right);
617 helper_->ClearReason();
618 AddProfileReason(task_id, left, right,
619 CapacityMax() - demands_->DemandMin(task_id));
621 helper_->MutableIntegerReason()->push_back(
622 integer_trail_->UpperBoundAsLiteral(capacity_.var));
626 helper_->AddEndMinReason(task_id, left + 1);
627 helper_->AddSizeMinReason(task_id);
628 demands_->AddDemandMinReason(task_id);
631 return helper_->IncreaseStartMin(task_id, right);
637void TimeTablingPerTask::AddProfileReason(
int task_id, IntegerValue left,
639 IntegerValue capacity_threshold) {
640 IntegerValue sum_of_demand(0);
644 DCHECK_GT(right, left);
645 DCHECK(task_id >= 0 || left + 1 == right);
646 if (left + 1 == right) {
650 }
else if (right - left < helper_->SizeMin(task_id) + 2) {
659 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
660 const int t = profile_tasks_[
i];
663 const IntegerValue start_max = helper_->StartMax(t);
664 if (right <= start_max)
continue;
665 const IntegerValue end_min = helper_->EndMin(t);
666 if (end_min <= left)
continue;
668 helper_->AddPresenceReason(t);
673 if (t != task_id) demands_->AddDemandMinReason(t);
676 helper_->AddStartMaxReason(t, left);
677 helper_->AddEndMinReason(t, right);
684 sum_of_demand += demands_->DemandMin(t);
685 if (sum_of_demand > capacity_threshold)
break;
686 }
else if (mode == 1) {
687 helper_->AddStartMaxReason(t, start_max <= left ? left : right - 1);
688 helper_->AddEndMinReason(t, end_min >= right ? right : left + 1);
690 helper_->AddStartMaxReason(t, std::max(left, start_max));
691 helper_->AddEndMinReason(t, std::min(right, end_min));
696bool TimeTablingPerTask::IncreaseCapacity(IntegerValue time,
697 IntegerValue new_min) {
698 if (new_min <= CapacityMin())
return true;
702 new_min = std::min(CapacityMax() + 1, new_min);
704 helper_->ClearReason();
705 AddProfileReason(-1, time, time + 1, new_min);
707 return helper_->ReportConflict();
709 return helper_->PushIntegerLiteral(capacity_.GreaterOrEqual(new_min));
void RegisterReversibleInt(int id, int *rev)
void NotifyThatPropagatorMayNotReachFixedPointInOnePass(int id)
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue StartMax(int t) const
bool IsPresent(int t) const
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
IntegerValue EndMin(int t) const
IntegerValue DemandMin(int t) const
bool LiteralIsFalse(Literal literal) const
bool LiteralIsTrue(Literal literal) const
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< AffineExpression > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.