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