OR-Tools  9.1
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<IntegerVariable>& 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 (demand_is_fixed(t)) {
188 if (helper->SizeIsFixed(t)) {
189 energy_lp += ToDouble(helper->SizeMin(t) * demand_min(t));
190 } else {
191 energy_lp += ToDouble(demand_min(t)) *
192 helper->Sizes()[t].LpValue(lp_values);
193 }
194 } else if (helper->SizeIsFixed(t)) {
195 DCHECK(!demands.empty());
196 energy_lp += lp_values[demands[t]] * ToDouble(helper->SizeMin(t));
197 } else if (!energies.empty()) {
198 energy_lp += energies[t].LpValue(lp_values);
199 } else { // demand and size are not fixed.
200 DCHECK(!demands.empty());
201 energy_lp +=
202 ToDouble(demand_min(t)) * helper->Sizes()[t].LpValue(lp_values);
203 energy_lp += lp_values[demands[t]] * ToDouble(helper->SizeMin(t));
204 energy_lp -= ToDouble(demand_min(t) * helper->SizeMin(t));
205 }
206 } else {
207 // TODO(user): Use the energy min if better than size_min *
208 // demand_min, here and when building the cut.
209 energy_lp += GetLiteralLpValue(helper->PresenceLiteral(t), lp_values,
210 encoder) *
211 ToDouble(helper->SizeMin(t) * demand_min(t));
212 }
213
214 min_of_starts = std::min(min_of_starts, helper->StartMin(t));
215 max_of_ends = std::max(max_of_ends, helper->EndMax(t));
216
217 // Dominance rule. If the next interval also fits in
218 // [min_of_starts, max_of_ends], the cut will be stronger with the
219 // next interval.
220 if (i2 + 1 < residual_intervals.size() &&
221 helper->StartMin(residual_intervals[i2 + 1]) >= min_of_starts &&
222 helper->EndMax(residual_intervals[i2 + 1]) <= max_of_ends) {
223 continue;
224 }
225
226 // Compute forced contributions from intervals not included in
227 // [min_of_starts..max_of_ends].
228 //
229 // TODO(user): We could precompute possible intervals and store them
230 // by start_max, end_min to reduce the complexity.
231 std::vector<int> lifted_intervals;
232 std::vector<IntegerValue> lifted_min_overlap;
233 double forced_contrib_lp = 0.0;
234 for (const int t : intervals_not_visited) {
235 // It should not happen because of the 2 dominance rules above.
236 if (helper->StartMin(t) >= min_of_starts &&
237 helper->EndMax(t) <= max_of_ends) {
238 continue;
239 }
240
241 const IntegerValue min_overlap =
242 helper->GetMinOverlap(t, min_of_starts, max_of_ends);
243
244 if (min_overlap <= 0) continue;
245
246 lifted_intervals.push_back(t);
247
248 if (helper->IsPresent(t)) {
249 if (demand_is_fixed(t)) {
250 forced_contrib_lp += ToDouble(min_overlap * demand_min(t));
251 } else {
252 DCHECK(!demands.empty());
253 forced_contrib_lp +=
254 lp_values[demands[t]] * ToDouble(min_overlap);
255 }
256 } else {
257 forced_contrib_lp += GetLiteralLpValue(helper->PresenceLiteral(t),
258 lp_values, encoder) *
259 ToDouble(min_overlap * demand_min(t));
260 }
261 }
262
263 // Compute the violation of the potential cut.
264 const double relative_violation =
265 (energy_lp + forced_contrib_lp) /
266 ToDouble((max_of_ends - min_of_starts) * capacity_max);
267 if (relative_violation > max_relative_violation) {
268 end_index_of_max_violation = i2;
269 max_relative_violation = relative_violation;
270 start_of_max_violation = min_of_starts;
271 end_of_max_violation = max_of_ends;
272 lifted_intervals_of_max_violation = lifted_intervals;
273 }
274 }
275
276 if (end_index_of_max_violation == -1) continue;
277
278 // A maximal violated cut has been found.
279 bool cut_generated = true;
280 bool has_opt_cuts = false;
281 bool lifted = false;
282 bool has_quadratic_cuts = false;
283 bool use_energy = false;
284
285 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
286
287 // Build the cut.
288 cut.AddTerm(capacity, start_of_max_violation - end_of_max_violation);
289 for (int i2 = 0; i2 <= end_index_of_max_violation; ++i2) {
290 const int t = residual_intervals[i2];
291 if (helper->IsPresent(t)) {
292 if (demand_is_fixed(t)) {
293 cut.AddTerm(helper->Sizes()[t], demand_min(t));
294 } else if (!helper->SizeIsFixed(t) && !energies.empty()) {
295 // We favor the energy info instead of the McCormick relaxation.
296 cut.AddLinearExpression(energies[t]);
297 use_energy = true;
298 } else {
299 // This will add linear term if the size is fixed.
300 cut.AddQuadraticLowerBound(helper->Sizes()[t], demands[t],
301 integer_trail);
302 if (!helper->SizeIsFixed(t)) {
303 has_quadratic_cuts = true;
304 }
305 }
306 } else {
307 // TODO(user): use the offset of the energy expression if better
308 // than size_min * demand_min.
309 has_opt_cuts = true;
310 if (!helper->SizeIsFixed(t) || !demand_is_fixed(t)) {
311 has_quadratic_cuts = true;
312 }
313 if (!cut.AddLiteralTerm(helper->PresenceLiteral(t),
314 helper->SizeMin(t) * demand_min(t))) {
315 cut_generated = false;
316 break;
317 }
318 }
319 }
320
321 for (int i2 = 0; i2 < lifted_intervals_of_max_violation.size(); ++i2) {
322 const int t = lifted_intervals_of_max_violation[i2];
323 const IntegerValue min_overlap = helper->GetMinOverlap(
324 t, start_of_max_violation, end_of_max_violation);
325 lifted = true;
326
327 if (helper->IsPresent(t)) {
328 if (demand_is_fixed(t)) {
329 cut.AddConstant(min_overlap * demand_min(t));
330 } else {
331 DCHECK(!demands.empty());
332 cut.AddTerm(demands[t], min_overlap);
333 }
334 } else {
335 has_opt_cuts = true;
336 if (!cut.AddLiteralTerm(helper->PresenceLiteral(t),
337 min_overlap * demand_min(t))) {
338 cut_generated = false;
339 break;
340 }
341 }
342 }
343
344 if (cut_generated) {
345 std::string full_name = cut_name;
346 if (has_opt_cuts) full_name.append("_opt");
347 if (has_quadratic_cuts) full_name.append("_quad");
348 if (lifted) full_name.append("_lifted");
349 if (use_energy) full_name.append("_energy");
350
351 manager->AddCut(cut.Build(), full_name, lp_values);
352 }
353 }
354 return true;
355 };
356}
357
359 const std::vector<IntervalVariable>& intervals,
360 const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
361 const std::vector<LinearExpression>& energies, Model* model) {
362 CutGenerator result;
363
365 new SchedulingConstraintHelper(intervals, model);
366 model->TakeOwnership(helper);
367
368 result.vars = demands;
369 result.vars.push_back(capacity);
370 AddIntegerVariableFromIntervals(helper, model, &result.vars);
371 for (const LinearExpression& energy : energies) {
372 result.vars.insert(result.vars.end(), energy.vars.begin(),
373 energy.vars.end());
374 }
376
377 // TODO(user): Do not create the cut generator if the capacity is fixed,
378 // all demands are fixed, and the intervals are always performed with a fixed
379 // size.
380 result.generate_cuts =
381 GenerateCumulativeEnergyCuts("CumulativeEnergy", helper, demands,
382 energies, AffineExpression(capacity), model);
383 return result;
384}
385
387 const std::vector<IntervalVariable>& intervals, Model* model) {
388 CutGenerator result;
389
391 new SchedulingConstraintHelper(intervals, model);
392 model->TakeOwnership(helper);
393
394 AddIntegerVariableFromIntervals(helper, model, &result.vars);
395
396 // TODO(user): Do not create the cut generator if all intervals are
397 // performed with a fixed size as it will not propagate more than
398 // the overload checker.
400 "NoOverlapEnergy", helper,
401 /*demands=*/{}, /*energies=*/{},
402 /*capacity=*/AffineExpression(IntegerValue(1)), model);
403 return result;
404}
405
407 const std::vector<IntervalVariable>& intervals,
408 const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
409 Model* model) {
410 CutGenerator result;
411
413 new SchedulingConstraintHelper(intervals, model);
414 model->TakeOwnership(helper);
415
416 result.vars = demands;
417 result.vars.push_back(capacity);
418 AddIntegerVariableFromIntervals(helper, model, &result.vars);
419
420 struct Event {
421 int interval_index;
422 IntegerValue time;
423 bool positive;
424 IntegerVariable demand;
425 };
426
427 Trail* trail = model->GetOrCreate<Trail>();
428 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
429
430 result.generate_cuts =
431 [helper, capacity, demands, trail, integer_trail, model](
433 LinearConstraintManager* manager) {
434 if (trail->CurrentDecisionLevel() > 0) return true;
435
436 std::vector<Event> events;
437 // Iterate through the intervals. If start_max < end_min, the demand
438 // is mandatory.
439 for (int i = 0; i < helper->NumTasks(); ++i) {
440 if (helper->IsAbsent(i)) continue;
441
442 const IntegerValue start_max = helper->StartMax(i);
443 const IntegerValue end_min = helper->EndMin(i);
444
445 if (start_max >= end_min) continue;
446
447 Event e1;
448 e1.interval_index = i;
449 e1.time = start_max;
450 e1.demand = demands[i];
451 e1.positive = true;
452
453 Event e2 = e1;
454 e2.time = end_min;
455 e2.positive = false;
456 events.push_back(e1);
457 events.push_back(e2);
458 }
459
460 // Sort events by time.
461 // It is also important that all positive event with the same time as
462 // negative events appear after for the correctness of the algo below.
463 std::sort(events.begin(), events.end(),
464 [](const Event i, const Event j) {
465 if (i.time == j.time) {
466 if (i.positive == j.positive) {
467 return i.interval_index < j.interval_index;
468 }
469 return !i.positive;
470 }
471 return i.time < j.time;
472 });
473
474 std::vector<Event> cut_events;
475 bool added_positive_event = false;
476 for (const Event& e : events) {
477 if (e.positive) {
478 added_positive_event = true;
479 cut_events.push_back(e);
480 continue;
481 }
482 if (added_positive_event && cut_events.size() > 1) {
483 // Create cut.
484 bool cut_generated = true;
486 IntegerValue(0));
487 cut.AddTerm(capacity, IntegerValue(-1));
488 for (const Event& cut_event : cut_events) {
489 if (helper->IsPresent(cut_event.interval_index)) {
490 cut.AddTerm(cut_event.demand, IntegerValue(1));
491 } else {
492 cut_generated &= cut.AddLiteralTerm(
493 helper->PresenceLiteral(cut_event.interval_index),
494 integer_trail->LowerBound(cut_event.demand));
495 if (!cut_generated) break;
496 }
497 }
498 if (cut_generated) {
499 // Violation of the cut is checked by AddCut so we don't check
500 // it here.
501 manager->AddCut(cut.Build(), "CumulativeTimeTable", lp_values);
502 }
503 }
504 // Remove the event.
505 int new_size = 0;
506 for (int i = 0; i < cut_events.size(); ++i) {
507 if (cut_events[i].interval_index == e.interval_index) {
508 continue;
509 }
510 cut_events[new_size] = cut_events[i];
511 new_size++;
512 }
513 cut_events.resize(new_size);
514 added_positive_event = false;
515 }
516 return true;
517 };
518 return result;
519}
520
521// Cached Information about one interval.
523 IntegerValue start_min;
524 IntegerValue start_max;
526 IntegerValue end_min;
527 IntegerValue end_max;
529 IntegerValue demand_min;
530};
531
533 const std::string& cut_name,
535 std::vector<PrecedenceEvent> events, IntegerValue capacity_max,
537 const int num_events = events.size();
538 if (num_events <= 1) return;
539
540 std::sort(events.begin(), events.end(),
541 [](const PrecedenceEvent& e1, const PrecedenceEvent& e2) {
542 return e1.start_min < e2.start_min ||
543 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
544 });
545
546 const double tolerance = 1e-4;
547
548 for (int i = 0; i + 1 < num_events; ++i) {
549 const PrecedenceEvent& e1 = events[i];
550 for (int j = i + 1; j < num_events; ++j) {
551 const PrecedenceEvent& e2 = events[j];
552 if (e2.start_min >= e1.end_max) break; // Break out of the index2 loop.
553
554 // Encode only the interesting pairs.
555 if (e1.demand_min + e2.demand_min <= capacity_max) continue;
556
557 const bool interval_1_can_precede_2 = e1.end_min <= e2.start_max;
558 const bool interval_2_can_precede_1 = e2.end_min <= e1.start_max;
559
560 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
561 e1.end.LpValue(lp_values) >=
562 e2.start.LpValue(lp_values) + tolerance) {
563 // interval1.end <= interval2.start
564 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
565 cut.AddTerm(e1.end, IntegerValue(1));
566 cut.AddTerm(e2.start, IntegerValue(-1));
567 } else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
568 e2.end.LpValue(lp_values) >=
569 e1.start.LpValue(lp_values) + tolerance) {
570 // interval2.end <= interval1.start
571 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
572 cut.AddTerm(e2.end, IntegerValue(1));
573 cut.AddTerm(e1.start, IntegerValue(-1));
574 manager->AddCut(cut.Build(), cut_name, lp_values);
575 }
576 }
577 }
578}
579
581 const std::vector<IntervalVariable>& intervals, IntegerVariable capacity,
582 const std::vector<IntegerVariable>& demands, Model* model) {
583 CutGenerator result;
584
586 new SchedulingConstraintHelper(intervals, model);
587 model->TakeOwnership(helper);
588
589 result.vars = demands;
590 result.vars.push_back(capacity);
591 AddIntegerVariableFromIntervals(helper, model, &result.vars);
592
593 Trail* trail = model->GetOrCreate<Trail>();
594 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
595
596 result.generate_cuts =
597 [trail, integer_trail, helper, demands, capacity, model](
599 LinearConstraintManager* manager) {
600 if (trail->CurrentDecisionLevel() > 0) return true;
601
602 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
603 std::vector<PrecedenceEvent> events;
604 for (int t = 0; t < helper->NumTasks(); ++t) {
605 if (!helper->IsPresent(t)) continue;
606 PrecedenceEvent event;
607 event.start_min = helper->StartMin(t);
608 event.start_max = helper->StartMax(t);
609 event.start = helper->Starts()[t];
610 event.end_min = helper->EndMin(t);
611 event.end_max = helper->EndMax(t);
612 event.end = helper->Ends()[t];
613 event.demand_min = integer_trail->LowerBound(demands[t]);
614 events.push_back(event);
615 }
616 GeneratePrecedenceCuts("CumulativePrecedence", lp_values,
617 std::move(events), capacity_max, model, manager);
618 return true;
619 };
620 return result;
621}
622
624 const std::vector<IntervalVariable>& intervals, Model* model) {
625 CutGenerator result;
626
628 new SchedulingConstraintHelper(intervals, model);
629 model->TakeOwnership(helper);
630
631 AddIntegerVariableFromIntervals(helper, model, &result.vars);
632
633 Trail* trail = model->GetOrCreate<Trail>();
634
635 result.generate_cuts =
636 [trail, helper, model](
638 LinearConstraintManager* manager) {
639 if (trail->CurrentDecisionLevel() > 0) return true;
640
641 std::vector<PrecedenceEvent> events;
642 for (int t = 0; t < helper->NumTasks(); ++t) {
643 if (!helper->IsPresent(t)) continue;
644 PrecedenceEvent event;
645 event.start_min = helper->StartMin(t);
646 event.start_max = helper->StartMax(t);
647 event.start = helper->Starts()[t];
648 event.end_min = helper->EndMin(t);
649 event.end_max = helper->EndMax(t);
650 event.end = helper->Ends()[t];
651 event.demand_min = IntegerValue(1);
652 events.push_back(event);
653 }
654 GeneratePrecedenceCuts("NoOverlapPrecedence", lp_values,
655 std::move(events), IntegerValue(1), model,
656 manager);
657 return true;
658 };
659
660 return result;
661}
662
663// Stores the event for a box along the two axis x and y.
664// For a no_overlap constraint, y is always of size 1 between 0 and 1.
665// For a cumulative constraint, y is the demand that must be between 0 and
666// capacity_max.
667// For a no_overlap_2d constraint, y the other dimension of the box.
668struct CtEvent {
669 // The start min of the x interval.
670 IntegerValue x_start_min;
671
672 // The size min of the x interval.
673 IntegerValue x_size_min;
674
675 // The end of the x interval.
677
678 // The lp value of the end of the x interval.
679 double x_lp_end;
680
681 // The start min of the y interval.
682 IntegerValue y_start_min;
683
684 // The end max of the y interval.
685 IntegerValue y_end_max;
686
687 // The min energy of the task (this is always larger or equal to x_size_min *
688 // y_size_min).
689 IntegerValue energy_min;
690
691 // Indicates if the events used the optional energy information from the
692 // model.
693 bool use_energy = false;
694
695 // Indicates if the cut is lifted, that is if it includes tasks that are not
696 // strictly contained in the current time window.
697 bool lifted = false;
698
699 std::string DebugString() const {
700 return absl::StrCat("CtEvent(x_end = ", x_end.DebugString(),
701 ", x_start_min = ", x_start_min.value(),
702 ", x_size_min = ", x_size_min.value(),
703 ", x_lp_end = ", x_lp_end,
704 ", y_start_min = ", y_start_min.value(),
705 ", y_end_max = ", y_end_max.value(),
706 ", energy_min = ", energy_min.value(),
707 ", use_energy = ", use_energy, ", lifted = ", lifted);
708 }
709};
710
711// We generate the cut from the Smith's rule from:
712// M. Queyranne, Structure of a simple scheduling polyhedron,
713// Mathematical Programming 58 (1993), 263–285
714//
715// The original cut is:
716// sum(end_min_i * duration_min_i) >=
717// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
718// We strenghten this cuts by noticing that if all tasks starts after S,
719// then replacing end_min_i by (end_min_i - S) is still valid.
720//
721// A second difference is that we look at a set of intervals starting
722// after a given start_min, sorted by relative (end_lp - start_min).
724 const std::string& cut_name,
726 std::vector<CtEvent> events, bool use_lifting, Model* model,
727 LinearConstraintManager* manager) {
728 TopNCuts top_n_cuts(15);
729
730 // Sort by start min to bucketize by start_min.
731 std::sort(events.begin(), events.end(),
732 [](const CtEvent& e1, const CtEvent& e2) {
733 return e1.x_start_min < e2.x_start_min;
734 });
735 for (int start = 0; start + 1 < events.size(); ++start) {
736 // Skip to the next start_min value.
737 if (start > 0 &&
738 events[start].x_start_min == events[start - 1].x_start_min) {
739 continue;
740 }
741
742 const IntegerValue sequence_start_min = events[start].x_start_min;
743 std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
744
745 // We look at event that start before sequence_start_min, but are forced
746 // to cross this time point. In that case, we replace this event by a
747 // truncated event starting at sequence_start_min. To do this, we reduce
748 // the size_min, align the start_min with the sequence_start_min, and
749 // scale the energy down accordingly.
750 if (use_lifting) {
751 for (int before = 0; before < start; ++before) {
752 if (events[before].x_start_min + events[before].x_size_min >
753 sequence_start_min) {
754 CtEvent event = events[before]; // Copy.
755 event.lifted = true;
756 const IntegerValue old_size_min = event.x_size_min;
757 event.x_size_min =
758 event.x_size_min + event.x_start_min - sequence_start_min;
759 event.x_start_min = sequence_start_min;
760 // We can rescale the energy min correctly.
761 //
762 // Let's take the example of a box of size 2 * 20 that overlaps
763 // sequence start min by 1, and that can rotate by 90 degrees.
764 // The energy min is 40, size min is 2, size_max is 20.
765 // If the box is horizontal, the lifted energy is (20 - 1) * 2 = 38.
766 // If the box is vertical, the lifted energy is (2 - 1) * 20 = 20.
767 // The min of the two is always reached when size = size_min.
768 event.energy_min = event.energy_min * event.x_size_min / old_size_min;
769 residual_tasks.push_back(event);
770 }
771 }
772 }
773
774 std::sort(residual_tasks.begin(), residual_tasks.end(),
775 [](const CtEvent& e1, const CtEvent& e2) {
776 return e1.x_lp_end < e2.x_lp_end;
777 });
778
779 int best_end = -1;
780 double best_efficacy = 0.01;
781 IntegerValue best_min_contrib(0);
782 IntegerValue sum_duration(0);
783 IntegerValue sum_square_duration(0);
784 IntegerValue best_size_divisor(0);
785 double unscaled_lp_contrib = 0;
786 IntegerValue current_start_min(kMaxIntegerValue);
787 IntegerValue y_start_min = kMaxIntegerValue;
788 IntegerValue y_end_max = kMinIntegerValue;
789
790 for (int i = 0; i < residual_tasks.size(); ++i) {
791 const CtEvent& event = residual_tasks[i];
792 DCHECK_GE(event.x_start_min, sequence_start_min);
793 const IntegerValue energy = event.energy_min;
794 sum_duration += energy;
795 sum_square_duration += energy * energy;
796 unscaled_lp_contrib += event.x_lp_end * ToDouble(energy);
797 current_start_min = std::min(current_start_min, event.x_start_min);
798 y_start_min = std::min(y_start_min, event.y_start_min);
799 y_end_max = std::max(y_end_max, event.y_end_max);
800
801 const IntegerValue size_divisor = y_end_max - y_start_min;
802
803 // We compute the cuts with all the sizes actually equal to
804 // size_min * demand_min / size_divisor
805 // but to keep the computation in the integer domain, we multiply by
806 // size_divisor where needed instead.
807 const IntegerValue min_contrib =
808 (sum_duration * sum_duration + sum_square_duration) / 2 +
809 current_start_min * sum_duration * size_divisor;
810 const double efficacy = (ToDouble(min_contrib) -
811 unscaled_lp_contrib * ToDouble(size_divisor)) /
812 std::sqrt(ToDouble(sum_square_duration));
813 // TODO(user): Check overflow and ignore if too big.
814 if (efficacy > best_efficacy) {
815 best_efficacy = efficacy;
816 best_end = i;
817 best_min_contrib = min_contrib;
818 best_size_divisor = size_divisor;
819 }
820 }
821 if (best_end != -1) {
822 LinearConstraintBuilder cut(model, best_min_contrib, kMaxIntegerValue);
823 bool is_lifted = false;
824 bool use_energy = false;
825 for (int i = 0; i <= best_end; ++i) {
826 const CtEvent& event = residual_tasks[i];
827 is_lifted |= event.lifted;
828 use_energy |= event.use_energy;
829 cut.AddTerm(event.x_end, event.energy_min * best_size_divisor);
830 }
831 std::string full_name = cut_name;
832 if (is_lifted) full_name.append("_lifted");
833 if (use_energy) full_name.append("_energy");
834 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
835 }
836 }
837 top_n_cuts.TransferToManager(lp_values, manager);
838}
839
841 const std::vector<IntervalVariable>& intervals, Model* model) {
842 CutGenerator result;
843
845 new SchedulingConstraintHelper(intervals, model);
846 model->TakeOwnership(helper);
847
848 AddIntegerVariableFromIntervals(helper, model, &result.vars);
849
850 Trail* trail = model->GetOrCreate<Trail>();
851
852 result.generate_cuts =
853 [trail, helper, model](
855 LinearConstraintManager* manager) {
856 if (trail->CurrentDecisionLevel() > 0) return true;
857
858 auto generate_cuts = [&lp_values, model, manager,
859 helper](const std::string& cut_name) {
860 std::vector<CtEvent> events;
861 for (int index = 0; index < helper->NumTasks(); ++index) {
862 if (!helper->IsPresent(index)) continue;
863 const IntegerValue size_min = helper->SizeMin(index);
864 if (size_min > 0) {
865 const AffineExpression end_expr = helper->Ends()[index];
866 CtEvent event;
867 event.x_start_min = helper->StartMin(index);
868 event.x_size_min = size_min;
869 event.x_end = end_expr;
870 event.x_lp_end = end_expr.LpValue(lp_values);
871 event.y_start_min = IntegerValue(0);
872 event.y_end_max = IntegerValue(1);
873 event.energy_min = size_min;
874 events.push_back(event);
875 }
876 }
877 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
878 /*use_lifting=*/false, model, manager);
879 };
880 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
881 generate_cuts("NoOverlapCompletionTime");
882 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
883 generate_cuts("NoOverlapCompletionTimeMirror");
884 return true;
885 };
886 return result;
887}
888
890 const std::vector<IntervalVariable>& intervals,
891 const IntegerVariable capacity, const std::vector<IntegerVariable>& demands,
892 const std::vector<LinearExpression>& energies, Model* model) {
893 CutGenerator result;
894
896 new SchedulingConstraintHelper(intervals, model);
897 model->TakeOwnership(helper);
898
899 result.vars = demands;
900 result.vars.push_back(capacity);
901 AddIntegerVariableFromIntervals(helper, model, &result.vars);
902
903 Trail* trail = model->GetOrCreate<Trail>();
904 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
905
906 result.generate_cuts =
907 [trail, integer_trail, helper, demands, energies, capacity, model](
909 LinearConstraintManager* manager) {
910 if (trail->CurrentDecisionLevel() > 0) return true;
911
912 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
913 auto generate_cuts = [&lp_values, model, manager, helper, capacity_max,
914 integer_trail, &demands,
915 &energies](const std::string& cut_name) {
916 std::vector<CtEvent> events;
917 for (int index = 0; index < helper->NumTasks(); ++index) {
918 if (!helper->IsPresent(index)) continue;
919 if (helper->SizeMin(index) > 0 &&
920 integer_trail->LowerBound(demands[index]) > 0) {
921 const AffineExpression end_expr = helper->Ends()[index];
922 IntegerValue energy_min =
923 energies.empty()
924 ? IntegerValue(0)
925 : LinExprLowerBound(energies[index], *integer_trail);
926
927 const IntegerValue size_min = helper->SizeMin(index);
928 const IntegerValue demand_min =
929 integer_trail->LowerBound(demands[index]);
930 CtEvent event;
931 event.x_start_min = helper->StartMin(index);
932 event.x_size_min = size_min;
933 event.x_end = end_expr;
934 event.x_lp_end = end_expr.LpValue(lp_values);
935 event.y_start_min = IntegerValue(0);
936 event.y_end_max = IntegerValue(capacity_max);
937 if (energy_min > size_min * demand_min) {
938 event.energy_min = energy_min;
939 event.use_energy = true;
940 } else {
941 event.energy_min = size_min * demand_min;
942 }
943 events.push_back(event);
944 }
945 }
946 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
947 /*use_lifting=*/true, model, manager);
948 };
949 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
950 generate_cuts("CumulativeCompletionTime");
951 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
952 generate_cuts("CumulativeCompletionTimeMirror");
953 return true;
954 };
955 return result;
956}
957
959 const std::vector<IntervalVariable>& x_intervals,
960 const std::vector<IntervalVariable>& y_intervals, Model* model) {
961 CutGenerator result;
962
964 new SchedulingConstraintHelper(x_intervals, model);
965 model->TakeOwnership(x_helper);
966
968 new SchedulingConstraintHelper(y_intervals, model);
969 model->TakeOwnership(y_helper);
970 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
971 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
972
973 Trail* trail = model->GetOrCreate<Trail>();
974
975 result.generate_cuts =
976 [trail, x_helper, y_helper, model](
978 LinearConstraintManager* manager) {
979 if (trail->CurrentDecisionLevel() > 0) return true;
980
981 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
982 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
983
984 const int num_boxes = x_helper->NumTasks();
985 std::vector<int> active_boxes;
986 std::vector<IntegerValue> cached_areas(num_boxes);
987 std::vector<Rectangle> cached_rectangles(num_boxes);
988 for (int box = 0; box < num_boxes; ++box) {
989 cached_areas[box] = x_helper->SizeMin(box) * y_helper->SizeMin(box);
990 if (cached_areas[box] == 0) continue;
991 if (!y_helper->IsPresent(box) || !y_helper->IsPresent(box)) continue;
992
993 // TODO(user): It might be possible/better to use some shifted value
994 // here, but for now this code is not in the hot spot, so better be
995 // defensive and only do connected components on really disjoint
996 // boxes.
997 Rectangle& rectangle = cached_rectangles[box];
998 rectangle.x_min = x_helper->StartMin(box);
999 rectangle.x_max = x_helper->EndMax(box);
1000 rectangle.y_min = y_helper->StartMin(box);
1001 rectangle.y_max = y_helper->EndMax(box);
1002
1003 active_boxes.push_back(box);
1004 }
1005
1006 if (active_boxes.size() <= 1) return true;
1007
1008 std::vector<absl::Span<int>> components =
1009 GetOverlappingRectangleComponents(cached_rectangles,
1010 absl::MakeSpan(active_boxes));
1011 for (absl::Span<int> boxes : components) {
1012 if (boxes.size() <= 1) continue;
1013
1014 auto generate_cuts = [&lp_values, model, manager, &boxes,
1015 &cached_areas](
1016 const std::string& cut_name,
1018 SchedulingConstraintHelper* y_helper) {
1019 std::vector<CtEvent> events;
1020
1021 for (const int box : boxes) {
1022 const AffineExpression x_end_expr = x_helper->Ends()[box];
1023 CtEvent event;
1024 event.x_start_min = x_helper->ShiftedStartMin(box);
1025 event.x_size_min = x_helper->SizeMin(box);
1026 event.x_end = x_end_expr;
1027 event.x_lp_end = x_end_expr.LpValue(lp_values);
1028 event.y_start_min = y_helper->ShiftedStartMin(box);
1029 event.y_end_max = y_helper->ShiftedEndMax(box);
1030 event.energy_min =
1031 x_helper->SizeMin(box) * y_helper->SizeMin(box);
1032 events.push_back(event);
1033 }
1034
1035 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
1036 /*use_lifting=*/true, model, manager);
1037 };
1038
1039 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1040 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1041 generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper);
1042 generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper);
1043 if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
1044 if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
1045 generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
1046 generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
1047 }
1048 return true;
1049 };
1050 return result;
1051}
1052
1053} // namespace sat
1054} // 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:887
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
#define DCHECK(condition)
Definition: base/logging.h:885
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 CreateCumulativePrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, IntegerVariable capacity, const std::vector< IntegerVariable > &demands, Model *model)
CutGenerator CreateCumulativeEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, const std::vector< LinearExpression > &energies, Model *model)
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition: integer.h:1501
CutGenerator CreateCumulativeCompletionTimeCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, const std::vector< LinearExpression > &energies, 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
std::function< bool(const absl::StrongVector< IntegerVariable, double > &, LinearConstraintManager *)> GenerateCumulativeEnergyCuts(const std::string &cut_name, SchedulingConstraintHelper *helper, const std::vector< IntegerVariable > &demands, const std::vector< LinearExpression > &energies, AffineExpression capacity, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
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)
CutGenerator CreateCumulativeTimeTableCutGenerator(const std::vector< IntervalVariable > &intervals, const IntegerVariable capacity, const std::vector< IntegerVariable > &demands, Model *model)
CutGenerator CreateNoOverlapEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
double ToDouble(IntegerValue value)
Definition: integer.h:70
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:253
const std::string DebugString() const
Definition: integer.h:259
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