20#include "absl/log/check.h"
21#include "absl/types/span.h"
32 std::vector<AffineExpression> deltas,
33 std::vector<Literal> presences, int64_t min_level,
36 IntegerValue min_possible(0);
37 IntegerValue max_possible(0);
40 min_possible += std::min(IntegerValue(0), integer_trail->LowerBound(d));
41 max_possible += std::max(IntegerValue(0), integer_trail->UpperBound(d));
43 if (max_possible > max_level) {
45 times, deltas, presences, IntegerValue(max_level),
model));
47 if (min_possible < min_level) {
50 times, deltas, presences, IntegerValue(-min_level),
model));
55 const std::vector<AffineExpression>& times,
56 const std::vector<AffineExpression>& deltas,
57 const std::vector<Literal>& presences, IntegerValue capacity,
Model*
model)
60 presences_(presences),
65 const int id = watcher->
Register(
this);
66 const int num_events = times.size();
67 for (
int e = 0; e < num_events; e++) {
68 watcher->WatchLowerBound(deltas_[e],
id);
69 if (integer_trail_->
UpperBound(deltas_[e]) > 0) {
70 watcher->WatchUpperBound(times_[e].
var,
id);
71 watcher->WatchLiteral(presences_[e],
id);
73 if (integer_trail_->
LowerBound(deltas_[e]) < 0) {
74 watcher->WatchLowerBound(times_[e].
var,
id);
75 watcher->WatchLiteral(presences_[e].Negated(),
id);
78 watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(
id);
82 const int num_events = times_.size();
83 if (!BuildProfile())
return false;
84 for (
int e = 0; e < num_events; e++) {
88 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[e]);
89 if (min_d > 0 && !TryToIncreaseMin(e))
return false;
92 if (min_d < 0 && !TryToDecreaseMax(e))
return false;
101bool ReservoirTimeTabling::BuildProfile() {
104 const int num_events = times_.size();
106 for (
int e = 0; e < num_events; e++) {
107 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[e]);
111 const IntegerValue ub = integer_trail_->
UpperBound(times_[e]);
112 profile_.push_back({ub, min_d});
113 }
else if (min_d < 0) {
116 profile_.push_back({integer_trail_->
LowerBound(times_[e]), min_d});
120 std::sort(profile_.begin(), profile_.end());
124 for (
const ProfileRectangle& rect : profile_) {
125 if (rect.start == profile_[last].start) {
126 profile_[last].height += rect.height;
129 profile_[last].start = rect.start;
130 profile_[last].height = rect.height + profile_[last - 1].height;
133 profile_.resize(last + 1);
136 for (
const ProfileRectangle& rect : profile_) {
137 if (rect.height <= capacity_)
continue;
139 FillReasonForProfileAtGivenTime(rect.start);
140 return integer_trail_->
ReportConflict(literal_reason_, integer_reason_);
148void AddLowerOrEqual(
const AffineExpression& expr, IntegerValue
bound,
149 std::vector<IntegerLiteral>* reason) {
150 if (expr.IsConstant())
return;
151 reason->push_back(expr.LowerOrEqual(
bound));
154void AddGreaterOrEqual(
const AffineExpression& expr, IntegerValue
bound,
155 std::vector<IntegerLiteral>* reason) {
156 if (expr.IsConstant())
return;
157 reason->push_back(expr.GreaterOrEqual(
bound));
168void ReservoirTimeTabling::FillReasonForProfileAtGivenTime(
169 IntegerValue t,
int event_to_ignore) {
170 integer_reason_.clear();
171 literal_reason_.clear();
172 const int num_events = times_.size();
173 for (
int e = 0; e < num_events; e++) {
174 if (e == event_to_ignore)
continue;
175 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[e]);
178 if (integer_trail_->
UpperBound(times_[e]) > t)
continue;
179 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
180 AddLowerOrEqual(times_[e], t, &integer_reason_);
181 literal_reason_.push_back(presences_[e].Negated());
182 }
else if (min_d <= 0) {
184 literal_reason_.push_back(presences_[e]);
187 AddGreaterOrEqual(deltas_[e], min_d, &integer_reason_);
188 if (min_d < 0 && integer_trail_->
LowerBound(times_[e]) > t) {
189 AddGreaterOrEqual(times_[e], t + 1, &integer_reason_);
197bool ReservoirTimeTabling::TryToDecreaseMax(
int event) {
198 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[event]);
201 const IntegerValue
end = integer_trail_->
UpperBound(times_[event]);
208 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
210 std::upper_bound(profile_.begin(), profile_.end(),
start,
211 [&](IntegerValue
value,
const ProfileRectangle& rect) {
212 return value < rect.start;
218 IntegerValue new_end =
end;
219 for (; profile_[rec_id].start <
end; ++rec_id) {
220 if (profile_[rec_id].
height - min_d > capacity_) {
221 new_end = profile_[rec_id].start;
226 if (!push)
return true;
230 FillReasonForProfileAtGivenTime(new_end, event);
235 if (new_end <
start) {
236 AddGreaterOrEqual(times_[event], new_end + 1, &integer_reason_);
237 return integer_trail_->
ReportConflict(literal_reason_, integer_reason_);
245 integer_trail_->
EnqueueLiteral(presences_[event], literal_reason_,
251 literal_reason_, integer_reason_);
254bool ReservoirTimeTabling::TryToIncreaseMin(
int event) {
255 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[event]);
258 const IntegerValue
end = integer_trail_->
UpperBound(times_[event]);
268 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
270 std::upper_bound(profile_.begin(), profile_.end(),
end,
271 [&](IntegerValue
value,
const ProfileRectangle& rect) {
272 return value < rect.start;
278 IntegerValue new_start =
start;
279 if (profile_[rec_id].
height + min_d > capacity_) {
284 }
else if (profile_[rec_id].
start <
end) {
291 for (; profile_[rec_id].start >
start; --rec_id) {
292 if (profile_[rec_id - 1].
height + min_d > capacity_) {
294 new_start = profile_[rec_id].start;
299 if (!push)
return true;
302 FillReasonForProfileAtGivenTime(new_start - 1, event);
303 AddGreaterOrEqual(deltas_[event], min_d, &integer_reason_);
306 &literal_reason_, &integer_reason_);
313 : num_tasks_(helper->NumTasks()),
321 profile_.reserve(2 * num_tasks_ + 4);
323 num_profile_tasks_ = 0;
324 profile_tasks_.resize(num_tasks_);
326 initial_max_demand_ = IntegerValue(0);
327 const bool capa_is_fixed = integer_trail_->
IsFixed(capacity_);
328 const IntegerValue capa_min = integer_trail_->
LowerBound(capacity_);
329 for (
int t = 0; t < num_tasks_; ++t) {
330 profile_tasks_[t] = t;
332 if (capa_is_fixed && demands_->
DemandMin(t) >= capa_min) {
336 has_demand_equal_to_capacity_ =
true;
339 initial_max_demand_ = std::max(initial_max_demand_, demands_->
DemandMax(t));
344 const int id = watcher->
Register(
this);
347 for (
int t = 0; t < num_tasks_; t++) {
360 if (!BuildProfile())
return false;
363 if (!SweepAllTasks())
return false;
370 if (!SweepAllTasks())
return false;
375bool TimeTablingPerTask::BuildProfile() {
381 for (
int i = num_profile_tasks_;
i < num_tasks_; ++
i) {
382 const int t = profile_tasks_[
i];
384 std::swap(profile_tasks_[
i], profile_tasks_[num_profile_tasks_]);
385 num_profile_tasks_++;
390 cached_demands_min_.assign(num_tasks_, 0);
391 absl::Span<IntegerValue> demands_min = absl::MakeSpan(cached_demands_min_);
392 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
393 const int t = profile_tasks_[
i];
402 IntegerValue max_height = 0;
407 IntegerValue height_at_start = IntegerValue(0);
408 IntegerValue current_height = IntegerValue(0);
414 const IntegerValue relevant_height =
415 integer_trail_->
UpperBound(capacity_) - initial_max_demand_;
416 const IntegerValue default_non_relevant_height =
417 has_demand_equal_to_capacity_ ? 1 : 0;
424 int next_start = num_tasks_ - 1;
426 const int num_tasks = num_tasks_;
427 while (next_end < num_tasks) {
428 IntegerValue
time = by_end_min[next_end].time;
429 if (next_start >= 0) {
430 time = std::min(
time, -by_negated_start_max[next_start].
time);
434 while (next_start >= 0 && -by_negated_start_max[next_start].
time ==
time) {
435 const int t = by_negated_start_max[next_start].task_index;
436 current_height += demands_min[t];
441 while (next_end < num_tasks && by_end_min[next_end].
time ==
time) {
442 const int t = by_end_min[next_end].task_index;
443 current_height -= demands_min[t];
447 if (current_height > max_height) {
448 max_height = current_height;
449 max_height_start =
time;
452 IntegerValue effective_height = current_height;
453 if (effective_height > 0 && effective_height <= relevant_height) {
454 effective_height = default_non_relevant_height;
458 if (effective_height != height_at_start) {
459 profile_.emplace_back(current_start, height_at_start);
460 current_start =
time;
461 height_at_start = effective_height;
466 DCHECK_GE(current_height, 0);
467 profile_.emplace_back(current_start, IntegerValue(0));
473 profile_max_height_ = max_height;
476 return IncreaseCapacity(max_height_start, profile_max_height_);
479void TimeTablingPerTask::ReverseProfile() {
481 std::reverse(profile_.begin() + 1, profile_.end() - 1);
482 for (
int i = 1;
i + 1 < profile_.size(); ++
i) {
483 profile_[
i].start = -profile_[
i].start;
484 profile_[
i].height = profile_[
i + 1].height;
488bool TimeTablingPerTask::SweepAllTasks() {
490 int profile_index = 1;
491 const IntegerValue capa_max = CapacityMax();
497 if (helper_->
SizeMin(t) == 0)
continue;
501 const IntegerValue conflict_height = capa_max - demands_->
DemandMin(t);
506 if (conflict_height >= profile_max_height_)
continue;
508 if (!SweepTask(t,
time, conflict_height, &profile_index))
return false;
514bool TimeTablingPerTask::SweepTask(
int task_id, IntegerValue initial_start_min,
515 IntegerValue conflict_height,
516 int* profile_index) {
518 const IntegerValue initial_end_min = helper_->
EndMin(task_id);
522 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
523 while (profile_[*profile_index].
start <= initial_start_min) {
526 int rec_id = *profile_index - 1;
541 IntegerValue new_start_min = initial_start_min;
542 if (IsInProfile(task_id)) {
544 for (; profile_[rec_id].start <
start_max; ++rec_id) {
546 if (profile_[rec_id].
height <= conflict_height)
continue;
550 new_start_min = profile_[rec_id + 1].start;
557 explanation_start_time = new_start_min - 1;
562 const IntegerValue
delta =
565 const IntegerValue threshold = CapacityMax() -
delta;
567 for (; profile_[rec_id].start < initial_end_min; ++rec_id) {
569 if (profile_[rec_id].
height <= threshold)
continue;
570 const IntegerValue new_max = CapacityMax() - profile_[rec_id].height +
577 AddProfileReason(task_id,
time,
time + 1, CapacityMax());
579 task_id, demands_->
Demands()[task_id].LowerOrEqual(new_max))) {
585 IntegerValue limit = initial_end_min;
586 const IntegerValue size_min = helper_->
SizeMin(task_id);
587 for (; profile_[rec_id].start < limit; ++rec_id) {
589 if (profile_[rec_id].
height <= conflict_height)
continue;
593 new_start_min = profile_[rec_id + 1].start;
594 limit = std::max(limit, new_start_min + size_min);
595 if (profile_[rec_id].
start < initial_end_min) {
596 explanation_start_time = std::min(new_start_min, initial_end_min) - 1;
601 new_start_min = std::max(profile_[rec_id].
start + 1,
start_max + 1);
602 explanation_start_time =
603 std::min(explanation_start_time, new_start_min - 1);
609 if (new_start_min == initial_start_min)
return true;
610 return UpdateStartingTime(task_id, explanation_start_time, new_start_min);
613bool TimeTablingPerTask::UpdateStartingTime(
int task_id, IntegerValue left,
614 IntegerValue right) {
615 DCHECK_LT(left, right);
617 AddProfileReason(task_id, left, right,
618 CapacityMax() - demands_->
DemandMin(task_id));
636void TimeTablingPerTask::AddProfileReason(
int task_id, IntegerValue left,
638 IntegerValue capacity_threshold) {
639 IntegerValue sum_of_demand(0);
643 DCHECK_GT(right, left);
644 DCHECK(task_id >= 0 || left + 1 == right);
645 if (left + 1 == right) {
649 }
else if (right - left < helper_->SizeMin(task_id) + 2) {
658 for (
int i = 0;
i < num_profile_tasks_; ++
i) {
659 const int t = profile_tasks_[
i];
684 if (sum_of_demand > capacity_threshold)
break;
685 }
else if (mode == 1) {
695bool TimeTablingPerTask::IncreaseCapacity(IntegerValue
time,
696 IntegerValue new_min) {
697 if (new_min <= CapacityMin())
return true;
701 new_min = std::min(CapacityMax() + 1, new_min);
704 AddProfileReason(-1,
time,
time + 1, 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.
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
IntegerValue UpperBound(IntegerVariable i) const
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
void AddPresenceReason(int t)
absl::Span< const TaskTime > TaskByIncreasingStartMin()
void WatchAllTasks(int id, bool watch_max_side=true)
void AddStartMaxReason(int t, IntegerValue upper_bound)
std::vector< IntegerLiteral > * MutableIntegerReason()
absl::Span< const TaskTime > TaskByIncreasingNegatedStartMax()
IntegerValue StartMax(int t) const
ABSL_MUST_USE_RESULT bool ReportConflict()
bool IsPresent(int t) const
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
void AddSizeMinReason(int t)
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
void AddEndMinReason(int t, IntegerValue lower_bound)
void ClearReason()
Functions to clear and then set the current reason.
IntegerValue SizeMin(int t) const
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
bool IsAbsent(int t) const
IntegerValue EndMin(int t) const
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
absl::Span< const TaskTime > TaskByIncreasingEndMin()
void AddDemandMinReason(int t)
IntegerValue DemandMax(int t) const
const std::vector< AffineExpression > & Demands() 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.
std::optional< int64_t > end
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.