Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cumulative_energy.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <iterator>
19#include <limits>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/container/inlined_vector.h"
25#include "absl/log/check.h"
26#include "absl/log/log.h"
27#include "absl/log/vlog_is_on.h"
28#include "absl/types/span.h"
32#include "ortools/sat/integer.h"
34#include "ortools/sat/model.h"
37#include "ortools/sat/util.h"
40
41namespace operations_research {
42namespace sat {
43
47 Model* model) {
48 auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
49 CumulativeEnergyConstraint* constraint =
50 new CumulativeEnergyConstraint(capacity, helper, demands, model);
51 constraint->RegisterWith(watcher);
52 model->TakeOwnership(constraint);
53}
54
58 Model* model) {
59 auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
60
62 new CumulativeDualFeasibleEnergyConstraint(capacity, helper, demands,
63 model);
64 constraint_dff->RegisterWith(watcher);
65 model->TakeOwnership(constraint_dff);
66}
67
70 SchedulingDemandHelper* demands, Model* model)
71 : capacity_(capacity),
72 integer_trail_(model->GetOrCreate<IntegerTrail>()),
73 helper_(helper),
74 demands_(demands) {
75 const int num_tasks = helper_->NumTasks();
76 task_to_start_event_.resize(num_tasks);
77}
78
80 const int id = watcher->Register(this);
81 helper_->WatchAllTasks(id);
82 watcher->SetPropagatorPriority(id, 2);
84}
85
87 // This only uses one time direction, but the helper might be used elsewhere.
88 // TODO(user): just keep the current direction?
89 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
90 if (!demands_->CacheAllEnergyValues()) return true;
91
92 const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
93 // TODO(user): force capacity_max >= 0, fail/remove optionals when 0.
94 if (capacity_max <= 0) return true;
95
96 // Set up theta tree.
97 start_event_task_time_.clear();
98 int num_events = 0;
99 for (const auto task_time : helper_->TaskByIncreasingStartMin()) {
100 const int task = task_time.task_index;
101 if (helper_->IsAbsent(task) || demands_->EnergyMax(task) == 0) {
102 task_to_start_event_[task] = -1;
103 continue;
104 }
105 start_event_task_time_.emplace_back(task_time);
106 task_to_start_event_[task] = num_events;
107 num_events++;
108 }
109 start_event_is_present_.assign(num_events, false);
110 theta_tree_.Reset(num_events);
111
112 bool tree_has_mandatory_intervals = false;
113
114 const IntegerValue start_end_magnitude =
115 std::max(IntTypeAbs(helper_->EndMax(
116 helper_->TaskByDecreasingEndMax().front().task_index)),
117 IntTypeAbs(helper_->TaskByIncreasingStartMin().front().time));
118 if (ProdOverflow(start_end_magnitude, capacity_max)) {
119 return true;
120 }
121
122 // Main loop: insert tasks by increasing end_max, check for overloads.
123 const auto by_decreasing_end_max = helper_->TaskByDecreasingEndMax();
124 for (const auto [current_task, current_end] :
125 ::gtl::reversed_view(by_decreasing_end_max)) {
126 if (task_to_start_event_[current_task] == -1) continue;
127
128 // Add the current task to the tree.
129 {
130 const int current_event = task_to_start_event_[current_task];
131 const IntegerValue start_min = start_event_task_time_[current_event].time;
132 const bool is_present = helper_->IsPresent(current_task);
133 start_event_is_present_[current_event] = is_present;
134 if (is_present) {
135 tree_has_mandatory_intervals = true;
136 theta_tree_.AddOrUpdateEvent(current_event, start_min * capacity_max,
137 demands_->EnergyMin(current_task),
138 demands_->EnergyMax(current_task));
139 } else {
140 theta_tree_.AddOrUpdateOptionalEvent(current_event,
141 start_min * capacity_max,
142 demands_->EnergyMax(current_task));
143 }
144 }
145
146 if (tree_has_mandatory_intervals) {
147 // Find the critical interval.
148 const IntegerValue envelope = theta_tree_.GetEnvelope();
149 const int critical_event =
150 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(envelope - 1);
151 const IntegerValue window_start =
152 start_event_task_time_[critical_event].time;
153 const IntegerValue window_end = current_end;
154 const IntegerValue window_size = window_end - window_start;
155 if (window_size == 0) continue;
156 const IntegerValue new_capacity_min =
157 CeilRatio(envelope - window_start * capacity_max, window_size);
158
159 // Push the new capacity min, note that this can fail if it go above the
160 // maximum capacity.
161 //
162 // TODO(user): We do not need the capacity max in the reason, but by using
163 // a lower one, we could maybe have propagated more the minimum capacity.
164 // investigate.
165 if (new_capacity_min > integer_trail_->LowerBound(capacity_)) {
166 helper_->ClearReason();
167 for (int event = critical_event; event < num_events; event++) {
168 if (start_event_is_present_[event]) {
169 const int task = start_event_task_time_[event].task_index;
170 helper_->AddPresenceReason(task);
171 demands_->AddEnergyMinReason(task);
172 helper_->AddStartMinReason(task, window_start);
173 helper_->AddEndMaxReason(task, window_end);
174 }
175 }
176 if (capacity_.var == kNoIntegerVariable) {
177 return helper_->ReportConflict();
178 } else {
179 if (!helper_->PushIntegerLiteral(
180 capacity_.GreaterOrEqual(new_capacity_min))) {
181 return false;
182 }
183 }
184 }
185 }
186
187 // Reduce energy of all tasks whose max energy would exceed an interval
188 // ending at current_end.
189 while (theta_tree_.GetOptionalEnvelope() > current_end * capacity_max) {
190 // Some task's max energy is too high, reduce its maximal energy.
191 // Explain with tasks present in the critical interval.
192 // If it is optional, it might get excluded, in that case,
193 // remove it from the tree.
194 // TODO(user): This could be done lazily.
195 // TODO(user): the same required task can have its energy pruned
196 // several times, making this algorithm O(n^2 log n). Is there a way
197 // to get the best pruning in one go? This looks like edge-finding not
198 // being able to converge in one pass, so it might not be easy.
199 helper_->ClearReason();
200 int critical_event;
201 int event_with_new_energy_max;
202 IntegerValue new_energy_max;
203 theta_tree_.GetEventsWithOptionalEnvelopeGreaterThan(
204 current_end * capacity_max, &critical_event,
205 &event_with_new_energy_max, &new_energy_max);
206
207 const IntegerValue window_start =
208 start_event_task_time_[critical_event].time;
209
210 // TODO(user): Improve window_end using envelope of critical event.
211 const IntegerValue window_end = current_end;
212 for (int event = critical_event; event < num_events; event++) {
213 if (start_event_is_present_[event]) {
214 if (event == event_with_new_energy_max) continue;
215 const int task = start_event_task_time_[event].task_index;
216 helper_->AddPresenceReason(task);
217 helper_->AddStartMinReason(task, window_start);
218 helper_->AddEndMaxReason(task, window_end);
219 demands_->AddEnergyMinReason(task);
220 }
221 }
222 if (capacity_.var != kNoIntegerVariable) {
223 helper_->MutableIntegerReason()->push_back(
224 integer_trail_->UpperBoundAsLiteral(capacity_.var));
225 }
226
227 const int task_with_new_energy_max =
228 start_event_task_time_[event_with_new_energy_max].task_index;
229 helper_->AddStartMinReason(task_with_new_energy_max, window_start);
230 helper_->AddEndMaxReason(task_with_new_energy_max, window_end);
231 if (!demands_->DecreaseEnergyMax(task_with_new_energy_max,
232 new_energy_max)) {
233 return false;
234 }
235
236 if (helper_->IsPresent(task_with_new_energy_max)) {
237 theta_tree_.AddOrUpdateEvent(
238 task_to_start_event_[task_with_new_energy_max],
239 start_event_task_time_[event_with_new_energy_max].time *
240 capacity_max,
241 demands_->EnergyMin(task_with_new_energy_max), new_energy_max);
242 } else {
243 theta_tree_.RemoveEvent(event_with_new_energy_max);
244 }
245 }
246 }
247 return true;
248}
249
251 IntegerVariable var, AffineExpression capacity,
252 const std::vector<int>& subtasks, absl::Span<const IntegerValue> offsets,
254 Model* model)
255 : var_to_push_(var),
256 capacity_(capacity),
257 subtasks_(subtasks),
258 integer_trail_(model->GetOrCreate<IntegerTrail>()),
259 helper_(helper),
260 demands_(demands) {
261 is_in_subtasks_.assign(helper->NumTasks(), false);
262 task_offsets_.assign(helper->NumTasks(), 0);
263 for (int i = 0; i < subtasks.size(); ++i) {
264 is_in_subtasks_[subtasks[i]] = true;
265 task_offsets_[subtasks[i]] = offsets[i];
266 }
267}
268
270 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
271
272 IntegerValue best_time = kMaxIntegerValue;
273 IntegerValue best_bound = kMinIntegerValue;
274
275 IntegerValue previous_time = kMaxIntegerValue;
276 IntegerValue energy_after_time(0);
277 IntegerValue profile_height(0);
278
279 // If the capacity_max is low enough, we compute the exact possible subset
280 // of reachable "sum of demands" of all tasks used in the energy. We will use
281 // the highest reachable as the capacity max.
282 const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
283 dp_.Reset(capacity_max.value());
284
285 // We consider the energy after a given time.
286 // From that we derive a bound on the end_min of the subtasks.
287 const auto& profile = helper_->GetEnergyProfile();
288 IntegerValue min_offset = kMaxIntegerValue;
289 for (int i = profile.size() - 1; i >= 0;) {
290 // Skip tasks not relevant for this propagator.
291 {
292 const int t = profile[i].task;
293 if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) {
294 --i;
295 continue;
296 }
297 }
298
299 const IntegerValue time = profile[i].time;
300 if (profile_height > 0) {
301 energy_after_time += profile_height * (previous_time - time);
302 }
303 previous_time = time;
304
305 // Any newly introduced tasks will only change the reachable capa max or
306 // the min_offset on the next time point.
307 const IntegerValue saved_capa_max = dp_.CurrentMax();
308 const IntegerValue saved_min_offset = min_offset;
309
310 for (; i >= 0 && profile[i].time == time; --i) {
311 // Skip tasks not relevant for this propagator.
312 const int t = profile[i].task;
313 if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) continue;
314
315 min_offset = std::min(min_offset, task_offsets_[t]);
316 const IntegerValue demand_min = demands_->DemandMin(t);
317 if (profile[i].is_first) {
318 profile_height -= demand_min;
319 } else {
320 profile_height += demand_min;
321 if (demands_->Demands()[t].IsConstant()) {
322 dp_.Add(demand_min.value());
323 } else {
324 dp_.Add(capacity_max.value()); // Abort DP.
325 }
326 }
327 }
328
329 // We prefer higher time in case of ties since that should reduce the
330 // explanation size.
331 //
332 // Note that if the energy is zero, we don't push anything. Other propagator
333 // will make sure that the end_min is greater than the end_min of any of
334 // the task considered here. TODO(user): actually, we will push using the
335 // last task, and the reason will be non-optimal, fix.
336 if (energy_after_time == 0) continue;
337 DCHECK_GT(saved_capa_max, 0);
338 DCHECK_LT(saved_min_offset, kMaxIntegerValue);
339 const IntegerValue end_min_with_offset =
340 time + CeilRatio(energy_after_time, saved_capa_max) + saved_min_offset;
341 if (end_min_with_offset > best_bound) {
342 best_time = time;
343 best_bound = end_min_with_offset;
344 }
345 }
346 DCHECK_EQ(profile_height, 0);
347
348 if (best_bound == kMinIntegerValue) return true;
349 if (best_bound > integer_trail_->LowerBound(var_to_push_)) {
350 // Compute the reason.
351 // It is just the reason for the energy after time.
352 helper_->ClearReason();
353 for (int t = 0; t < helper_->NumTasks(); ++t) {
354 if (!is_in_subtasks_[t]) continue;
355 if (!helper_->IsPresent(t)) continue;
356
357 const IntegerValue size_min = helper_->SizeMin(t);
358 if (size_min == 0) continue;
359
360 const IntegerValue demand_min = demands_->DemandMin(t);
361 if (demand_min == 0) continue;
362
363 const IntegerValue end_min = helper_->EndMin(t);
364 if (end_min <= best_time) continue;
365
366 helper_->AddEndMinReason(t, std::min(best_time + size_min, end_min));
367 helper_->AddSizeMinReason(t);
368 helper_->AddPresenceReason(t);
369 demands_->AddDemandMinReason(t);
370 }
371 if (capacity_.var != kNoIntegerVariable) {
372 helper_->MutableIntegerReason()->push_back(
373 integer_trail_->UpperBoundAsLiteral(capacity_.var));
374 }
375
376 // Propagate.
377 if (!helper_->PushIntegerLiteral(
378 IntegerLiteral::GreaterOrEqual(var_to_push_, best_bound))) {
379 return false;
380 }
381 }
382
383 return true;
384}
385
387 GenericLiteralWatcher* watcher) {
388 helper_->SetTimeDirection(true);
389 const int id = watcher->Register(this);
390 watcher->SetPropagatorPriority(id, 2);
391 watcher->WatchUpperBound(capacity_, id);
392 for (const int t : subtasks_) {
393 watcher->WatchLowerBound(helper_->Starts()[t], id);
394 watcher->WatchLowerBound(helper_->Ends()[t], id);
395 watcher->WatchLowerBound(helper_->Sizes()[t], id);
396 watcher->WatchLowerBound(demands_->Demands()[t], id);
397 if (!helper_->IsPresent(t) && !helper_->IsAbsent(t)) {
398 watcher->WatchLiteral(helper_->PresenceLiteral(t), id);
399 }
400 }
401}
402
405 SchedulingDemandHelper* demands, Model* model)
406 : random_(model->GetOrCreate<ModelRandomGenerator>()),
407 shared_stats_(model->GetOrCreate<SharedStatistics>()),
408 opp_infeasibility_detector_(*random_, shared_stats_),
409 capacity_(capacity),
410 integer_trail_(model->GetOrCreate<IntegerTrail>()),
411 helper_(helper),
412 demands_(demands) {
413 const int num_tasks = helper_->NumTasks();
414 task_to_start_event_.resize(num_tasks);
415}
416
419 if (!VLOG_IS_ON(1)) return;
420 std::vector<std::pair<std::string, int64_t>> stats;
421 stats.push_back(
422 {"CumulativeDualFeasibleEnergyConstraint/called", num_calls_});
423 stats.push_back(
424 {"CumulativeDualFeasibleEnergyConstraint/conflicts", num_conflicts_});
425 stats.push_back({"CumulativeDualFeasibleEnergyConstraint/no_potential_window",
426 num_no_potential_window_});
427
428 shared_stats_->AddStats(stats);
429}
430
432 GenericLiteralWatcher* watcher) {
433 const int id = watcher->Register(this);
434 helper_->WatchAllTasks(id);
435 watcher->SetPropagatorPriority(id, 3);
437}
438
439bool CumulativeDualFeasibleEnergyConstraint::FindAndPropagateConflict(
440 IntegerValue window_start, IntegerValue window_end) {
441 const int num_tasks = helper_->NumTasks();
442 const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
443 std::vector<IntegerValue> sizes;
444 std::vector<IntegerValue> demands;
445 std::vector<int> index_to_task;
446 sizes.reserve(num_tasks);
447 demands.reserve(num_tasks);
448 index_to_task.reserve(num_tasks);
449 for (int task = 0; task < num_tasks; ++task) {
450 if (!helper_->IsPresent(task) || demands_->DemandMin(task) == 0) {
451 continue;
452 }
453 const IntegerValue size = Smallest1DIntersection(
454 helper_->StartMin(task), helper_->EndMax(task), helper_->SizeMin(task),
455 window_start, window_end);
456 if (size == 0) continue;
457
458 sizes.push_back(size);
459 demands.push_back(demands_->DemandMin(task));
460 index_to_task.push_back(task);
461 }
462 auto result = opp_infeasibility_detector_.TestFeasibility(
463 sizes, demands, {window_end - window_start, capacity_max},
464 OrthogonalPackingOptions{
465 .use_pairwise = true,
466 .use_dff_f0 = true,
467 .use_dff_f2 = true,
468 // Disable brute force which is correct only for bin packing.
469 .brute_force_threshold = 0,
470 .dff2_max_number_of_parameters_to_check = 100});
471
472 if (result.GetResult() != OrthogonalPackingResult::Status::INFEASIBLE) {
473 return true;
474 }
475 VLOG_EVERY_N_SEC(2, 3) << "Found a conflict on the sub-problem of window ["
476 << window_start << ", " << window_end << "] (with "
477 << sizes.size() << "/" << num_tasks << " tasks)"
478 << " with "
479 << result.GetItemsParticipatingOnConflict().size()
480 << " tasks participating on the conflict.";
481
482 const auto& items = result.GetItemsParticipatingOnConflict();
483 for (int i = 0; i < items.size(); ++i) {
484 const int task = index_to_task[items[i].index];
485 const IntegerValue size_zero_level = Smallest1DIntersection(
486 helper_->LevelZeroStartMin(task), helper_->LevelZeroEndMax(task),
487 helper_->SizeMin(task), window_start, window_end);
488
489 result.TryUseSlackToReduceItemSize(
491 result.TryUseSlackToReduceItemSize(i,
493 demands_->LevelZeroDemandMin(task));
494 }
495 helper_->ClearReason();
496 for (const auto& item : result.GetItemsParticipatingOnConflict()) {
497 const int task = index_to_task[item.index];
498
499 const IntegerValue full_x_size = helper_->SizeMin(task);
500 const IntegerValue size_slack = full_x_size - item.size_x;
501
502 helper_->AddStartMinReason(task, window_start - size_slack);
503 helper_->AddEndMaxReason(task, window_end + size_slack);
504
505 helper_->AddSizeMinReason(task);
506 helper_->AddPresenceReason(task);
507
508 demands_->AddDemandMinReason(task, item.size_y);
509 }
510 if (capacity_.var != kNoIntegerVariable) {
511 helper_->MutableIntegerReason()->push_back(
512 integer_trail_->UpperBoundAsLiteral(capacity_.var));
513 }
514 return helper_->ReportConflict();
515}
516
518 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
519 if (!demands_->CacheAllEnergyValues()) return true;
520
521 const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
522 if (capacity_max <= 0) return true;
523
524 // Set up theta tree.
525 start_event_task_time_.clear();
526 int num_events = 0;
527 for (const auto task_time : helper_->TaskByIncreasingStartMin()) {
528 const int task = task_time.task_index;
529 if (!helper_->IsPresent(task) || demands_->DemandMin(task) == 0) {
530 task_to_start_event_[task] = -1;
531 continue;
532 }
533 start_event_task_time_.emplace_back(task_time);
534 task_to_start_event_[task] = num_events;
535 num_events++;
536 }
537
538 if (num_events == 0) return true;
539 ++num_calls_;
540
541 const IntegerValue start_end_magnitude =
542 std::max(IntTypeAbs(helper_->EndMax(
543 helper_->TaskByDecreasingEndMax().front().task_index)),
544 IntTypeAbs(helper_->TaskByIncreasingStartMin().front().time));
545 if (start_end_magnitude == 0) return true;
546
547 const IntegerValue max_energy =
548 CapProdI(CapProdI(start_end_magnitude, capacity_max), num_events);
549 if (max_energy == kMaxIntegerValue) {
550 return true;
551 }
552
553 const IntegerValue max_for_fixpoint_inverse =
554 std::numeric_limits<IntegerValue>::max() / max_energy;
555
556 theta_tree_.Reset(num_events);
557
558 // Since checking all possible dual-feasible functions is expensive, we only
559 // look for energy conflicts on time windows where a conflict with a DFF is
560 // possible. To rule out time windows where DFF conflicts are impossible, we
561 // use the following nice property stated in [1]:
562 //
563 // If f is a DFF, then for all possible sizes h_i of a problem of height H:
564 // f(h_i)/f(H) <= 1 / floor(H / h_i).
565 //
566 // This follows from the fact that floor(H / h_i) copies of h_i can fit
567 // sideways on the original problem and that those copies must still fit after
568 // any arbitrary DFF is applied.
569 //
570 // So, in practice, for a cumulative constraint with maximum capacity C and
571 // demands d_i, we look for time windows with energy conflicts for the
572 // modified problem:
573 // Capacity: L
574 // Demand for item i: L / (C / d_i)
575 // where L is any sufficiently large integer used to compute inverses without
576 // losing too much precision.
577 //
578 // [1] Carlier, Jacques, François Clautiaux, and Aziz Moukrim. "New reduction
579 // procedures and lower bounds for the two-dimensional bin packing problem
580 // with fixed orientation." Computers & Operations Research 34.8 (2007):
581 // 2223-2250.
582 std::vector<std::pair<IntegerValue, IntegerValue>> candidates_for_conflict;
583 const auto by_decreasing_end_max = helper_->TaskByDecreasingEndMax();
584 for (const auto [current_task, current_end] :
585 ::gtl::reversed_view(by_decreasing_end_max)) {
586 if (task_to_start_event_[current_task] == -1) continue;
587 if (!helper_->IsPresent(current_task)) continue;
588 if (helper_->SizeMin(current_task) == 0) continue;
589 if (demands_->DemandMin(current_task) == 0) continue;
590
591 if (demands_->DemandMin(current_task) > capacity_max) {
592 // Obvious conflict, we check here since we assume the demand for each
593 // task to be lower than the capacity in the code downstream.
594 demands_->AddDemandMinReason(current_task);
595
596 if (capacity_.var != kNoIntegerVariable) {
597 helper_->MutableIntegerReason()->push_back(
598 integer_trail_->UpperBoundAsLiteral(capacity_));
599 }
600
601 const AffineExpression size = helper_->Sizes()[current_task];
602 if (size.var != kNoIntegerVariable) {
603 helper_->MutableIntegerReason()->push_back(size.GreaterOrEqual(1));
604 }
605
606 return helper_->ReportConflict();
607 }
608
609 // Add the current task to the tree.
610 {
611 const IntegerValue current_pseudo_energy =
612 helper_->SizeMin(current_task) *
613 (max_for_fixpoint_inverse /
614 (capacity_max / demands_->DemandMin(current_task)));
615 const int current_event = task_to_start_event_[current_task];
616 const IntegerValue start_min = start_event_task_time_[current_event].time;
617 theta_tree_.AddOrUpdateEvent(
618 current_event, start_min * max_for_fixpoint_inverse,
619 current_pseudo_energy, current_pseudo_energy);
620 }
621
622 {
623 // Find the critical interval.
624 const IntegerValue envelope = theta_tree_.GetEnvelope();
625 const int critical_event =
626 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(envelope - 1);
627 const IntegerValue window_start =
628 start_event_task_time_[critical_event].time;
629 const IntegerValue window_end = current_end;
630 const IntegerValue window_size = window_end - window_start;
631 if (window_size == 0) continue;
632
633 if (envelope > window_end * max_for_fixpoint_inverse) {
634 candidates_for_conflict.push_back({window_start, window_end});
635 }
636 }
637 }
638 VLOG_EVERY_N_SEC(2, 3) << "Found " << candidates_for_conflict.size()
639 << " intervals with potential energy conflict using a "
640 "DFF on a problem of size "
641 << num_events << ".";
642
643 if (candidates_for_conflict.empty()) {
644 ++num_no_potential_window_;
645 return true;
646 }
647 // The code above is efficient for pruning the initial problem to a set of
648 // windows with potential conflict, but it might produce some "overly large"
649 // windows: ie., a window that has no conflict but would show one if narrowed.
650 //
651 // TODO(user): explore with using Theta-trees with a multi-valued energy
652 // value.
653 absl::InlinedVector<std::pair<IntegerValue, IntegerValue>, 3>
654 sampled_candidates;
655 std::sample(candidates_for_conflict.begin(), candidates_for_conflict.end(),
656 std::back_inserter(sampled_candidates), 3, *random_);
657 for (const auto& [window_start, window_end] : sampled_candidates) {
658 if (!FindAndPropagateConflict(window_start, window_end)) {
659 ++num_conflicts_;
660 return false;
661 }
662 }
663
664 return true;
665}
666
667} // namespace sat
668} // namespace operations_research
Implementation of AddCumulativeOverloadCheckerDff().
CumulativeDualFeasibleEnergyConstraint(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
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 WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1470
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1496
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1478
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2352
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2326
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1321
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())
int NumTasks() const
Returns the number of task.
Simple class to add statistics by name and print them at the end.
ReverseView< Container > reversed_view(const Container &c)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
bool ProdOverflow(IntegerValue t, IntegerValue value)
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)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
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)