24#include "absl/container/inlined_vector.h"
25#include "absl/log/check.h"
26#include "absl/types/span.h"
49 constraint->RegisterWith(watcher);
50 model->TakeOwnership(constraint);
62 constraint_dff->RegisterWith(watcher);
63 model->TakeOwnership(constraint_dff);
69 : capacity_(capacity),
73 const int num_tasks = helper_->
NumTasks();
74 task_to_start_event_.resize(num_tasks);
78 const int id = watcher->
Register(
this);
90 const IntegerValue capacity_max = integer_trail_->
UpperBound(capacity_);
92 if (capacity_max <= 0)
return true;
95 start_event_task_time_.clear();
98 const int task = task_time.task_index;
100 task_to_start_event_[task] = -1;
103 start_event_task_time_.emplace_back(task_time);
104 task_to_start_event_[task] = num_events;
107 start_event_is_present_.assign(num_events,
false);
108 theta_tree_.
Reset(num_events);
110 bool tree_has_mandatory_intervals =
false;
114 for (
const auto [current_task, current_end] :
116 if (task_to_start_event_[current_task] == -1)
continue;
120 const int current_event = task_to_start_event_[current_task];
121 const IntegerValue
start_min = start_event_task_time_[current_event].time;
122 const bool is_present = helper_->
IsPresent(current_task);
123 start_event_is_present_[current_event] = is_present;
125 tree_has_mandatory_intervals =
true;
136 if (tree_has_mandatory_intervals) {
138 const IntegerValue envelope = theta_tree_.
GetEnvelope();
139 const int critical_event =
141 const IntegerValue window_start =
142 start_event_task_time_[critical_event].time;
143 const IntegerValue window_end = current_end;
144 const IntegerValue window_size = window_end - window_start;
145 if (window_size == 0)
continue;
146 const IntegerValue new_capacity_min =
147 CeilRatio(envelope - window_start * capacity_max, window_size);
155 if (new_capacity_min > integer_trail_->
LowerBound(capacity_)) {
157 for (
int event = critical_event;
event < num_events;
event++) {
158 if (start_event_is_present_[event]) {
159 const int task = start_event_task_time_[event].task_index;
191 int event_with_new_energy_max;
192 IntegerValue new_energy_max;
194 current_end * capacity_max, &critical_event,
195 &event_with_new_energy_max, &new_energy_max);
197 const IntegerValue window_start =
198 start_event_task_time_[critical_event].time;
201 const IntegerValue window_end = current_end;
202 for (
int event = critical_event;
event < num_events;
event++) {
203 if (start_event_is_present_[event]) {
204 if (event == event_with_new_energy_max)
continue;
205 const int task = start_event_task_time_[event].task_index;
217 const int task_with_new_energy_max =
218 start_event_task_time_[event_with_new_energy_max].task_index;
226 if (helper_->
IsPresent(task_with_new_energy_max)) {
228 task_to_start_event_[task_with_new_energy_max],
229 start_event_task_time_[event_with_new_energy_max].
time *
231 demands_->
EnergyMin(task_with_new_energy_max), new_energy_max);
233 theta_tree_.
RemoveEvent(event_with_new_energy_max);
242 const std::vector<int>& subtasks, absl::Span<const IntegerValue> offsets,
251 is_in_subtasks_.assign(helper->
NumTasks(),
false);
252 task_offsets_.assign(helper->
NumTasks(), 0);
253 for (
int i = 0;
i < subtasks.size(); ++
i) {
254 is_in_subtasks_[subtasks[
i]] =
true;
255 task_offsets_[subtasks[
i]] = offsets[
i];
266 IntegerValue energy_after_time(0);
267 IntegerValue profile_height(0);
272 const IntegerValue capacity_max = integer_trail_->
UpperBound(capacity_);
273 dp_.
Reset(capacity_max.value());
279 for (
int i = profile.size() - 1;
i >= 0;) {
282 const int t = profile[
i].task;
283 if (!helper_->
IsPresent(t) || !is_in_subtasks_[t]) {
289 const IntegerValue
time = profile[
i].time;
290 if (profile_height > 0) {
291 energy_after_time += profile_height * (previous_time -
time);
293 previous_time =
time;
297 const IntegerValue saved_capa_max = dp_.
CurrentMax();
298 const IntegerValue saved_min_offset = min_offset;
300 for (;
i >= 0 && profile[
i].time ==
time; --
i) {
302 const int t = profile[
i].task;
303 if (!helper_->
IsPresent(t) || !is_in_subtasks_[t])
continue;
305 min_offset = std::min(min_offset, task_offsets_[t]);
306 const IntegerValue demand_min = demands_->
DemandMin(t);
307 if (profile[
i].is_first) {
308 profile_height -= demand_min;
310 profile_height += demand_min;
311 if (demands_->
Demands()[t].IsConstant()) {
312 dp_.
Add(demand_min.value());
314 dp_.
Add(capacity_max.value());
326 if (energy_after_time == 0)
continue;
327 DCHECK_GT(saved_capa_max, 0);
329 const IntegerValue end_min_with_offset =
330 time +
CeilRatio(energy_after_time, saved_capa_max) + saved_min_offset;
331 if (end_min_with_offset > best_bound) {
333 best_bound = end_min_with_offset;
336 DCHECK_EQ(profile_height, 0);
339 if (best_bound > integer_trail_->
LowerBound(var_to_push_)) {
343 for (
int t = 0; t < helper_->
NumTasks(); ++t) {
344 if (!is_in_subtasks_[t])
continue;
347 const IntegerValue size_min = helper_->
SizeMin(t);
348 if (size_min == 0)
continue;
350 const IntegerValue demand_min = demands_->
DemandMin(t);
351 if (demand_min == 0)
continue;
354 if (
end_min <= best_time)
continue;
379 const int id = watcher->
Register(
this);
382 for (
const int t : subtasks_) {
398 opp_infeasibility_detector_(*random_, shared_stats_),
403 const int num_tasks = helper_->
NumTasks();
404 task_to_start_event_.resize(num_tasks);
409 if (!VLOG_IS_ON(1))
return;
410 std::vector<std::pair<std::string, int64_t>> stats;
412 {
"CumulativeDualFeasibleEnergyConstraint/called", num_calls_});
414 {
"CumulativeDualFeasibleEnergyConstraint/conflicts", num_conflicts_});
415 stats.push_back({
"CumulativeDualFeasibleEnergyConstraint/no_potential_window",
416 num_no_potential_window_});
423 const int id = watcher->
Register(
this);
429bool CumulativeDualFeasibleEnergyConstraint::FindAndPropagateConflict(
430 IntegerValue window_start, IntegerValue window_end) {
431 const int num_tasks = helper_->
NumTasks();
432 const IntegerValue capacity_max = integer_trail_->
UpperBound(capacity_);
433 std::vector<IntegerValue> sizes;
434 std::vector<IntegerValue> demands;
435 std::vector<int> index_to_task;
436 sizes.reserve(num_tasks);
437 demands.reserve(num_tasks);
438 index_to_task.reserve(num_tasks);
439 for (
int task = 0; task < num_tasks; ++task) {
445 window_start, window_end);
446 if (
size == 0)
continue;
448 sizes.push_back(
size);
449 demands.push_back(demands_->
DemandMin(task));
450 index_to_task.push_back(task);
453 sizes, demands, {window_end - window_start, capacity_max},
454 OrthogonalPackingOptions{
455 .use_pairwise =
true,
459 .brute_force_threshold = 0,
460 .dff2_max_number_of_parameters_to_check = 100});
465 VLOG_EVERY_N_SEC(2, 3) <<
"Found a conflict on the sub-problem of window ["
466 << window_start <<
", " << window_end <<
"] (with "
467 << sizes.size() <<
"/" << num_tasks <<
" tasks)"
469 << result.GetItemsParticipatingOnConflict().size()
470 <<
" tasks participating on the conflict.";
472 const auto& items = result.GetItemsParticipatingOnConflict();
473 for (
int i = 0;
i < items.size(); ++
i) {
474 const int task = index_to_task[items[
i].index];
477 helper_->
SizeMin(task), window_start, window_end);
479 result.TryUseSlackToReduceItemSize(
481 result.TryUseSlackToReduceItemSize(
i,
486 for (
const auto& item : result.GetItemsParticipatingOnConflict()) {
487 const int task = index_to_task[item.index];
489 const IntegerValue full_x_size = helper_->
SizeMin(task);
490 const IntegerValue size_slack = full_x_size - item.size_x;
511 const IntegerValue capacity_max = integer_trail_->
UpperBound(capacity_);
512 if (capacity_max <= 0)
return true;
515 start_event_task_time_.clear();
518 const int task = task_time.task_index;
520 task_to_start_event_[task] = -1;
523 start_event_task_time_.emplace_back(task_time);
524 task_to_start_event_[task] = num_events;
528 if (num_events == 0)
return true;
531 const IntegerValue largest_window =
534 const IntegerValue max_for_fixpoint_inverse =
535 std::numeric_limits<IntegerValue>::max() /
536 (num_events * capacity_max * largest_window);
538 theta_tree_.
Reset(num_events);
564 std::vector<std::pair<IntegerValue, IntegerValue>> candidates_for_conflict;
566 for (
const auto [current_task, current_end] :
568 if (task_to_start_event_[current_task] == -1)
continue;
569 if (!helper_->
IsPresent(current_task))
continue;
573 const IntegerValue current_pseudo_energy =
574 helper_->
SizeMin(current_task) *
575 (max_for_fixpoint_inverse /
576 (capacity_max / demands_->
DemandMin(current_task)));
577 const int current_event = task_to_start_event_[current_task];
578 const IntegerValue
start_min = start_event_task_time_[current_event].time;
580 current_event,
start_min * max_for_fixpoint_inverse,
581 current_pseudo_energy, current_pseudo_energy);
586 const IntegerValue envelope = theta_tree_.
GetEnvelope();
587 const int critical_event =
589 const IntegerValue window_start =
590 start_event_task_time_[critical_event].time;
591 const IntegerValue window_end = current_end;
592 const IntegerValue window_size = window_end - window_start;
593 if (window_size == 0)
continue;
595 if (envelope > window_end * max_for_fixpoint_inverse) {
596 candidates_for_conflict.push_back({window_start, window_end});
600 VLOG_EVERY_N_SEC(2, 3) <<
"Found " << candidates_for_conflict.size()
601 <<
" intervals with potential energy conflict using a "
602 "DFF on a problem of size "
603 << num_events <<
".";
605 if (candidates_for_conflict.empty()) {
606 ++num_no_potential_window_;
615 absl::InlinedVector<std::pair<IntegerValue, IntegerValue>, 3>
617 std::sample(candidates_for_conflict.begin(), candidates_for_conflict.end(),
618 std::back_inserter(sampled_candidates), 3, *random_);
619 for (
const auto& [window_start, window_end] : sampled_candidates) {
620 if (!FindAndPropagateConflict(window_start, window_end)) {
Implementation of AddCumulativeOverloadCheckerDff().
void RegisterWith(GenericLiteralWatcher *watcher)
CumulativeDualFeasibleEnergyConstraint(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
~CumulativeDualFeasibleEnergyConstraint() override
Implementation of AddCumulativeOverloadChecker().
void RegisterWith(GenericLiteralWatcher *watcher)
CumulativeEnergyConstraint(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
CumulativeIsAfterSubsetConstraint(IntegerVariable var, AffineExpression capacity, const std::vector< int > &subtasks, absl::Span< const IntegerValue > offsets, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
void RegisterWith(GenericLiteralWatcher *watcher)
void NotifyThatPropagatorMayNotReachFixedPointInOnePass(int id)
void WatchLiteral(Literal l, int id, int watch_index=-1)
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
void SetPropagatorPriority(int id, int priority)
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.
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
IntegerValue UpperBound(IntegerVariable i) const
int64_t CurrentMax() const
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
void Reset(int64_t bound)
OrthogonalPackingResult TestFeasibility(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, const OrthogonalPackingOptions &options=OrthogonalPackingOptions())
void AddPresenceReason(int t)
absl::Span< const TaskTime > TaskByIncreasingStartMin()
void WatchAllTasks(int id, bool watch_max_side=true)
std::vector< IntegerLiteral > * MutableIntegerReason()
int NumTasks() const
Returns the number of task.
ABSL_MUST_USE_RESULT bool ReportConflict()
absl::Span< const AffineExpression > Sizes() const
const std::vector< ProfileEvent > & GetEnergyProfile()
bool IsPresent(int t) const
void SetTimeDirection(bool is_forward)
Literal PresenceLiteral(int index) const
void AddSizeMinReason(int t)
absl::Span< const AffineExpression > Starts() const
void AddEndMaxReason(int t, IntegerValue upper_bound)
absl::Span< const AffineExpression > Ends() const
void AddEndMinReason(int t, IntegerValue lower_bound)
IntegerValue StartMin(int t) const
void ClearReason()
Functions to clear and then set the current reason.
IntegerValue SizeMin(int t) const
absl::Span< const TaskTime > TaskByDecreasingEndMax()
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
IntegerValue EndMax(int t) const
bool IsAbsent(int t) const
IntegerValue EndMin(int t) const
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
IntegerValue LevelZeroEndMax(int t) const
void AddStartMinReason(int t, IntegerValue lower_bound)
IntegerValue LevelZeroStartMin(int t) const
void AddDemandMinReason(int t)
IntegerValue LevelZeroDemandMin(int t) const
IntegerValue EnergyMax(int t) const
void AddEnergyMinReason(int t)
void CacheAllEnergyValues()
const std::vector< AffineExpression > & Demands() const
ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value)
IntegerValue DemandMin(int t) const
IntegerValue EnergyMin(int t) const
Simple class to add statistics by name and print them at the end.
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
void Reset(int num_events)
IntegerType GetOptionalEnvelope() const
IntegerType GetEnvelope() const
void RemoveEvent(int event)
Makes event absent, compute the new envelope in O(log n).
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
ReverseView< Container > reversed_view(const Container &c)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddCumulativeOverloadCheckerDff(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
IntegerValue Smallest1DIntersection(IntegerValue range_min, IntegerValue range_max, IntegerValue size, IntegerValue interval_min, IntegerValue interval_max)
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)