OR-Tools  9.2
scheduling_cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <string>
23#include <utility>
24#include <vector>
25
30#include "ortools/sat/integer.h"
35#include "ortools/sat/util.h"
37
38namespace operations_research {
39namespace sat {
40
41namespace {
42
43// Minimum amount of violation of the cut constraint by the solution. This
44// is needed to avoid numerical issues and adding cuts with minor effect.
45const double kMinCutViolation = 1e-4;
46
47// Returns the lp value of a Literal.
48double GetLiteralLpValue(
49 const Literal lit,
51 const IntegerEncoder* encoder) {
52 const IntegerVariable direct_view = encoder->GetLiteralView(lit);
53 if (direct_view != kNoIntegerVariable) {
54 return lp_values[direct_view];
55 }
56 const IntegerVariable opposite_view = encoder->GetLiteralView(lit.Negated());
57 DCHECK_NE(opposite_view, kNoIntegerVariable);
58 return 1.0 - lp_values[opposite_view];
59}
60
61void AddIntegerVariableFromIntervals(SchedulingConstraintHelper* helper,
62 Model* model,
63 std::vector<IntegerVariable>* vars) {
64 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
65 for (int t = 0; t < helper->NumTasks(); ++t) {
66 if (helper->Starts()[t].var != kNoIntegerVariable) {
67 vars->push_back(helper->Starts()[t].var);
68 }
69 if (helper->Sizes()[t].var != kNoIntegerVariable) {
70 vars->push_back(helper->Sizes()[t].var);
71 }
72 if (helper->Ends()[t].var != kNoIntegerVariable) {
73 vars->push_back(helper->Ends()[t].var);
74 }
75 if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
76 !helper->IsPresent(t)) {
77 const Literal l = helper->PresenceLiteral(t);
78 if (encoder->GetLiteralView(l) == kNoIntegerVariable &&
79 encoder->GetLiteralView(l.Negated()) == kNoIntegerVariable) {
81 }
82 const IntegerVariable direct_view = encoder->GetLiteralView(l);
83 if (direct_view != kNoIntegerVariable) {
84 vars->push_back(direct_view);
85 } else {
86 vars->push_back(encoder->GetLiteralView(l.Negated()));
87 DCHECK_NE(vars->back(), kNoIntegerVariable);
88 }
89 }
90 }
92}
93
94} // namespace
95
96std::function<bool(const absl::StrongVector<IntegerVariable, double>&,
97 LinearConstraintManager*)>
98GenerateCumulativeEnergyCuts(const std::string& cut_name,
100 const std::vector<AffineExpression>& demands,
101 const std::vector<LinearExpression>& energies,
103 Trail* trail = model->GetOrCreate<Trail>();
104 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
105 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
106
107 return [capacity, demands, energies, trail, integer_trail, helper, model,
108 cut_name,
109 encoder](const absl::StrongVector<IntegerVariable, double>& lp_values,
110 LinearConstraintManager* manager) {
111 if (trail->CurrentDecisionLevel() > 0) return true;
112
113 const auto demand_is_fixed = [integer_trail, &demands](int i) {
114 return demands.empty() || integer_trail->IsFixed(demands[i]);
115 };
116 const auto demand_min = [integer_trail, &demands](int i) {
117 return demands.empty() ? IntegerValue(1)
118 : integer_trail->LowerBound(demands[i]);
119 };
120 const auto demand_max = [integer_trail, &demands](int i) {
121 return demands.empty() ? IntegerValue(1)
122 : integer_trail->UpperBound(demands[i]);
123 };
124
125 std::vector<int> active_intervals;
126 for (int i = 0; i < helper->NumTasks(); ++i) {
127 if (!helper->IsAbsent(i) && demand_max(i) > 0 && helper->SizeMin(i) > 0) {
128 active_intervals.push_back(i);
129 }
130 }
131
132 if (active_intervals.size() < 2) return true;
133
134 std::sort(active_intervals.begin(), active_intervals.end(),
135 [helper](int a, int b) {
136 return helper->StartMin(a) < helper->StartMin(b) ||
137 (helper->StartMin(a) == helper->StartMin(b) &&
138 helper->EndMax(a) < helper->EndMax(b));
139 });
140
141 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
142 IntegerValue processed_start = kMinIntegerValue;
143 for (int i1 = 0; i1 + 1 < active_intervals.size(); ++i1) {
144 const int start_index = active_intervals[i1];
145 DCHECK(!helper->IsAbsent(start_index));
146
147 // We want maximal cuts. For any start_min value, we only need to create
148 // cuts starting from the first interval having this start_min value.
149 if (helper->StartMin(start_index) == processed_start) {
150 continue;
151 } else {
152 processed_start = helper->StartMin(start_index);
153 }
154
155 // For each start time, we will keep the most violated cut generated while
156 // scanning the residual intervals.
157 int end_index_of_max_violation = -1;
158 double max_relative_violation = 1.01;
159 IntegerValue start_of_max_violation(0);
160 IntegerValue end_of_max_violation(0);
161 std::vector<int> lifted_intervals_of_max_violation;
162
163 // Accumulate intervals and check for potential cuts.
164 double energy_lp = 0.0;
165 IntegerValue min_of_starts = kMaxIntegerValue;
166 IntegerValue max_of_ends = kMinIntegerValue;
167
168 // We sort all tasks (start_min(task) >= start_min(start_index) by
169 // increasing end max.
170 std::vector<int> residual_intervals(active_intervals.begin() + i1,
171 active_intervals.end());
172 // Keep track of intervals not included in the potential cut.
173 // TODO(user): remove ?
174 std::set<int> intervals_not_visited(active_intervals.begin(),
175 active_intervals.end());
176 std::sort(
177 residual_intervals.begin(), residual_intervals.end(),
178 [&](int a, int b) { return helper->EndMax(a) < helper->EndMax(b); });
179
180 // Let's process residual tasks and evaluate the cut violation of the cut
181 // at each step. We follow the same structure as the cut creation code
182 // below.
183 for (int i2 = 0; i2 < residual_intervals.size(); ++i2) {
184 const int t = residual_intervals[i2];
185 intervals_not_visited.erase(t);
186 if (helper->IsPresent(t)) {
187 if (ProductIsLinearized(energies[t])) {
188 energy_lp += energies[t].LpValue(lp_values);
189 } else { // demand and size are not fixed.
190 DCHECK(!demands.empty());
191 energy_lp +=
192 ToDouble(demand_min(t)) * helper->Sizes()[t].LpValue(lp_values);
193 energy_lp +=
194 demands[t].LpValue(lp_values) * ToDouble(helper->SizeMin(t));
195 energy_lp -= ToDouble(demand_min(t) * helper->SizeMin(t));
196 }
197 } else {
198 // TODO(user): Use the energy min if better than size_min *
199 // demand_min, here and when building the cut.
200 energy_lp += GetLiteralLpValue(helper->PresenceLiteral(t), lp_values,
201 encoder) *
202 ToDouble(helper->SizeMin(t) * demand_min(t));
203 }
204
205 min_of_starts = std::min(min_of_starts, helper->StartMin(t));
206 max_of_ends = std::max(max_of_ends, helper->EndMax(t));
207
208 // Dominance rule. If the next interval also fits in
209 // [min_of_starts, max_of_ends], the cut will be stronger with the
210 // next interval.
211 if (i2 + 1 < residual_intervals.size() &&
212 helper->StartMin(residual_intervals[i2 + 1]) >= min_of_starts &&
213 helper->EndMax(residual_intervals[i2 + 1]) <= max_of_ends) {
214 continue;
215 }
216
217 // Compute forced contributions from intervals not included in
218 // [min_of_starts..max_of_ends].
219 //
220 // TODO(user): We could precompute possible intervals and store them
221 // by start_max, end_min to reduce the complexity.
222 std::vector<int> lifted_intervals;
223 std::vector<IntegerValue> lifted_min_overlap;
224 double forced_contrib_lp = 0.0;
225 for (const int t : intervals_not_visited) {
226 // It should not happen because of the 2 dominance rules above.
227 if (helper->StartMin(t) >= min_of_starts &&
228 helper->EndMax(t) <= max_of_ends) {
229 continue;
230 }
231
232 const IntegerValue min_overlap =
233 helper->GetMinOverlap(t, min_of_starts, max_of_ends);
234
235 if (min_overlap <= 0) continue;
236
237 lifted_intervals.push_back(t);
238
239 if (helper->IsPresent(t)) {
240 if (demand_is_fixed(t)) {
241 forced_contrib_lp += ToDouble(min_overlap * demand_min(t));
242 } else {
243 DCHECK(!demands.empty());
244 forced_contrib_lp +=
245 demands[t].LpValue(lp_values) * ToDouble(min_overlap);
246 }
247 } else {
248 forced_contrib_lp += GetLiteralLpValue(helper->PresenceLiteral(t),
249 lp_values, encoder) *
250 ToDouble(min_overlap * demand_min(t));
251 }
252 }
253
254 // Compute the violation of the potential cut.
255 const double relative_violation =
256 (energy_lp + forced_contrib_lp) /
257 ToDouble((max_of_ends - min_of_starts) * capacity_max);
258 if (relative_violation > max_relative_violation) {
259 end_index_of_max_violation = i2;
260 max_relative_violation = relative_violation;
261 start_of_max_violation = min_of_starts;
262 end_of_max_violation = max_of_ends;
263 lifted_intervals_of_max_violation = lifted_intervals;
264 }
265 }
266
267 if (end_index_of_max_violation == -1) continue;
268
269 // A maximal violated cut has been found.
270 bool cut_generated = true;
271 bool has_opt_cuts = false;
272 bool lifted = false;
273 bool has_quadratic_cuts = false;
274 bool use_energy = false;
275
276 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
277
278 // Build the cut.
279 cut.AddTerm(capacity, start_of_max_violation - end_of_max_violation);
280 for (int i2 = 0; i2 <= end_index_of_max_violation; ++i2) {
281 const int t = residual_intervals[i2];
282 if (helper->IsPresent(t)) {
283 if (ProductIsLinearized(energies[t])) {
284 // We favor the energy info instead of the McCormick relaxation.
285 cut.AddLinearExpression(energies[t]);
286 use_energy = true;
287 } else {
288 // This will add linear term if the size is fixed.
289 cut.AddQuadraticLowerBound(helper->Sizes()[t], demands[t],
290 integer_trail);
291 if (!helper->SizeIsFixed(t)) {
292 has_quadratic_cuts = true;
293 }
294 }
295 } else {
296 // TODO(user): use the offset of the energy expression if better
297 // than size_min * demand_min.
298 has_opt_cuts = true;
299 if (!helper->SizeIsFixed(t) || !demand_is_fixed(t)) {
300 has_quadratic_cuts = true;
301 }
302 if (!cut.AddLiteralTerm(helper->PresenceLiteral(t),
303 helper->SizeMin(t) * demand_min(t))) {
304 cut_generated = false;
305 break;
306 }
307 }
308 }
309
310 for (int i2 = 0; i2 < lifted_intervals_of_max_violation.size(); ++i2) {
311 const int t = lifted_intervals_of_max_violation[i2];
312 const IntegerValue min_overlap = helper->GetMinOverlap(
313 t, start_of_max_violation, end_of_max_violation);
314 lifted = true;
315
316 if (helper->IsPresent(t)) {
317 if (demand_is_fixed(t)) {
318 cut.AddConstant(min_overlap * demand_min(t));
319 } else {
320 DCHECK(!demands.empty());
321 cut.AddTerm(demands[t], min_overlap);
322 }
323 } else {
324 has_opt_cuts = true;
325 if (!cut.AddLiteralTerm(helper->PresenceLiteral(t),
326 min_overlap * demand_min(t))) {
327 cut_generated = false;
328 break;
329 }
330 }
331 }
332
333 if (cut_generated) {
334 std::string full_name = cut_name;
335 if (has_opt_cuts) full_name.append("_opt");
336 if (has_quadratic_cuts) full_name.append("_quad");
337 if (lifted) full_name.append("_lifted");
338 if (use_energy) full_name.append("_energy");
339
340 manager->AddCut(cut.Build(), full_name, lp_values);
341 }
342 }
343 return true;
344 };
345}
346
349 const std::vector<AffineExpression>& demands, IntegerTrail* integer_trail,
350 CutGenerator* result) {
351 for (const AffineExpression& demand_expr : demands) {
352 if (!integer_trail->IsFixed(demand_expr)) {
353 result->vars.push_back(demand_expr.var);
354 }
355 }
356 if (!integer_trail->IsFixed(capacity)) {
357 result->vars.push_back(capacity.var);
358 }
359}
360
362 const std::vector<IntervalVariable>& intervals,
364 const std::vector<AffineExpression>& demands,
365 const std::vector<LinearExpression>& energies, Model* model) {
366 CutGenerator result;
367
369 new SchedulingConstraintHelper(intervals, model);
370 model->TakeOwnership(helper);
371
372 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
373 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
374 AddIntegerVariableFromIntervals(helper, model, &result.vars);
375 for (const LinearExpression& energy : energies) {
376 result.vars.insert(result.vars.end(), energy.vars.begin(),
377 energy.vars.end());
378 }
380
381 // TODO(user): Do not create the cut generator if the capacity is fixed,
382 // all demands are fixed, and the intervals are always performed with a fixed
383 // size.
384 result.generate_cuts =
385 GenerateCumulativeEnergyCuts("CumulativeEnergy", helper, demands,
386 energies, AffineExpression(capacity), model);
387 return result;
388}
389
391 const std::vector<IntervalVariable>& intervals, Model* model) {
392 CutGenerator result;
393
395 new SchedulingConstraintHelper(intervals, model);
396 model->TakeOwnership(helper);
397
398 AddIntegerVariableFromIntervals(helper, model, &result.vars);
400
401 std::vector<LinearExpression> sizes;
402 sizes.reserve(intervals.size());
403 for (int i = 0; i < intervals.size(); ++i) {
405 builder.AddTerm(helper->Sizes()[i], IntegerValue(1));
406 sizes.push_back(builder.BuildExpression());
407 }
408
409 // TODO(user): Do not create the cut generator if all intervals are
410 // performed with a fixed size as it will not propagate more than
411 // the overload checker.
413 "NoOverlapEnergy", helper,
414 /*demands=*/{}, /*energies=*/sizes,
415 /*capacity=*/AffineExpression(IntegerValue(1)), model);
416 return result;
417}
418
420 const std::vector<IntervalVariable>& intervals,
422 const std::vector<AffineExpression>& demands, Model* model) {
423 CutGenerator result;
424
426 new SchedulingConstraintHelper(intervals, model);
427 model->TakeOwnership(helper);
428
429 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
430 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
431
432 AddIntegerVariableFromIntervals(helper, model, &result.vars);
434
435 struct Event {
436 int interval_index;
437 IntegerValue time;
438 bool positive;
440 };
441
442 Trail* trail = model->GetOrCreate<Trail>();
443
444 result.generate_cuts =
445 [helper, capacity, demands, trail, integer_trail, model](
447 LinearConstraintManager* manager) {
448 if (trail->CurrentDecisionLevel() > 0) return true;
449
450 std::vector<Event> events;
451 // Iterate through the intervals. If start_max < end_min, the demand
452 // is mandatory.
453 for (int i = 0; i < helper->NumTasks(); ++i) {
454 if (helper->IsAbsent(i)) continue;
455
456 const IntegerValue start_max = helper->StartMax(i);
457 const IntegerValue end_min = helper->EndMin(i);
458
459 if (start_max >= end_min) continue;
460
461 Event e1;
462 e1.interval_index = i;
463 e1.time = start_max;
464 e1.demand = demands[i];
465 e1.positive = true;
466
467 Event e2 = e1;
468 e2.time = end_min;
469 e2.positive = false;
470 events.push_back(e1);
471 events.push_back(e2);
472 }
473
474 // Sort events by time.
475 // It is also important that all positive event with the same time as
476 // negative events appear after for the correctness of the algo below.
477 std::sort(events.begin(), events.end(),
478 [](const Event i, const Event j) {
479 if (i.time == j.time) {
480 if (i.positive == j.positive) {
481 return i.interval_index < j.interval_index;
482 }
483 return !i.positive;
484 }
485 return i.time < j.time;
486 });
487
488 std::vector<Event> cut_events;
489 bool added_positive_event = false;
490 for (const Event& e : events) {
491 if (e.positive) {
492 added_positive_event = true;
493 cut_events.push_back(e);
494 continue;
495 }
496 if (added_positive_event && cut_events.size() > 1) {
497 // Create cut.
498 bool cut_generated = true;
500 IntegerValue(0));
501 cut.AddTerm(capacity, IntegerValue(-1));
502 for (const Event& cut_event : cut_events) {
503 if (helper->IsPresent(cut_event.interval_index)) {
504 cut.AddTerm(cut_event.demand, IntegerValue(1));
505 } else {
506 cut_generated &= cut.AddLiteralTerm(
507 helper->PresenceLiteral(cut_event.interval_index),
508 integer_trail->LowerBound(cut_event.demand));
509 if (!cut_generated) break;
510 }
511 }
512 if (cut_generated) {
513 // Violation of the cut is checked by AddCut so we don't check
514 // it here.
515 manager->AddCut(cut.Build(), "CumulativeTimeTable", lp_values);
516 }
517 }
518 // Remove the event.
519 int new_size = 0;
520 for (int i = 0; i < cut_events.size(); ++i) {
521 if (cut_events[i].interval_index == e.interval_index) {
522 continue;
523 }
524 cut_events[new_size] = cut_events[i];
525 new_size++;
526 }
527 cut_events.resize(new_size);
528 added_positive_event = false;
529 }
530 return true;
531 };
532 return result;
533}
534
535// Cached Information about one interval.
537 IntegerValue start_min;
538 IntegerValue start_max;
540 IntegerValue end_min;
541 IntegerValue end_max;
543 IntegerValue demand_min;
544};
545
547 const std::string& cut_name,
549 std::vector<PrecedenceEvent> events, IntegerValue capacity_max,
551 const int num_events = events.size();
552 if (num_events <= 1) return;
553
554 std::sort(events.begin(), events.end(),
555 [](const PrecedenceEvent& e1, const PrecedenceEvent& e2) {
556 return e1.start_min < e2.start_min ||
557 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
558 });
559
560 const double tolerance = 1e-4;
561
562 for (int i = 0; i + 1 < num_events; ++i) {
563 const PrecedenceEvent& e1 = events[i];
564 for (int j = i + 1; j < num_events; ++j) {
565 const PrecedenceEvent& e2 = events[j];
566 if (e2.start_min >= e1.end_max) break; // Break out of the index2 loop.
567
568 // Encode only the interesting pairs.
569 if (e1.demand_min + e2.demand_min <= capacity_max) continue;
570
571 const bool interval_1_can_precede_2 = e1.end_min <= e2.start_max;
572 const bool interval_2_can_precede_1 = e2.end_min <= e1.start_max;
573
574 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
575 e1.end.LpValue(lp_values) >=
576 e2.start.LpValue(lp_values) + tolerance) {
577 // interval1.end <= interval2.start
578 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
579 cut.AddTerm(e1.end, IntegerValue(1));
580 cut.AddTerm(e2.start, IntegerValue(-1));
581 } else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
582 e2.end.LpValue(lp_values) >=
583 e1.start.LpValue(lp_values) + tolerance) {
584 // interval2.end <= interval1.start
585 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
586 cut.AddTerm(e2.end, IntegerValue(1));
587 cut.AddTerm(e1.start, IntegerValue(-1));
588 manager->AddCut(cut.Build(), cut_name, lp_values);
589 }
590 }
591 }
592}
593
595 const std::vector<IntervalVariable>& intervals,
597 const std::vector<AffineExpression>& demands, Model* model) {
598 CutGenerator result;
599
601 new SchedulingConstraintHelper(intervals, model);
602 model->TakeOwnership(helper);
603
604 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
605 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
606
607 AddIntegerVariableFromIntervals(helper, model, &result.vars);
609
610 Trail* trail = model->GetOrCreate<Trail>();
611
612 result.generate_cuts =
613 [trail, integer_trail, helper, demands, capacity, model](
615 LinearConstraintManager* manager) {
616 if (trail->CurrentDecisionLevel() > 0) return true;
617
618 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
619 std::vector<PrecedenceEvent> events;
620 for (int t = 0; t < helper->NumTasks(); ++t) {
621 if (!helper->IsPresent(t)) continue;
622 PrecedenceEvent event;
623 event.start_min = helper->StartMin(t);
624 event.start_max = helper->StartMax(t);
625 event.start = helper->Starts()[t];
626 event.end_min = helper->EndMin(t);
627 event.end_max = helper->EndMax(t);
628 event.end = helper->Ends()[t];
629 event.demand_min = integer_trail->LowerBound(demands[t]);
630 events.push_back(event);
631 }
632 GeneratePrecedenceCuts("CumulativePrecedence", lp_values,
633 std::move(events), capacity_max, model, manager);
634 return true;
635 };
636 return result;
637}
638
640 const std::vector<IntervalVariable>& intervals, Model* model) {
641 CutGenerator result;
642
644 new SchedulingConstraintHelper(intervals, model);
645 model->TakeOwnership(helper);
646
647 AddIntegerVariableFromIntervals(helper, model, &result.vars);
648
649 Trail* trail = model->GetOrCreate<Trail>();
650
651 result.generate_cuts =
652 [trail, helper, model](
654 LinearConstraintManager* manager) {
655 if (trail->CurrentDecisionLevel() > 0) return true;
656
657 std::vector<PrecedenceEvent> events;
658 for (int t = 0; t < helper->NumTasks(); ++t) {
659 if (!helper->IsPresent(t)) continue;
660 PrecedenceEvent event;
661 event.start_min = helper->StartMin(t);
662 event.start_max = helper->StartMax(t);
663 event.start = helper->Starts()[t];
664 event.end_min = helper->EndMin(t);
665 event.end_max = helper->EndMax(t);
666 event.end = helper->Ends()[t];
667 event.demand_min = IntegerValue(1);
668 events.push_back(event);
669 }
670 GeneratePrecedenceCuts("NoOverlapPrecedence", lp_values,
671 std::move(events), IntegerValue(1), model,
672 manager);
673 return true;
674 };
675
676 return result;
677}
678
679// Stores the event for a box along the two axis x and y.
680// For a no_overlap constraint, y is always of size 1 between 0 and 1.
681// For a cumulative constraint, y is the demand that must be between 0 and
682// capacity_max.
683// For a no_overlap_2d constraint, y the other dimension of the box.
684struct CtEvent {
685 // The start min of the x interval.
686 IntegerValue x_start_min;
687
688 // The size min of the x interval.
689 IntegerValue x_size_min;
690
691 // The end of the x interval.
693
694 // The lp value of the end of the x interval.
695 double x_lp_end;
696
697 // The start min of the y interval.
698 IntegerValue y_start_min;
699
700 // The end max of the y interval.
701 IntegerValue y_end_max;
702
703 // The min energy of the task (this is always larger or equal to x_size_min *
704 // y_size_min).
705 IntegerValue energy_min;
706
707 // Indicates if the events used the optional energy information from the
708 // model.
709 bool use_energy = false;
710
711 // Indicates if the cut is lifted, that is if it includes tasks that are not
712 // strictly contained in the current time window.
713 bool lifted = false;
714
715 std::string DebugString() const {
716 return absl::StrCat("CtEvent(x_end = ", x_end.DebugString(),
717 ", x_start_min = ", x_start_min.value(),
718 ", x_size_min = ", x_size_min.value(),
719 ", x_lp_end = ", x_lp_end,
720 ", y_start_min = ", y_start_min.value(),
721 ", y_end_max = ", y_end_max.value(),
722 ", energy_min = ", energy_min.value(),
723 ", use_energy = ", use_energy, ", lifted = ", lifted);
724 }
725};
726
727// We generate the cut from the Smith's rule from:
728// M. Queyranne, Structure of a simple scheduling polyhedron,
729// Mathematical Programming 58 (1993), 263–285
730//
731// The original cut is:
732// sum(end_min_i * duration_min_i) >=
733// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
734// We strenghten this cuts by noticing that if all tasks starts after S,
735// then replacing end_min_i by (end_min_i - S) is still valid.
736//
737// A second difference is that we look at a set of intervals starting
738// after a given start_min, sorted by relative (end_lp - start_min).
740 const std::string& cut_name,
742 std::vector<CtEvent> events, bool use_lifting, Model* model,
743 LinearConstraintManager* manager) {
744 TopNCuts top_n_cuts(15);
745
746 // Sort by start min to bucketize by start_min.
747 std::sort(events.begin(), events.end(),
748 [](const CtEvent& e1, const CtEvent& e2) {
749 return e1.x_start_min < e2.x_start_min;
750 });
751 for (int start = 0; start + 1 < events.size(); ++start) {
752 // Skip to the next start_min value.
753 if (start > 0 &&
754 events[start].x_start_min == events[start - 1].x_start_min) {
755 continue;
756 }
757
758 const IntegerValue sequence_start_min = events[start].x_start_min;
759 std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
760
761 // We look at event that start before sequence_start_min, but are forced
762 // to cross this time point. In that case, we replace this event by a
763 // truncated event starting at sequence_start_min. To do this, we reduce
764 // the size_min, align the start_min with the sequence_start_min, and
765 // scale the energy down accordingly.
766 if (use_lifting) {
767 for (int before = 0; before < start; ++before) {
768 if (events[before].x_start_min + events[before].x_size_min >
769 sequence_start_min) { // Build the vector of energies as the vector
770 // of sizes.
771 CtEvent event = events[before]; // Copy.
772 event.lifted = true;
773 const IntegerValue old_size_min = event.x_size_min;
774 event.x_size_min =
775 event.x_size_min + event.x_start_min - sequence_start_min;
776 event.x_start_min = sequence_start_min;
777 // We can rescale the energy min correctly.
778 //
779 // Let's take the example of a box of size 2 * 20 that overlaps
780 // sequence start min by 1, and that can rotate by 90 degrees.
781 // The energy min is 40, size min is 2, size_max is 20.
782 // If the box is horizontal, the lifted energy is (20 - 1) * 2 = 38.
783 // If the box is vertical, the lifted energy is (2 - 1) * 20 = 20.
784 // The min of the two is always reached when size = size_min.
785 event.energy_min = event.energy_min * event.x_size_min / old_size_min;
786 residual_tasks.push_back(event);
787 }
788 }
789 }
790
791 std::sort(residual_tasks.begin(), residual_tasks.end(),
792 [](const CtEvent& e1, const CtEvent& e2) {
793 return e1.x_lp_end < e2.x_lp_end;
794 });
795
796 int best_end = -1;
797 double best_efficacy = 0.01;
798 IntegerValue best_min_contrib(0);
799 IntegerValue sum_duration(0);
800 IntegerValue sum_square_duration(0);
801 IntegerValue best_size_divisor(0);
802 double unscaled_lp_contrib = 0;
803 IntegerValue current_start_min(kMaxIntegerValue);
804 IntegerValue y_start_min = kMaxIntegerValue;
805 IntegerValue y_end_max = kMinIntegerValue;
806
807 for (int i = 0; i < residual_tasks.size(); ++i) {
808 const CtEvent& event = residual_tasks[i];
809 DCHECK_GE(event.x_start_min, sequence_start_min);
810 const IntegerValue energy = event.energy_min;
811 sum_duration += energy;
812 sum_square_duration += energy * energy;
813 unscaled_lp_contrib += event.x_lp_end * ToDouble(energy);
814 current_start_min = std::min(current_start_min, event.x_start_min);
815 y_start_min = std::min(y_start_min, event.y_start_min);
816 y_end_max = std::max(y_end_max, event.y_end_max);
817
818 const IntegerValue size_divisor = y_end_max - y_start_min;
819
820 // We compute the cuts with all the sizes actually equal to
821 // size_min * demand_min / size_divisor
822 // but to keep the computation in the integer domain, we multiply by
823 // size_divisor where needed instead.
824 const IntegerValue min_contrib =
825 (sum_duration * sum_duration + sum_square_duration) / 2 +
826 current_start_min * sum_duration * size_divisor;
827 const double efficacy = (ToDouble(min_contrib) -
828 unscaled_lp_contrib * ToDouble(size_divisor)) /
829 std::sqrt(ToDouble(sum_square_duration));
830 // TODO(user): Check overflow and ignore if too big.
831 if (efficacy > best_efficacy) {
832 best_efficacy = efficacy;
833 best_end = i;
834 best_min_contrib = min_contrib;
835 best_size_divisor = size_divisor;
836 }
837 }
838 if (best_end != -1) {
839 LinearConstraintBuilder cut(model, best_min_contrib, kMaxIntegerValue);
840 bool is_lifted = false;
841 bool use_energy = false;
842 for (int i = 0; i <= best_end; ++i) {
843 const CtEvent& event = residual_tasks[i];
844 is_lifted |= event.lifted;
845 use_energy |= event.use_energy;
846 cut.AddTerm(event.x_end, event.energy_min * best_size_divisor);
847 }
848 std::string full_name = cut_name;
849 if (is_lifted) full_name.append("_lifted");
850 if (use_energy) full_name.append("_energy");
851 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
852 }
853 }
854 top_n_cuts.TransferToManager(lp_values, manager);
855}
856
858 const std::vector<IntervalVariable>& intervals, Model* model) {
859 CutGenerator result;
860
862 new SchedulingConstraintHelper(intervals, model);
863 model->TakeOwnership(helper);
864
865 AddIntegerVariableFromIntervals(helper, model, &result.vars);
866
867 Trail* trail = model->GetOrCreate<Trail>();
868
869 result.generate_cuts =
870 [trail, helper, model](
872 LinearConstraintManager* manager) {
873 if (trail->CurrentDecisionLevel() > 0) return true;
874
875 auto generate_cuts = [&lp_values, model, manager,
876 helper](const std::string& cut_name) {
877 std::vector<CtEvent> events;
878 for (int index = 0; index < helper->NumTasks(); ++index) {
879 if (!helper->IsPresent(index)) continue;
880 const IntegerValue size_min = helper->SizeMin(index);
881 if (size_min > 0) {
882 const AffineExpression end_expr = helper->Ends()[index];
883 CtEvent event;
884 event.x_start_min = helper->StartMin(index);
885 event.x_size_min = size_min;
886 event.x_end = end_expr;
887 event.x_lp_end = end_expr.LpValue(lp_values);
888 event.y_start_min = IntegerValue(0);
889 event.y_end_max = IntegerValue(1);
890 event.energy_min = size_min;
891 events.push_back(event);
892 }
893 }
894 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
895 /*use_lifting=*/false, model, manager);
896 };
897 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
898 generate_cuts("NoOverlapCompletionTime");
899 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
900 generate_cuts("NoOverlapCompletionTimeMirror");
901 return true;
902 };
903 return result;
904}
905
907 const std::vector<IntervalVariable>& intervals,
909 const std::vector<AffineExpression>& demands,
910 const std::vector<LinearExpression>& energies, Model* model) {
911 CutGenerator result;
912
914 new SchedulingConstraintHelper(intervals, model);
915 model->TakeOwnership(helper);
916
917 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
918 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
919
920 AddIntegerVariableFromIntervals(helper, model, &result.vars);
922
923 Trail* trail = model->GetOrCreate<Trail>();
924
925 result.generate_cuts =
926 [trail, integer_trail, helper, demands, energies, capacity, model](
928 LinearConstraintManager* manager) {
929 if (trail->CurrentDecisionLevel() > 0) return true;
930
931 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
932 auto generate_cuts = [&lp_values, model, manager, helper, capacity_max,
933 integer_trail, &demands,
934 &energies](const std::string& cut_name) {
935 std::vector<CtEvent> events;
936 for (int index = 0; index < helper->NumTasks(); ++index) {
937 if (!helper->IsPresent(index)) continue;
938 if (helper->SizeMin(index) > 0 &&
939 integer_trail->LowerBound(demands[index]) > 0) {
940 const AffineExpression end_expr = helper->Ends()[index];
941 const IntegerValue size_min = helper->SizeMin(index);
942 const IntegerValue demand_min =
943 integer_trail->LowerBound(demands[index]);
944
945 CtEvent event;
946 event.x_start_min = helper->StartMin(index);
947 event.x_size_min = size_min;
948 event.x_end = end_expr;
949 event.x_lp_end = end_expr.LpValue(lp_values);
950 event.y_start_min = IntegerValue(0);
951 event.y_end_max = IntegerValue(capacity_max);
952 event.energy_min = size_min * demand_min;
953 // TODO(user): Investigate and re-enable a correct version.
954 if (/*DISABLES_CODE*/ (false) &&
955 ProductIsLinearized(energies[index])) {
956 const IntegerValue linearized_energy =
957 LinExprLowerBound(energies[index], *integer_trail);
958 if (linearized_energy > event.energy_min) {
959 event.energy_min = linearized_energy;
960 event.use_energy = true;
961 }
962 }
963 events.push_back(event);
964 }
965 }
966 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
967 /*use_lifting=*/true, model, manager);
968 };
969 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
970 generate_cuts("CumulativeCompletionTime");
971 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
972 generate_cuts("CumulativeCompletionTimeMirror");
973 return true;
974 };
975 return result;
976}
977
979 const std::vector<IntervalVariable>& x_intervals,
980 const std::vector<IntervalVariable>& y_intervals, Model* model) {
981 CutGenerator result;
982
984 new SchedulingConstraintHelper(x_intervals, model);
985 model->TakeOwnership(x_helper);
986
988 new SchedulingConstraintHelper(y_intervals, model);
989 model->TakeOwnership(y_helper);
990 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
991 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
992
993 Trail* trail = model->GetOrCreate<Trail>();
994
995 result.generate_cuts =
996 [trail, x_helper, y_helper, model](
998 LinearConstraintManager* manager) {
999 if (trail->CurrentDecisionLevel() > 0) return true;
1000
1001 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1002 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1003
1004 const int num_boxes = x_helper->NumTasks();
1005 std::vector<int> active_boxes;
1006 std::vector<IntegerValue> cached_areas(num_boxes);
1007 std::vector<Rectangle> cached_rectangles(num_boxes);
1008 for (int box = 0; box < num_boxes; ++box) {
1009 if (!y_helper->IsPresent(box) || !y_helper->IsPresent(box)) continue;
1010
1011 cached_areas[box] = x_helper->SizeMin(box) * y_helper->SizeMin(box);
1012 if (cached_areas[box] == 0) continue;
1013
1014 // TODO(user): It might be possible/better to use some shifted value
1015 // here, but for now this code is not in the hot spot, so better be
1016 // defensive and only do connected components on really disjoint
1017 // boxes.
1018 Rectangle& rectangle = cached_rectangles[box];
1019 rectangle.x_min = x_helper->StartMin(box);
1020 rectangle.x_max = x_helper->EndMax(box);
1021 rectangle.y_min = y_helper->StartMin(box);
1022 rectangle.y_max = y_helper->EndMax(box);
1023
1024 active_boxes.push_back(box);
1025 }
1026
1027 if (active_boxes.size() <= 1) return true;
1028
1029 std::vector<absl::Span<int>> components =
1030 GetOverlappingRectangleComponents(cached_rectangles,
1031 absl::MakeSpan(active_boxes));
1032 for (absl::Span<int> boxes : components) {
1033 if (boxes.size() <= 1) continue;
1034
1035 auto generate_cuts = [&lp_values, model, manager, &boxes,
1036 &cached_areas](
1037 const std::string& cut_name,
1039 SchedulingConstraintHelper* y_helper) {
1040 std::vector<CtEvent> events;
1041
1042 for (const int box : boxes) {
1043 const AffineExpression x_end_expr = x_helper->Ends()[box];
1044 CtEvent event;
1045 event.x_start_min = x_helper->ShiftedStartMin(box);
1046 event.x_size_min = x_helper->SizeMin(box);
1047 event.x_end = x_end_expr;
1048 event.x_lp_end = x_end_expr.LpValue(lp_values);
1049 event.y_start_min = y_helper->ShiftedStartMin(box);
1050 event.y_end_max = y_helper->ShiftedEndMax(box);
1051 event.energy_min =
1052 x_helper->SizeMin(box) * y_helper->SizeMin(box);
1053 events.push_back(event);
1054 }
1055
1056 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
1057 /*use_lifting=*/true, model, manager);
1058 };
1059
1060 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1061 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1062 generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper);
1063 generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper);
1064 if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
1065 if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
1066 generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
1067 generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
1068 }
1069 return true;
1070 };
1071 return result;
1072}
1073
1076 IntegerTrail* integer_trail, IntegerEncoder* encoder,
1077 LinearConstraintManager* manager,
1078 const std::vector<LinearExpression>& energies, absl::Span<int> boxes,
1079 const std::string& cut_name, SchedulingConstraintHelper* x_helper,
1080 SchedulingConstraintHelper* y_helper) {
1081 // Sort tasks by StartMin, tie breaked by EndMax.
1082 std::sort(boxes.begin(), boxes.end(), [x_helper](int a, int b) {
1083 return x_helper->StartMin(a) < x_helper->StartMin(b) ||
1084 (x_helper->StartMin(a) == x_helper->StartMin(b) &&
1085 x_helper->EndMax(a) < x_helper->EndMax(b));
1086 });
1087
1088 for (int i1 = 0; i1 + 1 < boxes.size(); ++i1) {
1089 // For each start time, we will keep the most violated cut
1090 // generated while scanning the residual intervals.
1091 int end_index_of_max_violation = -1;
1092 double max_relative_violation = 1.01;
1093 IntegerValue x_min_of_max_violation(0);
1094 IntegerValue x_max_of_max_violation(0);
1095 IntegerValue y_min_of_max_violation(0);
1096 IntegerValue y_max_of_max_violation(0);
1097
1098 // Accumulates intervals and check for potential cuts.
1099 double energy_lp = 0.0;
1100 IntegerValue x_min = kMaxIntegerValue;
1101 IntegerValue x_max = kMinIntegerValue;
1102 IntegerValue y_min = kMaxIntegerValue;
1103 IntegerValue y_max = kMinIntegerValue;
1104
1105 // We sort all tasks (start_min(task) >= start_min(start_index) by
1106 // increasing end max.
1107 std::vector<int> residual_intervals(boxes.begin() + i1, boxes.end());
1108 std::sort(residual_intervals.begin(), residual_intervals.end(),
1109 [&](int a, int b) {
1110 return x_helper->EndMax(a) < x_helper->EndMax(b);
1111 });
1112
1113 // Let's process residual tasks and evaluate the cut violation of
1114 // the cut at each step. We follow the same structure as the cut
1115 // creation code below.
1116 for (int i2 = 0; i2 < residual_intervals.size(); ++i2) {
1117 const int t = residual_intervals[i2];
1118 if (x_helper->IsPresent(t) && y_helper->IsPresent(t)) {
1119 if (ProductIsLinearized(energies[t])) {
1120 energy_lp += energies[t].LpValue(lp_values);
1121 } else { // demand and size are not fixed.
1122 energy_lp += x_helper->Sizes()[t].LpValue(lp_values) *
1123 ToDouble(y_helper->SizeMin(t));
1124 energy_lp += y_helper->Sizes()[t].LpValue(lp_values) *
1125 ToDouble(x_helper->SizeMin(t));
1126 energy_lp -= ToDouble(x_helper->SizeMin(t) * y_helper->SizeMin(t));
1127 }
1128 } else {
1129 const Literal lit = x_helper->IsOptional(t)
1130 ? x_helper->PresenceLiteral(t)
1131 : y_helper->PresenceLiteral(t);
1132 // TODO(user): Use the energy min if better than size_min *
1133 // demand_min, here and when building the cut.
1134 energy_lp += GetLiteralLpValue(lit, lp_values, encoder) *
1135 ToDouble(x_helper->SizeMin(t) * y_helper->SizeMin(t));
1136 }
1137
1138 // Update bounding box.
1139 x_min = std::min(x_min, x_helper->StartMin(t));
1140 x_max = std::max(x_max, x_helper->EndMax(t));
1141 y_min = std::min(y_min, y_helper->StartMin(t));
1142 y_max = std::max(y_max, y_helper->EndMax(t));
1143
1144 // Compute the violation of the potential cut.
1145 const double relative_violation =
1146 energy_lp / ToDouble((x_max - x_min) * (y_max - y_min));
1147 if (relative_violation > max_relative_violation) {
1148 end_index_of_max_violation = i2;
1149 max_relative_violation = relative_violation;
1150 x_min_of_max_violation = x_min;
1151 x_max_of_max_violation = x_max;
1152 y_min_of_max_violation = y_min;
1153 y_max_of_max_violation = y_max;
1154 }
1155 }
1156 if (end_index_of_max_violation == -1) return;
1157
1158 // A maximal violated cut has been found.
1159 bool has_opt_cuts = false;
1160 bool has_quadratic_cuts = false;
1161 bool use_energy = false;
1162
1165 (x_max_of_max_violation - x_min_of_max_violation) *
1166 (y_max_of_max_violation - y_min_of_max_violation));
1167
1168 // Build the cut.
1169 for (int i2 = 0; i2 <= end_index_of_max_violation; ++i2) {
1170 const int t = residual_intervals[i2];
1171 if (x_helper->IsPresent(t) && y_helper->IsPresent(t)) {
1172 if (ProductIsLinearized(energies[t])) {
1173 // We favor the energy info instead of the McCormick relaxation.
1174 cut.AddLinearExpression(energies[t]);
1175 use_energy = true;
1176 } else {
1177 // This will add linear term if the size is fixed.
1178 cut.AddQuadraticLowerBound(x_helper->Sizes()[t], y_helper->Sizes()[t],
1179 integer_trail);
1180 has_quadratic_cuts = true;
1181 }
1182 } else {
1183 // TODO(user): use the offset of the energy expression if better
1184 // than size_min * demand_min.
1185 DCHECK(!x_helper->IsPresent(t) || !y_helper->IsPresent(t));
1186 const Literal lit = !x_helper->IsPresent(t)
1187 ? x_helper->PresenceLiteral(t)
1188 : y_helper->PresenceLiteral(t);
1189 if (cut.AddLiteralTerm(lit,
1190 x_helper->SizeMin(t) * y_helper->SizeMin(t))) {
1191 has_opt_cuts = true;
1192 if (!x_helper->SizeIsFixed(t) && !y_helper->SizeIsFixed(t)) {
1193 has_quadratic_cuts = true;
1194 }
1195 }
1196 }
1197 }
1198
1199 std::string full_name = cut_name;
1200 if (has_opt_cuts) full_name.append("_opt");
1201 if (has_quadratic_cuts) full_name.append("_quad");
1202 if (use_energy) full_name.append("_energy");
1203
1204 manager->AddCut(cut.Build(), full_name, lp_values);
1205 }
1206}
1207
1209 const std::vector<IntervalVariable>& x_intervals,
1210 const std::vector<IntervalVariable>& y_intervals, Model* model) {
1211 CutGenerator result;
1212 IntervalsRepository* intervals_repository =
1213 model->GetOrCreate<IntervalsRepository>();
1214 const int num_boxes = x_intervals.size();
1215
1216 std::vector<LinearExpression> energies;
1217 std::vector<AffineExpression> x_sizes;
1218 std::vector<AffineExpression> y_sizes;
1219 for (int i = 0; i < num_boxes; ++i) {
1220 x_sizes.push_back(intervals_repository->Size(x_intervals[i]));
1221 y_sizes.push_back(intervals_repository->Size(y_intervals[i]));
1222 }
1223 LinearizeInnerProduct(x_sizes, y_sizes, model, &energies);
1224
1225 SchedulingConstraintHelper* x_helper =
1226 new SchedulingConstraintHelper(x_intervals, model);
1227 model->TakeOwnership(x_helper);
1228
1229 SchedulingConstraintHelper* y_helper =
1230 new SchedulingConstraintHelper(y_intervals, model);
1231 model->TakeOwnership(y_helper);
1232 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
1233 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
1234
1235 Trail* trail = model->GetOrCreate<Trail>();
1236 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1237 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1238
1239 result.generate_cuts =
1240 [integer_trail, trail, encoder, x_helper, y_helper, model, energies](
1242 LinearConstraintManager* manager) {
1243 if (trail->CurrentDecisionLevel() > 0) return true;
1244
1245 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1246 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1247
1248 const int num_boxes = x_helper->NumTasks();
1249 std::vector<int> active_boxes;
1250 std::vector<Rectangle> cached_rectangles(num_boxes);
1251 for (int box = 0; box < num_boxes; ++box) {
1252 if (y_helper->IsAbsent(box) || y_helper->IsAbsent(box)) continue;
1253 // We cannot consider boxes controlled by 2 active enforcement
1254 // literals.
1255 if (!x_helper->IsPresent(box) && !y_helper->IsPresent(box) &&
1256 x_helper->PresenceLiteral(box) !=
1257 y_helper->PresenceLiteral(box)) {
1258 continue;
1259 }
1260
1261 // TODO(user): It might be possible/better to use some shifted value
1262 // here, but for now this code is not in the hot spot, so better be
1263 // defensive and only do connected components on really disjoint
1264 // boxes.
1265 Rectangle& rectangle = cached_rectangles[box];
1266 rectangle.x_min = x_helper->StartMin(box);
1267 rectangle.x_max = x_helper->EndMax(box);
1268 rectangle.y_min = y_helper->StartMin(box);
1269 rectangle.y_max = y_helper->EndMax(box);
1270
1271 active_boxes.push_back(box);
1272 }
1273
1274 if (active_boxes.size() <= 1) return true;
1275
1276 std::vector<absl::Span<int>> components =
1277 GetOverlappingRectangleComponents(cached_rectangles,
1278 absl::MakeSpan(active_boxes));
1279
1280 // Forward pass.
1281 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1282 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1283 for (absl::Span<int> boxes : components) {
1284 if (boxes.size() <= 1) continue;
1285
1287 lp_values, model, integer_trail, encoder, manager, energies,
1288 boxes, "NoOverlap2dXEnergy", x_helper, y_helper);
1290 lp_values, model, integer_trail, encoder, manager, energies,
1291 boxes, "NoOverlap2dYEnergy", y_helper, x_helper);
1292 }
1293
1294 // Backward pass.
1295 if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
1296 if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
1297 for (absl::Span<int> boxes : components) {
1298 if (boxes.size() <= 1) continue;
1299
1301 lp_values, model, integer_trail, encoder, manager, energies,
1302 boxes, "NoOverlap2dXEnergyMirror", x_helper, y_helper);
1304 lp_values, model, integer_trail, encoder, manager, energies,
1305 boxes, "NoOverlap2dYEnergyMirror", y_helper, x_helper);
1306 }
1307 return true;
1308 };
1309 return result;
1310}
1311
1312} // namespace sat
1313} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:888
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:891
#define DCHECK(condition)
Definition: base/logging.h:886
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1443
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1439
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
AffineExpression Size(IntervalVariable i) const
Definition: intervals.h:94
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail)
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:336
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:338
IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const
Definition: intervals.cc:559
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:295
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:337
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
int64_t b
int64_t a
GRBmodel * model
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
static double ToDouble(double f)
Definition: lp_types.h:69
CutGenerator CreateCumulativeEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, const std::vector< LinearExpression > &energies, Model *model)
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition: integer.h:1630
CutGenerator CreateNoOverlap2dEnergyCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
void GeneratePrecedenceCuts(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< PrecedenceEvent > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntegerValue LinExprLowerBound(const LinearExpression &expr, const IntegerTrail &integer_trail)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:26
void LinearizeInnerProduct(const std::vector< AffineExpression > &left, const std::vector< AffineExpression > &right, Model *model, std::vector< LinearExpression > *energies)
const IntegerVariable kNoIntegerVariable(-1)
void AppendVariablesToCumulativeCut(const AffineExpression &capacity, const std::vector< AffineExpression > &demands, IntegerTrail *integer_trail, CutGenerator *result)
CutGenerator CreateCumulativeTimeTableCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, Model *model)
void GenerateCompletionTimeCuts(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CtEvent > events, bool use_lifting, Model *model, LinearConstraintManager *manager)
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
void GenerateNoOverlap2dEnergyCut(const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model, IntegerTrail *integer_trail, IntegerEncoder *encoder, LinearConstraintManager *manager, const std::vector< LinearExpression > &energies, absl::Span< int > boxes, const std::string &cut_name, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper)
bool ProductIsLinearized(const LinearExpression &expr)
std::function< bool(const absl::StrongVector< IntegerVariable, double > &, LinearConstraintManager *)> GenerateCumulativeEnergyCuts(const std::string &cut_name, SchedulingConstraintHelper *helper, const std::vector< AffineExpression > &demands, const std::vector< LinearExpression > &energies, AffineExpression capacity, Model *model)
CutGenerator CreateCumulativeCompletionTimeCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, const std::vector< LinearExpression > &energies, Model *model)
CutGenerator CreateNoOverlapEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
CutGenerator CreateCumulativePrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, Model *model)
double ToDouble(IntegerValue value)
Definition: integer.h:71
Collection of objects used to extend the Constraint Solver library.
int index
Definition: pack.cc:509
int64_t demand
Definition: resource.cc:125
int64_t energy
Definition: resource.cc:354
int64_t time
Definition: resource.cc:1691
int64_t capacity
Rev< int64_t > start_max
Rev< int64_t > end_min
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:272
const std::string DebugString() const
Definition: integer.h:278
std::vector< IntegerVariable > vars
Definition: cuts.h:43
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:47