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