33 std::vector<AffineExpression> deltas,
34 std::vector<Literal> presences, int64_t min_level,
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,
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++) {
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;
102 bool 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_);
149 void AddLowerOrEqual(
const AffineExpression& expr, IntegerValue
bound,
150 std::vector<IntegerLiteral>* reason) {
151 if (expr.IsConstant())
return;
152 reason->push_back(expr.LowerOrEqual(
bound));
155 void AddGreaterOrEqual(
const AffineExpression& expr, IntegerValue
bound,
156 std::vector<IntegerLiteral>* reason) {
157 if (expr.IsConstant())
return;
158 reason->push_back(expr.GreaterOrEqual(
bound));
169 void 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]);
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) {
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_);
198 bool ReservoirTimeTabling::TryToDecreaseMax(
int event) {
199 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[event]);
202 const IntegerValue
end = integer_trail_->
UpperBound(times_[event]);
209 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
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_);
246 integer_trail_->
EnqueueLiteral(presences_[event], literal_reason_,
252 literal_reason_, integer_reason_);
255 bool ReservoirTimeTabling::TryToIncreaseMin(
int event) {
256 const IntegerValue min_d = integer_trail_->
LowerBound(deltas_[event]);
259 const IntegerValue
end = integer_trail_->
UpperBound(times_[event]);
269 DCHECK(std::is_sorted(profile_.begin(), profile_.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_) {
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_);
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_);
326 positions_in_profile_tasks_.resize(num_tasks_);
328 initial_max_demand_ = IntegerValue(0);
329 const bool capa_is_fixed = integer_trail_->
IsFixed(capacity_);
330 const IntegerValue capa_min = integer_trail_->
LowerBound(capacity_);
331 for (
int t = 0; t < num_tasks_; ++t) {
332 profile_tasks_[t] = t;
333 positions_in_profile_tasks_[t] = t;
335 if (capa_is_fixed && demands_->
DemandMin(t) >= capa_min) {
339 has_demand_equal_to_capacity_ =
true;
347 const int id = watcher->
Register(
this);
350 for (
int t = 0; t < num_tasks_; t++) {
363 if (!BuildProfile())
return false;
366 if (!SweepAllTasks())
return false;
373 if (!SweepAllTasks())
return false;
378 bool TimeTablingPerTask::BuildProfile() {
384 for (
int i = num_profile_tasks_; i < num_tasks_; ++i) {
385 const int t1 = profile_tasks_[i];
388 const int t2 = profile_tasks_[num_profile_tasks_];
389 profile_tasks_[i] = t2;
390 profile_tasks_[num_profile_tasks_] = t1;
391 positions_in_profile_tasks_[t1] = num_profile_tasks_;
392 positions_in_profile_tasks_[t2] = i;
393 num_profile_tasks_++;
405 profile_max_height_ = 0;
410 IntegerValue height_at_start = IntegerValue(0);
411 IntegerValue current_height = IntegerValue(0);
417 const IntegerValue relevant_height =
418 integer_trail_->
UpperBound(capacity_) - initial_max_demand_;
422 int next_start = num_tasks_ - 1;
424 while (next_end < num_tasks_) {
425 IntegerValue
time = by_end_min[next_end].time;
426 if (next_start >= 0) {
431 while (next_start >= 0 &&
432 by_decreasing_start_max[next_start].
time ==
time) {
433 const int t = by_decreasing_start_max[next_start].task_index;
434 if (IsInProfile(t)) current_height += demands_->
DemandMin(t);
439 while (next_end < num_tasks_ && by_end_min[next_end].
time ==
time) {
440 const int t = by_end_min[next_end].task_index;
441 if (IsInProfile(t)) current_height -= demands_->
DemandMin(t);
445 if (current_height > profile_max_height_) {
446 profile_max_height_ = current_height;
447 max_height_start =
time;
450 IntegerValue effective_height = current_height;
451 if (effective_height > 0 && effective_height <= relevant_height) {
452 effective_height = IntegerValue(has_demand_equal_to_capacity_ ? 1 : 0);
456 if (effective_height != height_at_start) {
457 profile_.emplace_back(current_start, height_at_start);
458 current_start =
time;
459 height_at_start = effective_height;
464 DCHECK_GE(current_height, 0);
465 profile_.emplace_back(current_start, IntegerValue(0));
471 return IncreaseCapacity(max_height_start, profile_max_height_);
474 void TimeTablingPerTask::ReverseProfile() {
476 std::reverse(profile_.begin() + 1, profile_.end() - 1);
477 for (
int i = 1; i + 1 < profile_.size(); ++i) {
478 profile_[i].start = -profile_[i].start;
479 profile_[i].height = profile_[i + 1].height;
483 bool TimeTablingPerTask::SweepAllTasks() {
485 int profile_index = 1;
486 const IntegerValue capa_max = CapacityMax();
492 if (helper_->
SizeMin(t) == 0)
continue;
496 const IntegerValue conflict_height = capa_max - demands_->
DemandMin(t);
501 if (conflict_height >= profile_max_height_)
continue;
503 if (!SweepTask(t,
time, conflict_height, &profile_index))
return false;
509 bool TimeTablingPerTask::SweepTask(
int task_id, IntegerValue initial_start_min,
510 IntegerValue conflict_height,
511 int* profile_index) {
513 const IntegerValue initial_end_min = helper_->
EndMin(task_id);
517 DCHECK(std::is_sorted(profile_.begin(), profile_.end()));
518 while (profile_[*profile_index].
start <= initial_start_min) {
521 int rec_id = *profile_index - 1;
536 IntegerValue new_start_min = initial_start_min;
537 if (IsInProfile(task_id)) {
539 for (; profile_[rec_id].start <
start_max; ++rec_id) {
541 if (profile_[rec_id].
height <= conflict_height)
continue;
545 new_start_min = profile_[rec_id + 1].start;
552 explanation_start_time = new_start_min - 1;
557 const IntegerValue
delta =
560 const IntegerValue threshold = CapacityMax() -
delta;
562 for (; profile_[rec_id].start < initial_end_min; ++rec_id) {
564 if (profile_[rec_id].
height <= threshold)
continue;
565 const IntegerValue new_max = CapacityMax() - profile_[rec_id].height +
572 AddProfileReason(task_id,
time,
time + 1, CapacityMax());
574 task_id, demands_->
Demands()[task_id].LowerOrEqual(new_max))) {
580 IntegerValue limit = initial_end_min;
581 const IntegerValue size_min = helper_->
SizeMin(task_id);
582 for (; profile_[rec_id].start < limit; ++rec_id) {
584 if (profile_[rec_id].
height <= conflict_height)
continue;
588 new_start_min = profile_[rec_id + 1].start;
589 limit =
std::max(limit, new_start_min + size_min);
590 if (profile_[rec_id].
start < initial_end_min) {
591 explanation_start_time =
std::min(new_start_min, initial_end_min) - 1;
597 explanation_start_time =
598 std::min(explanation_start_time, new_start_min - 1);
604 if (new_start_min == initial_start_min)
return true;
605 return UpdateStartingTime(task_id, explanation_start_time, new_start_min);
608 bool TimeTablingPerTask::UpdateStartingTime(
int task_id, IntegerValue left,
609 IntegerValue right) {
610 DCHECK_LT(left, right);
612 AddProfileReason(task_id, left, right,
613 CapacityMax() - demands_->
DemandMin(task_id));
631 void TimeTablingPerTask::AddProfileReason(
int task_id, IntegerValue left,
633 IntegerValue capacity_threshold) {
634 IntegerValue sum_of_demand(0);
638 DCHECK_GT(right, left);
639 DCHECK(task_id >= 0 || left + 1 == right);
640 if (left + 1 == right) {
644 }
else if (right - left < helper_->SizeMin(task_id) + 2) {
653 for (
int i = 0; i < num_profile_tasks_; ++i) {
654 const int t = profile_tasks_[i];
679 if (sum_of_demand > capacity_threshold)
break;
680 }
else if (mode == 1) {
690 bool TimeTablingPerTask::IncreaseCapacity(IntegerValue
time,
691 IntegerValue new_min) {
692 if (new_min <= CapacityMin())
return true;
696 new_min =
std::min(CapacityMax() + 1, new_min);
699 AddProfileReason(-1,
time,
time + 1, new_min);
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void RegisterReversibleInt(int id, int *rev)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
int Register(PropagatorInterface *propagator)
void NotifyThatPropagatorMayNotReachFixedPointInOnePass(int id)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
bool IsFixed(IntegerVariable i) const
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue LowerBound(IntegerVariable i) const
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
Class that owns everything related to a particular optimization model.
ReservoirTimeTabling(const std::vector< AffineExpression > ×, const std::vector< AffineExpression > &deltas, const std::vector< Literal > &presences, IntegerValue capacity, Model *model)
IntegerValue EndMin(int t) const
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
void AddSizeMinReason(int t)
const std::vector< TaskTime > & TaskByIncreasingStartMin()
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
void AddPresenceReason(int t)
const std::vector< TaskTime > & TaskByIncreasingEndMin()
std::vector< IntegerLiteral > * MutableIntegerReason()
bool IsPresent(int t) const
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
void AddEndMinReason(int t, IntegerValue lower_bound)
bool IsAbsent(int t) const
ABSL_MUST_USE_RESULT bool ReportConflict()
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
const std::vector< TaskTime > & TaskByDecreasingStartMax()
IntegerValue StartMax(int t) const
void AddStartMaxReason(int t, IntegerValue upper_bound)
IntegerValue SizeMin(int t) const
IntegerValue DemandMax(int t) const
void AddDemandMinReason(int t)
const std::vector< AffineExpression > & Demands() const
IntegerValue DemandMin(int t) const
void RegisterWith(GenericLiteralWatcher *watcher)
TimeTablingPerTask(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
bool LiteralIsTrue(Literal literal) const
bool LiteralIsFalse(Literal literal) const
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< AffineExpression > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Collection of objects used to extend the Constraint Solver library.
std::optional< int64_t > end
IntegerLiteral GreaterOrEqual(IntegerValue bound) const