20#include "absl/log/check.h"
21#include "absl/types/span.h"
34 absl::Span<const AffineExpression> times,
35 absl::Span<const AffineExpression> deltas,
36 absl::Span<const Literal> presences,
37 int64_t min_level, int64_t max_level,
40 IntegerValue min_possible(0);
41 IntegerValue max_possible(0);
44 min_possible += std::min(IntegerValue(0), integer_trail->LowerBound(d));
45 max_possible += std::max(IntegerValue(0), integer_trail->UpperBound(d));
47 if (max_possible > max_level) {
50 IntegerValue(max_level), model));
52 if (min_possible < min_level) {
53 std::vector<AffineExpression> negated_deltas;
55 negated_deltas.push_back(ref.Negated());
59 presences, IntegerValue(-min_level), model));
64 absl::Span<const Literal> enforcement_literals,
65 absl::Span<const AffineExpression> times,
66 absl::Span<const AffineExpression> deltas,
67 absl::Span<const Literal> presences, IntegerValue capacity,
Model* model)
68 : enforcement_literals_(enforcement_literals.
begin(),
69 enforcement_literals.
end()),
71 deltas_(deltas.
begin(), deltas.
end()),
72 presences_(presences.
begin(), presences.
end()),
78 const int id = watcher->
Register(
this);
79 const int num_events = times.size();
80 for (
int e = 0; e < num_events; e++) {
81 watcher->WatchLowerBound(deltas_[e],
id);
82 if (integer_trail_.UpperBound(deltas_[e]) > 0) {
83 watcher->WatchUpperBound(times_[e].var,
id);
84 watcher->WatchLiteral(presences_[e],
id);
86 if (integer_trail_.LowerBound(deltas_[e]) < 0) {
87 watcher->WatchLowerBound(times_[e].var,
id);
88 watcher->WatchLiteral(presences_[e].Negated(),
id);
91 watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(
id);
93 enforcement_helper_.Register(enforcement_literals, watcher,
id);
103 const int num_events = times_.size();
104 if (!BuildProfile())
return false;
106 for (
int e = 0; e < num_events; e++) {
107 if (assignment_.LiteralIsFalse(presences_[e]))
continue;
110 const IntegerValue min_d = integer_trail_.LowerBound(deltas_[e]);
111 if (min_d > 0 && !TryToIncreaseMin(e))
return false;
114 if (min_d < 0 && !TryToDecreaseMax(e))
return false;
123bool ReservoirTimeTabling::BuildProfile() {
126 const int num_events = times_.size();
128 for (
int e = 0; e < num_events; e++) {
129 const IntegerValue min_d = integer_trail_.
LowerBound(deltas_[e]);
133 const IntegerValue ub = integer_trail_.
UpperBound(times_[e]);
134 profile_.push_back({ub, min_d});
135 }
else if (min_d < 0) {
138 profile_.push_back({integer_trail_.
LowerBound(times_[e]), min_d});
142 std::sort(profile_.begin(), profile_.end());
146 for (
const ProfileRectangle& rect : profile_) {
147 if (rect.start == profile_[last].start) {
148 profile_[last].height += rect.height;
151 profile_[last].start = rect.start;
152 profile_[last].height = rect.height + profile_[last - 1].height;
155 profile_.resize(last + 1);
158 const bool is_enforced = enforcement_helper_.Status(enforcement_id_) ==
160 for (
const ProfileRectangle& rect : profile_) {
161 if (rect.height <= capacity_)
continue;
163 FillReasonForProfileAtGivenTime(rect.start);
165 return enforcement_helper_.ReportConflict(
166 enforcement_id_, literal_reason_, integer_reason_);
168 return enforcement_helper_.PropagateWhenFalse(
169 enforcement_id_, literal_reason_, integer_reason_);
179 std::vector<IntegerLiteral>* reason) {
180 if (expr.IsConstant())
return;
181 reason->push_back(expr.LowerOrEqual(bound));
185 std::vector<IntegerLiteral>* reason) {
186 if (expr.IsConstant())
return;
187 reason->push_back(expr.GreaterOrEqual(bound));
198void ReservoirTimeTabling::FillReasonForProfileAtGivenTime(
199 IntegerValue t,
int event_to_ignore) {
200 integer_reason_.clear();
201 literal_reason_.clear();
202 const int num_events = times_.size();
203 for (
int e = 0; e < num_events; e++) {
204 if (e == event_to_ignore)
continue;
205 const IntegerValue min_d = integer_trail_.LowerBound(deltas_[e]);
207 if (!assignment_.LiteralIsTrue(presences_[e]))
continue;
208 if (integer_trail_.UpperBound(times_[e]) > t)
continue;
209 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
210 AddLowerOrEqual(times_[e], t, &integer_reason_);
211 literal_reason_.push_back(presences_[e].Negated());
212 }
else if (min_d <= 0) {
213 if (assignment_.LiteralIsFalse(presences_[e])) {
214 literal_reason_.push_back(presences_[e]);
217 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
218 if (min_d < 0 && integer_trail_.LowerBound(times_[e]) > t) {
219 AddGreaterOrEqual(times_[e], t + 1, &integer_reason_);
227bool ReservoirTimeTabling::TryToDecreaseMax(
int event) {
228 const IntegerValue min_d = integer_trail_.LowerBound(deltas_[event]);
230 const IntegerValue start = integer_trail_.LowerBound(times_[event]);
231 const IntegerValue
end = integer_trail_.UpperBound(times_[event]);
234 if (start ==
end)
return true;
238 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
240 std::upper_bound(profile_.begin(), profile_.end(), start,
241 [&](IntegerValue value,
const ProfileRectangle& rect) {
242 return value < rect.start;
248 IntegerValue new_end =
end;
249 for (; profile_[rec_id].start <
end; ++rec_id) {
250 if (profile_[rec_id].height - min_d > capacity_) {
251 new_end = profile_[rec_id].start;
256 if (!push)
return true;
260 FillReasonForProfileAtGivenTime(new_end, event);
265 if (new_end < start) {
266 AddGreaterOrEqual(times_[event], new_end + 1, &integer_reason_);
267 return enforcement_helper_.ReportConflict(enforcement_id_, literal_reason_,
275 if (!assignment_.LiteralIsTrue(presences_[event])) {
276 if (!enforcement_helper_.EnqueueLiteral(enforcement_id_, presences_[event],
277 literal_reason_, integer_reason_)) {
283 return enforcement_helper_.Enqueue(enforcement_id_,
285 literal_reason_, integer_reason_);
288bool ReservoirTimeTabling::TryToIncreaseMin(
int event) {
289 const IntegerValue min_d = integer_trail_.LowerBound(deltas_[event]);
291 const IntegerValue start = integer_trail_.LowerBound(times_[event]);
292 const IntegerValue
end = integer_trail_.UpperBound(times_[event]);
295 if (start ==
end)
return true;
302 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
304 std::upper_bound(profile_.begin(), profile_.end(),
end,
305 [&](IntegerValue value,
const ProfileRectangle& rect) {
306 return value < rect.start;
312 IntegerValue new_start = start;
313 if (profile_[rec_id].height + min_d > capacity_) {
314 if (!assignment_.LiteralIsTrue(presences_[event])) {
318 }
else if (profile_[rec_id].start <
end) {
325 for (; profile_[rec_id].start > start; --rec_id) {
326 if (profile_[rec_id - 1].height + min_d > capacity_) {
328 new_start = profile_[rec_id].start;
333 if (!push)
return true;
336 FillReasonForProfileAtGivenTime(new_start - 1, event);
337 AddGreaterOrEqual(deltas_[event], min_d, &integer_reason_);
338 return enforcement_helper_.ConditionalEnqueue(
339 enforcement_id_, presences_[event],
348 : num_tasks_(helper->NumTasks()),
356 profile_.reserve(2 * num_tasks_ + 4);
358 num_profile_tasks_ = 0;
359 profile_tasks_.resize(num_tasks_);
361 initial_max_demand_ = IntegerValue(0);
362 const bool capa_is_fixed = integer_trail_->IsFixed(capacity_);
363 const IntegerValue capa_min = integer_trail_->LowerBound(capacity_);
364 for (
int t = 0; t < num_tasks_; ++t) {
365 profile_tasks_[t] = t;
367 if (capa_is_fixed && demands_->DemandMin(t) >= capa_min) {
371 has_demand_equal_to_capacity_ =
true;
374 initial_max_demand_ = std::max(initial_max_demand_, demands_->DemandMax(t));
379 const int id = watcher->
Register(
this);
380 helper_->WatchAllTasks(
id);
382 for (
int t = 0; t < num_tasks_; t++) {
394 if (!helper_->IsEnforced())
return true;
396 if (!BuildProfile())
return false;
399 if (!SweepAllTasks())
return false;
402 if (!helper_->SynchronizeAndSetTimeDirection(
false))
return false;
406 if (!SweepAllTasks())
return false;
411bool TimeTablingPerTask::BuildProfile() {
417 for (
int i = num_profile_tasks_;
i < num_tasks_; ++
i) {
418 const int t = profile_tasks_[
i];
420 std::swap(profile_tasks_[
i], profile_tasks_[num_profile_tasks_]);
421 num_profile_tasks_++;
426 cached_demands_min_.assign(num_tasks_, 0);
427 absl::Span<IntegerValue> demands_min = absl::MakeSpan(cached_demands_min_);
428 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
429 const int t = profile_tasks_[
i];
438 IntegerValue max_height = 0;
443 IntegerValue height_at_start = IntegerValue(0);
444 IntegerValue current_height = IntegerValue(0);
450 const IntegerValue relevant_height =
451 integer_trail_->UpperBound(capacity_) - initial_max_demand_;
452 const IntegerValue default_non_relevant_height =
453 has_demand_equal_to_capacity_ ? 1 : 0;
455 const auto& by_negated_start_max = helper_->TaskByIncreasingNegatedStartMax();
456 const auto& by_end_min = helper_->TaskByIncreasingEndMin();
460 int next_start = num_tasks_ - 1;
462 const int num_tasks = num_tasks_;
463 while (next_end < num_tasks) {
464 IntegerValue time = by_end_min[next_end].time;
465 if (next_start >= 0) {
466 time = std::min(time, -by_negated_start_max[next_start].time);
470 while (next_start >= 0 && -by_negated_start_max[next_start].time == time) {
471 const int t = by_negated_start_max[next_start].task_index;
472 current_height += demands_min[t];
477 while (next_end < num_tasks && by_end_min[next_end].time == time) {
478 const int t = by_end_min[next_end].task_index;
479 current_height -= demands_min[t];
483 if (current_height > max_height) {
484 max_height = current_height;
485 max_height_start = time;
488 IntegerValue effective_height = current_height;
489 if (effective_height > 0 && effective_height <= relevant_height) {
490 effective_height = default_non_relevant_height;
494 if (effective_height != height_at_start) {
495 profile_.emplace_back(current_start, height_at_start);
496 current_start = time;
497 height_at_start = effective_height;
502 DCHECK_GE(current_height, 0);
503 profile_.emplace_back(current_start, IntegerValue(0));
509 profile_max_height_ = max_height;
512 return IncreaseCapacity(max_height_start, profile_max_height_);
515void TimeTablingPerTask::ReverseProfile() {
517 std::reverse(profile_.begin() + 1, profile_.end() - 1);
518 for (
int i = 1;
i + 1 < profile_.size(); ++
i) {
519 profile_[
i].start = -profile_[
i].start;
520 profile_[
i].height = profile_[
i + 1].height;
524bool TimeTablingPerTask::SweepAllTasks() {
526 int profile_index = 1;
527 const IntegerValue capa_max = CapacityMax();
528 for (
const auto& [t, time] : helper_->TaskByIncreasingStartMin()) {
532 if (helper_->IsAbsent(t))
continue;
533 if (helper_->SizeMin(t) == 0)
continue;
537 const IntegerValue conflict_height = capa_max - demands_->DemandMin(t);
542 if (conflict_height >= profile_max_height_)
continue;
544 if (!SweepTask(t, time, conflict_height, &profile_index))
return false;
550bool TimeTablingPerTask::SweepTask(
int task_id, IntegerValue initial_start_min,
551 IntegerValue conflict_height,
552 int* profile_index) {
553 const IntegerValue start_max = helper_->StartMax(task_id);
554 const IntegerValue initial_end_min = helper_->EndMin(task_id);
558 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
559 while (profile_[*profile_index].start <= initial_start_min) {
562 int rec_id = *profile_index - 1;
577 IntegerValue new_start_min = initial_start_min;
578 if (IsInProfile(task_id)) {
579 DCHECK_LE(start_max, initial_end_min);
580 for (; profile_[rec_id].start < start_max; ++rec_id) {
582 if (profile_[rec_id].height <= conflict_height)
continue;
586 new_start_min = profile_[rec_id + 1].start;
587 if (start_max < new_start_min) {
589 new_start_min = start_max;
590 explanation_start_time = start_max - 1;
593 explanation_start_time = new_start_min - 1;
598 const IntegerValue delta =
599 demands_->DemandMax(task_id) - demands_->DemandMin(task_id);
601 const IntegerValue threshold = CapacityMax() - delta;
602 if (profile_[rec_id].start > start_max) --rec_id;
603 for (; profile_[rec_id].start < initial_end_min; ++rec_id) {
604 DCHECK_GT(profile_[rec_id + 1].start, start_max);
605 if (profile_[rec_id].height <= threshold)
continue;
606 const IntegerValue new_max = CapacityMax() - profile_[rec_id].height +
607 demands_->DemandMin(task_id);
611 helper_->ResetReason();
612 const IntegerValue time = std::max(start_max, profile_[rec_id].start);
613 AddProfileReason(task_id, time, time + 1, CapacityMax());
614 if (!helper_->PushIntegerLiteralIfTaskPresent(
615 task_id, demands_->Demands()[task_id].LowerOrEqual(new_max))) {
621 IntegerValue limit = initial_end_min;
622 const IntegerValue size_min = helper_->SizeMin(task_id);
623 for (; profile_[rec_id].start < limit; ++rec_id) {
625 if (profile_[rec_id].height <= conflict_height)
continue;
629 new_start_min = profile_[rec_id + 1].start;
630 limit = std::max(limit, new_start_min + size_min);
631 if (profile_[rec_id].start < initial_end_min) {
632 explanation_start_time = std::min(new_start_min, initial_end_min) - 1;
635 if (new_start_min > start_max) {
637 new_start_min = std::max(profile_[rec_id].start + 1, start_max + 1);
638 explanation_start_time =
639 std::min(explanation_start_time, new_start_min - 1);
645 if (new_start_min == initial_start_min)
return true;
646 return UpdateStartingTime(task_id, explanation_start_time, new_start_min);
649bool TimeTablingPerTask::UpdateStartingTime(
int task_id, IntegerValue left,
650 IntegerValue right) {
651 DCHECK_LT(left, right);
652 helper_->ResetReason();
653 AddProfileReason(task_id, left, right,
654 CapacityMax() - demands_->DemandMin(task_id));
656 helper_->AddIntegerReason(
657 integer_trail_->UpperBoundAsLiteral(capacity_.var));
661 helper_->AddEndMinReason(task_id, left + 1);
662 helper_->AddSizeMinReason(task_id);
663 demands_->AddDemandMinReason(task_id);
666 return helper_->IncreaseStartMin(task_id, right);
672void TimeTablingPerTask::AddProfileReason(
int task_id, IntegerValue left,
674 IntegerValue capacity_threshold) {
675 IntegerValue sum_of_demand(0);
679 DCHECK_GT(right, left);
680 DCHECK(task_id >= 0 || left + 1 == right);
681 if (left + 1 == right) {
685 }
else if (right - left < helper_->SizeMin(task_id) + 2) {
694 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
695 const int t = profile_tasks_[
i];
698 const IntegerValue start_max = helper_->StartMax(t);
699 if (right <= start_max)
continue;
700 const IntegerValue end_min = helper_->EndMin(t);
701 if (end_min <= left)
continue;
703 helper_->AddPresenceReason(t);
708 if (t != task_id) demands_->AddDemandMinReason(t);
711 helper_->AddStartMaxReason(t, left);
712 helper_->AddEndMinReason(t, right);
719 sum_of_demand += demands_->DemandMin(t);
720 if (sum_of_demand > capacity_threshold)
break;
721 }
else if (mode == 1) {
722 helper_->AddStartMaxReason(t, start_max <= left ? left : right - 1);
723 helper_->AddEndMinReason(t, end_min >= right ? right : left + 1);
725 helper_->AddStartMaxReason(t, std::max(left, start_max));
726 helper_->AddEndMinReason(t, std::min(right, end_min));
731bool TimeTablingPerTask::IncreaseCapacity(IntegerValue time,
732 IntegerValue new_min) {
733 if (new_min <= CapacityMin())
return true;
737 new_min = std::min(CapacityMax() + 1, new_min);
739 helper_->ResetReason();
740 AddProfileReason(-1, time, time + 1, new_min);
742 return helper_->ReportConflict();
744 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)
IntegerValue LowerBound(IntegerVariable i) const
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< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddReservoirConstraint(absl::Span< const Literal > enforcement_literals, absl::Span< const AffineExpression > times, absl::Span< const AffineExpression > deltas, absl::Span< const Literal > presences, int64_t min_level, int64_t max_level, Model *model)
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
ClosedInterval::Iterator end(ClosedInterval interval)
ClosedInterval::Iterator begin(ClosedInterval interval)