OR-Tools  9.3
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 <cstdlib>
19#include <functional>
20#include <optional>
21#include <string>
22#include <tuple>
23#include <utility>
24#include <vector>
25
26#include "absl/strings/str_cat.h"
27#include "absl/types/span.h"
31#include "ortools/sat/cuts.h"
34#include "ortools/sat/integer.h"
38#include "ortools/sat/model.h"
41
42namespace operations_research {
43namespace sat {
44
45namespace {
46
47// Minimum amount of violation of the cut constraint by the solution. This
48// is needed to avoid numerical issues and adding cuts with minor effect.
49const double kMinCutViolation = 1e-4;
50
51// Returns the lp value of a Literal.
52double GetLiteralLpValue(
53 const Literal lit,
55 const IntegerEncoder* encoder) {
56 const IntegerVariable direct_view = encoder->GetLiteralView(lit);
57 if (direct_view != kNoIntegerVariable) {
58 return lp_values[direct_view];
59 }
60 const IntegerVariable opposite_view = encoder->GetLiteralView(lit.Negated());
61 DCHECK_NE(opposite_view, kNoIntegerVariable);
62 return 1.0 - lp_values[opposite_view];
63}
64
65void AddIntegerVariableFromIntervals(SchedulingConstraintHelper* helper,
66 Model* model,
67 std::vector<IntegerVariable>* vars) {
68 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
69 for (int t = 0; t < helper->NumTasks(); ++t) {
70 if (helper->Starts()[t].var != kNoIntegerVariable) {
71 vars->push_back(helper->Starts()[t].var);
72 }
73 if (helper->Sizes()[t].var != kNoIntegerVariable) {
74 vars->push_back(helper->Sizes()[t].var);
75 }
76 if (helper->Ends()[t].var != kNoIntegerVariable) {
77 vars->push_back(helper->Ends()[t].var);
78 }
79 if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
80 !helper->IsPresent(t)) {
81 const Literal l = helper->PresenceLiteral(t);
82 if (encoder->GetLiteralView(l) == kNoIntegerVariable &&
83 encoder->GetLiteralView(l.Negated()) == kNoIntegerVariable) {
85 }
86 const IntegerVariable direct_view = encoder->GetLiteralView(l);
87 if (direct_view != kNoIntegerVariable) {
88 vars->push_back(direct_view);
89 } else {
90 vars->push_back(encoder->GetLiteralView(l.Negated()));
91 DCHECK_NE(vars->back(), kNoIntegerVariable);
92 }
93 }
94 }
95}
96
97} // namespace
98
100 IntegerValue x_start_min;
101 IntegerValue x_start_max;
102 IntegerValue x_end_min;
103 IntegerValue x_end_max;
105 IntegerValue y_min = IntegerValue(0); // Useful for no_overlap_2d.
106 IntegerValue y_max = IntegerValue(0); // Useful for no_overlap_2d.
108
109 // Energy will be present only if the x_size * y_size could be linearized.
110 std::optional<LinearExpression> energy;
112
113 // Caches for MinOf(x_size), MinOf(y_size), and the LP values of the energy
114 // and the presence literal.
115 IntegerValue x_size_min;
116 IntegerValue y_size_min;
117 double literal_lp = 1.0;
118 double energy_lp = 0.0;
119
120 // Used to minimize the increase on the y axis for rectangles.
121 double y_spread = 0.0;
122
123 // The actual value of the presence literal of the interval(s) is checked
124 // when the event is created. A value of kNoLiteralIndex indicates that either
125 // the interval was not optional, or that its presence literal is true at
126 // level zero.
128
129 // Computes the mandatory minimal overlap of the interval with the time window
130 // [start, end].
131 IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const {
133 end - start}),
134 IntegerValue(0));
135 }
136
137 std::string DebugString() const {
138 return absl::StrCat(
139 "EnergyEvent(x_start_min = ", x_start_min.value(),
140 ", x_start_max = ", x_start_max.value(),
141 ", x_end_min = ", x_end_min.value(),
142 ", x_end_max = ", x_end_max.value(),
143 ", x_size = ", x_size.DebugString(), ", y_min = ", y_min.value(),
144 ", y_max = ", y_max.value(), ", y_size = ", y_size.DebugString(),
145 ", energy = ", energy ? energy.value().DebugString() : "{}",
146 ", presence_literal_index = ", presence_literal_index.value(), ")");
147 }
148};
149
150// Note that we only support to cases:
151// - capacity is non-zero and the y_min/y_max of the events must be zero.
152// - capacity is zero, and the y_min/y_max can be set.
153// This is DCHECKed in the code.
155 const std::string& cut_name,
157 std::vector<EnergyEvent> events, const AffineExpression capacity,
158 bool events_are_2d, Model* model, LinearConstraintManager* manager) {
159 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
160 TopNCuts top_n_cuts(15);
161
162 std::sort(events.begin(), events.end(),
163 [](const EnergyEvent& a, const EnergyEvent& b) {
164 return std::tie(a.x_start_min, a.y_spread, a.x_end_max) <
165 std::tie(b.x_start_min, b.y_spread, b.x_end_max);
166 });
167
168 const double capacity_lp = capacity.LpValue(lp_values);
169
170 // The sum of all energies can be used to stop iterating early.
171 double sum_of_all_energies = 0.0;
172 for (const auto& e : events) {
173 sum_of_all_energies += e.energy_lp;
174 }
175 CapacityProfile capacity_profile;
176
177 IntegerValue processed_start = kMinIntegerValue;
178 for (int i1 = 0; i1 + 1 < events.size(); ++i1) {
179 // We want maximal cuts. For any x_start_min value, we only need to create
180 // cuts starting from the first interval having this x_start_min value.
181 //
182 // Enabling this optimization reduces dramatically the number of generated
183 // cuts in the rectangle_case case. So we only use it in the !events_are_2d
184 // case.
185 if (!events_are_2d) {
186 if (events[i1].x_start_min == processed_start) {
187 continue;
188 } else {
189 processed_start = events[i1].x_start_min;
190 }
191 }
192
193 // For each start time, we will keep the most violated cut generated while
194 // scanning the residual intervals.
195 int max_violation_end_index = -1;
196 double max_relative_violation = 1.0 + kMinCutViolation;
197 IntegerValue max_violation_window_start(0);
198 IntegerValue max_violation_window_end(0);
199 IntegerValue max_violation_y_min(0);
200 IntegerValue max_violation_y_max(0);
201 IntegerValue max_violation_precise_area(0);
202 bool max_violation_use_precise_area = false;
203 std::vector<EnergyEvent> max_violation_lifted_events;
204
205 // Accumulate intervals, areas, energies and check for potential cuts.
206 double energy_lp = 0.0;
207 IntegerValue window_min = kMaxIntegerValue;
208 IntegerValue window_max = kMinIntegerValue;
209 IntegerValue y_min = kMaxIntegerValue;
210 IntegerValue y_max = kMinIntegerValue;
211 capacity_profile.Clear();
212
213 // We sort all tasks (x_start_min(task) >= x_start_min(start_index) by
214 // increasing end max.
215 std::vector<EnergyEvent> residual_events(events.begin() + i1, events.end());
216 std::sort(residual_events.begin(), residual_events.end(),
217 [](const EnergyEvent& a, const EnergyEvent& b) {
218 return std::tie(a.x_end_max, a.y_spread) <
219 std::tie(b.x_end_max, b.y_spread);
220 });
221 // Let's process residual tasks and evaluate the violation of the cut at
222 // each step. We follow the same structure as the cut creation code below.
223 for (int i2 = 0; i2 < residual_events.size(); ++i2) {
224 const EnergyEvent& e = residual_events[i2];
225 energy_lp += e.energy_lp;
226 window_min = std::min(window_min, e.x_start_min);
227 window_max = std::max(window_max, e.x_end_max);
228 y_min = std::min(y_min, e.y_min);
229 y_max = std::max(y_max, e.y_max);
230 if (events_are_2d) {
231 capacity_profile.AddRectangle(e.x_start_min, e.x_end_max, e.y_min,
232 e.y_max);
233 }
234
235 // Dominance rule. If the next interval also fits in
236 // [window_min, window_max], the cut will be stronger with the
237 // next interval/rectangle.
238 if (i2 + 1 < residual_events.size() &&
239 residual_events[i2 + 1].x_start_min >= window_min &&
240 residual_events[i2 + 1].x_end_max <= window_max &&
241 residual_events[i2 + 1].y_min >= y_min &&
242 residual_events[i2 + 1].y_max <= y_max) {
243 continue;
244 }
245
246 // Checks the current area vs the sum of all energies.
247 DCHECK(capacity_lp == 0.0 || y_max == y_min);
248 // we have two cases:
249 // - events_are_2d = false: the area is (window_max - window_min) *
250 // capacity.
251 // Its LP value is (window_max - window_min) * capacity_lp.
252 // - events_are_2d = true: The area is
253 // capacity_profile.GetBoundingArea().
254 // We can compare it to the bounding box area:
255 // (window_max - window_min) * (y_max - y_min).
256 bool use_precise_area = false;
257 IntegerValue precise_area(0);
258 double area_lp = 0.0;
259 if (events_are_2d) {
260 const IntegerValue bbox_area =
261 (window_max - window_min) * (y_max - y_min);
262 precise_area = capacity_profile.GetBoundingArea();
263 use_precise_area = precise_area < bbox_area;
264 area_lp = ToDouble(precise_area);
265 } else {
266 area_lp = capacity_lp * ToDouble(window_max - window_min);
267 }
268
269 if (area_lp >= sum_of_all_energies) {
270 break;
271 }
272
273 // Compute forced contributions from intervals not included in
274 // [window_min..window_max].
275 //
276 // TODO(user): We could precompute possible intervals and store them
277 // by x_start_max, x_end_min to reduce the complexity.
278 //
279 // Note: this is not useful in the no_overlap_2d case. Maybe because of
280 // geometric nature of the overlap w.r.t. strict rectangles. i.e. a
281 // rectangle that can be lifted will most likely increase the y_range.
282 std::vector<EnergyEvent> lifted_events;
283 double lifted_contrib_lp = 0.0;
284
285 // Because of the 2D conflicts, lifted events are unlikely in the
286 // no_overlap_2d case. Let's disable this expensive loop for the time
287 // being.
288 if (!events_are_2d) {
289 const auto check_lifted_cuts = [&lifted_events, window_min, window_max,
290 y_min, y_max, &lifted_contrib_lp,
291 &lp_values](const EnergyEvent& e) {
292 // It should not happen because of the 2 dominance rules above.
293 if (e.x_start_min >= window_min && e.x_end_max <= window_max) {
294 return;
295 }
296
297 // Do not add if it extends the y range.
298 if (e.y_min < y_min || e.y_max > y_max) return;
299
300 // Exit if the interval can be pushed left or right of the window.
301 if (e.x_end_min <= window_min || e.x_start_max >= window_max) return;
302
303 DCHECK_GT(window_max, window_min);
304 DCHECK_GT(e.x_size_min, 0);
305
306 const IntegerValue min_overlap =
307 e.GetMinOverlap(window_min, window_max);
308
309 if (min_overlap <= 0) return;
310
311 lifted_events.push_back(e);
312 double contrib_lp = 0.0;
313 if (e.IsPresent()) {
314 contrib_lp = e.y_size.LpValue(lp_values) * ToDouble(min_overlap);
315 } else {
316 contrib_lp = e.literal_lp * ToDouble(min_overlap * e.y_size_min);
317 }
318 // We know the energy is >= 0.0. Some epsilon in the simplex can make
319 // contrib_lp a small negative number. We can correct this.
320 lifted_contrib_lp += std::max(contrib_lp, 0.0);
321 };
322
323 for (int i3 = 0; i3 < i1; ++i3) {
324 check_lifted_cuts(events[i3]);
325 }
326 for (int i3 = i2 + 1; i3 < residual_events.size(); ++i3) {
327 check_lifted_cuts(residual_events[i3]);
328 }
329 }
330
331 // Compute the violation of the potential cut.
332 const double relative_violation =
333 (energy_lp + lifted_contrib_lp) / area_lp;
334 if (relative_violation > max_relative_violation) {
335 max_violation_end_index = i2;
336 max_relative_violation = relative_violation;
337 max_violation_window_start = window_min;
338 max_violation_window_end = window_max;
339 std::swap(max_violation_lifted_events, lifted_events);
340 max_violation_y_min = y_min;
341 max_violation_y_max = y_max;
342 max_violation_precise_area = precise_area;
343 max_violation_use_precise_area = use_precise_area;
344 }
345 }
346
347 if (max_violation_end_index == -1) continue;
348
349 // A maximal violated cut has been found.
350 bool cut_generated = true;
351 bool has_opt_cuts = false;
352 bool use_lifted_events = false;
353 bool has_quadratic_cuts = false;
354 bool use_energy = false;
355
356 DCHECK(capacity_lp == 0.0 || max_violation_y_max == max_violation_y_min);
357 // Build the cut.
360 events_are_2d ? max_violation_precise_area : IntegerValue(0));
361
362 // Adds the capacity in the disjunctive/cumulative case.
363 if (!events_are_2d) {
364 cut.AddTerm(capacity,
365 max_violation_window_start - max_violation_window_end);
366 }
367
368 for (int i2 = 0; i2 <= max_violation_end_index; ++i2) {
369 const EnergyEvent& e = residual_events[i2];
370 if (e.IsPresent()) {
371 if (e.energy) {
372 // We favor the energy info instead of the McCormick relaxation.
373 cut.AddLinearExpression(e.energy.value());
374 use_energy = true;
375 } else {
376 cut.AddQuadraticLowerBound(e.x_size, e.y_size, integer_trail);
377 has_quadratic_cuts = true;
378 }
379 } else {
380 has_opt_cuts = true;
381 const IntegerValue min_energy =
383 e.energy ? e.energy.value().LevelZeroMin(integer_trail)
384 : IntegerValue(0));
385 if (min_energy > e.x_size_min * e.y_size_min) {
386 use_energy = true;
387 }
389 min_energy)) {
390 cut_generated = false;
391 break;
392 }
393 }
394 }
395
396 for (const EnergyEvent& e : max_violation_lifted_events) {
397 const IntegerValue min_overlap =
398 e.GetMinOverlap(max_violation_window_start, max_violation_window_end);
399 DCHECK_GT(min_overlap, 0);
400 use_lifted_events = true;
401
402 if (e.IsPresent()) {
403 cut.AddTerm(e.y_size, min_overlap);
404 } else {
405 has_opt_cuts = true;
406 if (!cut.AddLiteralTerm(Literal(e.presence_literal_index),
407 min_overlap * e.y_size_min)) {
408 cut_generated = false;
409 break;
410 }
411 }
412 }
413
414 if (cut_generated) {
415 std::string full_name = cut_name;
416 if (has_opt_cuts) full_name.append("_opt");
417 if (has_quadratic_cuts) full_name.append("_quad");
418 if (use_lifted_events) full_name.append("_lifted");
419 if (use_energy) full_name.append("_energy");
420 if (max_violation_use_precise_area) full_name.append("_precise");
421 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
422 }
423 }
424 top_n_cuts.TransferToManager(lp_values, manager);
425}
426
429 const std::vector<AffineExpression>& demands, IntegerTrail* integer_trail,
430 CutGenerator* result) {
431 for (const AffineExpression& demand_expr : demands) {
432 if (!integer_trail->IsFixed(demand_expr)) {
433 result->vars.push_back(demand_expr.var);
434 }
435 }
436 if (!integer_trail->IsFixed(capacity)) {
437 result->vars.push_back(capacity.var);
438 }
439}
440
442 const EnergyEvent& e,
444 IntegerTrail* integer_trail) {
446 if (e.energy) {
447 return e.energy.value().LpValue(lp_values);
448 } else { // demand and size are not fixed.
449 // X * Y >= X * y_min + x_min * Y - x_min * y_min.
450 return ToDouble(e.y_size_min) * e.x_size.LpValue(lp_values) +
451 ToDouble(e.x_size_min) * e.y_size.LpValue(lp_values) -
453 }
454 } else {
455 const IntegerValue min_energy =
457 e.energy ? e.energy.value().LevelZeroMin(integer_trail)
458 : IntegerValue(0));
459 return e.literal_lp * ToDouble(min_energy);
460 }
461}
462
464 const std::vector<IntervalVariable>& intervals,
466 const std::vector<AffineExpression>& demands,
467 const std::vector<LinearExpression>& energies, Model* model) {
468 CutGenerator result;
469
471 new SchedulingConstraintHelper(intervals, model);
472 model->TakeOwnership(helper);
473
474 Trail* trail = model->GetOrCreate<Trail>();
475 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
476 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
477 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
478 AddIntegerVariableFromIntervals(helper, model, &result.vars);
479 for (const LinearExpression& energy : energies) {
480 result.vars.insert(result.vars.end(), energy.vars.begin(),
481 energy.vars.end());
482 }
484
485 result.generate_cuts =
486 [capacity, demands, energies, trail, integer_trail, helper, model,
487 encoder](const absl::StrongVector<IntegerVariable, double>& lp_values,
488 LinearConstraintManager* manager) {
489 if (trail->CurrentDecisionLevel() > 0) return true;
490
491 std::vector<EnergyEvent> events;
492 for (int i = 0; i < helper->NumTasks(); ++i) {
493 if (helper->IsAbsent(i)) continue;
494 if (integer_trail->LevelZeroUpperBound(demands[i]) == 0 ||
495 helper->SizeMin(i) == 0) {
496 continue;
497 }
498
499 EnergyEvent e;
500 e.x_start_min = helper->StartMin(i);
501 e.x_start_max = helper->StartMax(i);
502 e.x_end_min = helper->EndMin(i);
503 e.x_end_max = helper->EndMax(i);
504 e.x_size = helper->Sizes()[i];
505 e.y_size = demands[i];
506 if (ProductIsLinearized(energies[i])) {
507 e.energy = energies[i];
508 }
509 e.x_size_min = helper->SizeMin(i);
510 e.y_size_min = integer_trail->LevelZeroLowerBound(demands[i]);
511 if (!helper->IsPresent(i)) {
513 e.literal_lp = GetLiteralLpValue(Literal(e.presence_literal_index),
514 lp_values, encoder);
515 }
516 e.energy_lp = ComputeEnergyLp(e, lp_values, integer_trail);
517 events.push_back(e);
518 }
519
520 GenerateEnergeticCuts("CumulativeEnergy", lp_values, events, capacity,
521 /*events_are_2d=*/false, model, manager);
522 return true;
523 };
524
525 return result;
526}
527
529 const std::vector<IntervalVariable>& intervals, Model* model) {
530 CutGenerator result;
531
532 Trail* trail = model->GetOrCreate<Trail>();
533 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
535 new SchedulingConstraintHelper(intervals, model);
536 model->TakeOwnership(helper);
537
538 AddIntegerVariableFromIntervals(helper, model, &result.vars);
540
541 // We need to convert AffineExpression to LinearExpression for the energy.
542 std::vector<LinearExpression> sizes;
543 sizes.reserve(intervals.size());
544 for (int i = 0; i < intervals.size(); ++i) {
546 builder.AddTerm(helper->Sizes()[i], IntegerValue(1));
547 sizes.push_back(builder.BuildExpression());
548 }
549
550 result.generate_cuts =
551 [sizes, trail, helper, model, encoder](
553 LinearConstraintManager* manager) {
554 if (trail->CurrentDecisionLevel() > 0) return true;
555
556 std::vector<EnergyEvent> events;
557 for (int i = 0; i < helper->NumTasks(); ++i) {
558 if (helper->IsAbsent(i)) continue;
559 if (helper->SizeMin(i) == 0) {
560 continue;
561 }
562
563 EnergyEvent e;
564 e.x_start_min = helper->StartMin(i);
565 e.x_start_max = helper->StartMax(i);
566 e.x_end_min = helper->EndMin(i);
567 e.x_end_max = helper->EndMax(i);
568 e.x_size = helper->Sizes()[i];
569 e.y_size = IntegerValue(1);
570 e.energy = sizes[i];
571 e.x_size_min = helper->SizeMin(i);
572 e.y_size_min = IntegerValue(1);
573 if (helper->IsPresent(i)) {
574 e.energy_lp = e.energy->LpValue(lp_values);
575 } else {
576 e.presence_literal_index = helper->PresenceLiteral(i).Index();
577 e.literal_lp = GetLiteralLpValue(Literal(e.presence_literal_index),
578 lp_values, encoder);
580 }
581 events.push_back(e);
582 }
583
584 GenerateEnergeticCuts("NoOverlapEnergy", lp_values, events,
585 IntegerValue(1), /*events_are_2d=*/false, model,
586 manager);
587 return true;
588 };
589 return result;
590}
591
593 const std::vector<LinearExpression>& energies, absl::Span<int> rectangles,
594 const std::string& cut_name,
596 IntegerTrail* integer_trail, IntegerEncoder* encoder,
598 SchedulingConstraintHelper* y_helper) {
599 std::vector<EnergyEvent> events;
600 for (const int rect : rectangles) {
601 if (y_helper->SizeMax(rect) == 0 || x_helper->SizeMax(rect) == 0) {
602 continue;
603 }
604
605 EnergyEvent e;
606 e.x_start_min = x_helper->StartMin(rect);
607 e.x_start_max = x_helper->StartMax(rect);
608 e.x_end_min = x_helper->EndMin(rect);
609 e.x_end_max = x_helper->EndMax(rect);
610 e.x_size = x_helper->Sizes()[rect];
611 e.y_min = y_helper->StartMin(rect);
612 e.y_max = y_helper->EndMax(rect);
613 e.y_size = y_helper->Sizes()[rect];
614 if (ProductIsLinearized(energies[rect])) {
615 e.energy = energies[rect];
616 }
618 x_helper->IsPresent(rect)
619 ? (y_helper->IsPresent(rect)
621 : y_helper->PresenceLiteral(rect).Index())
622 : x_helper->PresenceLiteral(rect).Index();
624 e.literal_lp = GetLiteralLpValue(Literal(e.presence_literal_index),
625 lp_values, encoder);
626 }
627 e.x_size_min = x_helper->SizeMin(rect);
628 e.y_size_min = y_helper->SizeMin(rect);
629 e.energy_lp = ComputeEnergyLp(e, lp_values, integer_trail);
630 events.push_back(e);
631 }
632
633 if (events.empty()) return;
634
635 // Compute y_spread.
636 double average_d = 0.0;
637 for (const auto& e : events) {
638 average_d += ToDouble(e.y_min + e.y_max);
639 }
640 const double average = average_d / 2.0 / static_cast<double>(events.size());
641 for (auto& e : events) {
642 e.y_spread = std::abs(ToDouble(e.y_max) - average) +
643 std::abs(average - ToDouble(e.y_min));
644 }
645 GenerateEnergeticCuts(cut_name, lp_values, events, IntegerValue(0),
646 /*events_are_2d=*/true, model, manager);
647}
648
650 const std::vector<IntervalVariable>& x_intervals,
651 const std::vector<IntervalVariable>& y_intervals, Model* model) {
652 CutGenerator result;
653 IntervalsRepository* intervals_repository =
654 model->GetOrCreate<IntervalsRepository>();
655 const int num_rectangles = x_intervals.size();
656
657 std::vector<LinearExpression> energies;
658 std::vector<AffineExpression> x_sizes;
659 std::vector<AffineExpression> y_sizes;
660 for (int i = 0; i < num_rectangles; ++i) {
661 x_sizes.push_back(intervals_repository->Size(x_intervals[i]));
662 y_sizes.push_back(intervals_repository->Size(y_intervals[i]));
663 }
664 LinearizeInnerProduct(x_sizes, y_sizes, model, &energies);
665
667 new SchedulingConstraintHelper(x_intervals, model);
668 model->TakeOwnership(x_helper);
669
671 new SchedulingConstraintHelper(y_intervals, model);
672 model->TakeOwnership(y_helper);
673
674 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
675 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
677
678 Trail* trail = model->GetOrCreate<Trail>();
679 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
680 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
681
682 result.generate_cuts =
683 [integer_trail, trail, encoder, x_helper, y_helper, model, energies](
685 LinearConstraintManager* manager) {
686 if (trail->CurrentDecisionLevel() > 0) return true;
687
688 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
689 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
690
691 const int num_rectangles = x_helper->NumTasks();
692 std::vector<int> active_rectangles;
693 std::vector<Rectangle> cached_rectangles(num_rectangles);
694 for (int rect = 0; rect < num_rectangles; ++rect) {
695 if (y_helper->IsAbsent(rect) || y_helper->IsAbsent(rect)) continue;
696 // We do not consider rectangles controlled by 2 different unassigned
697 // enforcement literals.
698 if (!x_helper->IsPresent(rect) && !y_helper->IsPresent(rect) &&
699 x_helper->PresenceLiteral(rect) !=
700 y_helper->PresenceLiteral(rect)) {
701 continue;
702 }
703
704 // TODO(user): It might be possible/better to use some shifted value
705 // here, but for now this code is not in the hot spot, so better be
706 // defensive and only do connected components on really disjoint
707 // rectangles.
708 Rectangle& rectangle = cached_rectangles[rect];
709 rectangle.x_min = x_helper->StartMin(rect);
710 rectangle.x_max = x_helper->EndMax(rect);
711 rectangle.y_min = y_helper->StartMin(rect);
712 rectangle.y_max = y_helper->EndMax(rect);
713
714 active_rectangles.push_back(rect);
715 }
716
717 if (active_rectangles.size() <= 1) return true;
718
719 std::vector<absl::Span<int>> components =
721 cached_rectangles, absl::MakeSpan(active_rectangles));
722
723 // Forward pass. No need to do a backward pass.
724 for (absl::Span<int> rectangles : components) {
725 if (rectangles.size() <= 1) continue;
726
728 energies, rectangles, "NoOverlap2dXEnergy", lp_values, model,
729 integer_trail, encoder, manager, x_helper, y_helper);
731 energies, rectangles, "NoOverlap2dYEnergy", lp_values, model,
732 integer_trail, encoder, manager, y_helper, x_helper);
733 }
734
735 return true;
736 };
737 return result;
738}
739
741 const std::vector<IntervalVariable>& intervals,
743 const std::vector<AffineExpression>& demands, Model* model) {
744 CutGenerator result;
745
747 new SchedulingConstraintHelper(intervals, model);
748 model->TakeOwnership(helper);
749
750 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
751 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
752
753 AddIntegerVariableFromIntervals(helper, model, &result.vars);
755
756 struct TimeTableEvent {
757 int interval_index;
758 IntegerValue time;
759 bool positive;
761 };
762
763 Trail* trail = model->GetOrCreate<Trail>();
764
765 result.generate_cuts =
766 [helper, capacity, demands, trail, integer_trail, model](
768 LinearConstraintManager* manager) {
769 if (trail->CurrentDecisionLevel() > 0) return true;
770
771 std::vector<TimeTableEvent> events;
772 // Iterate through the intervals. If start_max < end_min, the demand
773 // is mandatory.
774 for (int i = 0; i < helper->NumTasks(); ++i) {
775 if (helper->IsAbsent(i)) continue;
776
777 const IntegerValue start_max = helper->StartMax(i);
778 const IntegerValue end_min = helper->EndMin(i);
779
780 if (start_max >= end_min) continue;
781
782 TimeTableEvent e1;
783 e1.interval_index = i;
784 e1.time = start_max;
785 e1.demand = demands[i];
786 e1.positive = true;
787
788 TimeTableEvent e2 = e1;
789 e2.time = end_min;
790 e2.positive = false;
791 events.push_back(e1);
792 events.push_back(e2);
793 }
794
795 // Sort events by time.
796 // It is also important that all positive event with the same time as
797 // negative events appear after for the correctness of the algo below.
798 std::sort(events.begin(), events.end(),
799 [](const TimeTableEvent& i, const TimeTableEvent& j) {
800 if (i.time == j.time) {
801 if (i.positive == j.positive) {
802 return i.interval_index < j.interval_index;
803 }
804 return !i.positive;
805 }
806 return i.time < j.time;
807 });
808
809 std::vector<TimeTableEvent> cut_events;
810 bool added_positive_event = false;
811 for (const TimeTableEvent& e : events) {
812 if (e.positive) {
813 added_positive_event = true;
814 cut_events.push_back(e);
815 continue;
816 }
817 if (added_positive_event && cut_events.size() > 1) {
818 // Create cut.
819 bool cut_generated = true;
821 IntegerValue(0));
822 cut.AddTerm(capacity, IntegerValue(-1));
823 for (const TimeTableEvent& cut_event : cut_events) {
824 if (helper->IsPresent(cut_event.interval_index)) {
825 cut.AddTerm(cut_event.demand, IntegerValue(1));
826 } else {
827 cut_generated &= cut.AddLiteralTerm(
828 helper->PresenceLiteral(cut_event.interval_index),
829 integer_trail->LowerBound(cut_event.demand));
830 if (!cut_generated) break;
831 }
832 }
833 if (cut_generated) {
834 // Violation of the cut is checked by AddCut so we don't check
835 // it here.
836 manager->AddCut(cut.Build(), "CumulativeTimeTable", lp_values);
837 }
838 }
839 // Remove the event.
840 int new_size = 0;
841 for (int i = 0; i < cut_events.size(); ++i) {
842 if (cut_events[i].interval_index == e.interval_index) {
843 continue;
844 }
845 cut_events[new_size] = cut_events[i];
846 new_size++;
847 }
848 cut_events.resize(new_size);
849 added_positive_event = false;
850 }
851 return true;
852 };
853 return result;
854}
855
856// Cached Information about one interval.
858 IntegerValue start_min;
859 IntegerValue start_max;
861 IntegerValue end_min;
862 IntegerValue end_max;
864 IntegerValue demand_min;
865};
866
868 const std::string& cut_name,
870 std::vector<PrecedenceEvent> events, IntegerValue capacity_max,
872 const int num_events = events.size();
873 if (num_events <= 1) return;
874
875 std::sort(events.begin(), events.end(),
876 [](const PrecedenceEvent& e1, const PrecedenceEvent& e2) {
877 return e1.start_min < e2.start_min ||
878 (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
879 });
880
881 const double tolerance = 1e-4;
882
883 for (int i = 0; i + 1 < num_events; ++i) {
884 const PrecedenceEvent& e1 = events[i];
885 for (int j = i + 1; j < num_events; ++j) {
886 const PrecedenceEvent& e2 = events[j];
887 if (e2.start_min >= e1.end_max) break; // Break out of the index2 loop.
888
889 // Encode only the interesting pairs.
890 if (e1.demand_min + e2.demand_min <= capacity_max) continue;
891
892 const bool interval_1_can_precede_2 = e1.end_min <= e2.start_max;
893 const bool interval_2_can_precede_1 = e2.end_min <= e1.start_max;
894
895 if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
896 e1.end.LpValue(lp_values) >=
897 e2.start.LpValue(lp_values) + tolerance) {
898 // interval1.end <= interval2.start
899 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
900 cut.AddTerm(e1.end, IntegerValue(1));
901 cut.AddTerm(e2.start, IntegerValue(-1));
902 } else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
903 e2.end.LpValue(lp_values) >=
904 e1.start.LpValue(lp_values) + tolerance) {
905 // interval2.end <= interval1.start
906 LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
907 cut.AddTerm(e2.end, IntegerValue(1));
908 cut.AddTerm(e1.start, IntegerValue(-1));
909 manager->AddCut(cut.Build(), cut_name, lp_values);
910 }
911 }
912 }
913}
914
916 const std::vector<IntervalVariable>& intervals,
918 const std::vector<AffineExpression>& demands, Model* model) {
919 CutGenerator result;
920
922 new SchedulingConstraintHelper(intervals, model);
923 model->TakeOwnership(helper);
924
925 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
926 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
927
928 AddIntegerVariableFromIntervals(helper, model, &result.vars);
930
931 Trail* trail = model->GetOrCreate<Trail>();
932
933 result.generate_cuts =
934 [trail, integer_trail, helper, demands, capacity, model](
936 LinearConstraintManager* manager) {
937 if (trail->CurrentDecisionLevel() > 0) return true;
938
939 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
940 std::vector<PrecedenceEvent> events;
941 for (int t = 0; t < helper->NumTasks(); ++t) {
942 if (!helper->IsPresent(t)) continue;
943 PrecedenceEvent event;
944 event.start_min = helper->StartMin(t);
945 event.start_max = helper->StartMax(t);
946 event.start = helper->Starts()[t];
947 event.end_min = helper->EndMin(t);
948 event.end_max = helper->EndMax(t);
949 event.end = helper->Ends()[t];
950 event.demand_min = integer_trail->LowerBound(demands[t]);
951 events.push_back(event);
952 }
953 GeneratePrecedenceCuts("CumulativePrecedence", lp_values,
954 std::move(events), capacity_max, model, manager);
955 return true;
956 };
957 return result;
958}
959
961 const std::vector<IntervalVariable>& intervals, Model* model) {
962 CutGenerator result;
963
965 new SchedulingConstraintHelper(intervals, model);
966 model->TakeOwnership(helper);
967
968 AddIntegerVariableFromIntervals(helper, model, &result.vars);
970
971 Trail* trail = model->GetOrCreate<Trail>();
972
973 result.generate_cuts =
974 [trail, helper, model](
976 LinearConstraintManager* manager) {
977 if (trail->CurrentDecisionLevel() > 0) return true;
978
979 std::vector<PrecedenceEvent> events;
980 for (int t = 0; t < helper->NumTasks(); ++t) {
981 if (!helper->IsPresent(t)) continue;
982 PrecedenceEvent event;
983 event.start_min = helper->StartMin(t);
984 event.start_max = helper->StartMax(t);
985 event.start = helper->Starts()[t];
986 event.end_min = helper->EndMin(t);
987 event.end_max = helper->EndMax(t);
988 event.end = helper->Ends()[t];
989 event.demand_min = IntegerValue(1);
990 events.push_back(event);
991 }
992 GeneratePrecedenceCuts("NoOverlapPrecedence", lp_values,
993 std::move(events), IntegerValue(1), model,
994 manager);
995 return true;
996 };
997
998 return result;
999}
1000
1001// Stores the event for a rectangle along the two axis x and y.
1002// For a no_overlap constraint, y is always of size 1 between 0 and 1.
1003// For a cumulative constraint, y is the demand that must be between 0 and
1004// capacity_max.
1005// For a no_overlap_2d constraint, y the other dimension of the rect.
1006struct CtEvent {
1007 // The start min of the x interval.
1008 IntegerValue x_start_min;
1009
1010 // The size min of the x interval.
1011 IntegerValue x_size_min;
1012
1013 // The end of the x interval.
1015
1016 // The lp value of the end of the x interval.
1017 double x_lp_end;
1018
1019 // The start min of the y interval.
1020 IntegerValue y_start_min;
1021
1022 // The end max of the y interval.
1023 IntegerValue y_end_max;
1024
1025 // The min energy of the task (this is always larger or equal to x_size_min *
1026 // y_size_min).
1027 IntegerValue energy_min;
1028
1029 // Indicates if the events used the optional energy information from the
1030 // model.
1031 bool use_energy = false;
1032
1033 // Indicates if the cut is lifted, that is if it includes tasks that are not
1034 // strictly contained in the current time window.
1035 bool lifted = false;
1036
1037 std::string DebugString() const {
1038 return absl::StrCat("CtEvent(x_end = ", x_end.DebugString(),
1039 ", x_start_min = ", x_start_min.value(),
1040 ", x_size_min = ", x_size_min.value(),
1041 ", x_lp_end = ", x_lp_end,
1042 ", y_start_min = ", y_start_min.value(),
1043 ", y_end_max = ", y_end_max.value(),
1044 ", energy_min = ", energy_min.value(),
1045 ", use_energy = ", use_energy, ", lifted = ", lifted);
1046 }
1047};
1048
1049// We generate the cut from the Smith's rule from:
1050// M. Queyranne, Structure of a simple scheduling polyhedron,
1051// Mathematical Programming 58 (1993), 263–285
1052//
1053// The original cut is:
1054// sum(end_min_i * duration_min_i) >=
1055// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
1056// We strenghten this cuts by noticing that if all tasks starts after S,
1057// then replacing end_min_i by (end_min_i - S) is still valid.
1058//
1059// A second difference is that we look at a set of intervals starting
1060// after a given start_min, sorted by relative (end_lp - start_min).
1062 const std::string& cut_name,
1064 std::vector<CtEvent> events, bool use_lifting, Model* model,
1065 LinearConstraintManager* manager) {
1066 TopNCuts top_n_cuts(15);
1067
1068 // Sort by start min to bucketize by start_min.
1069 std::sort(events.begin(), events.end(),
1070 [](const CtEvent& e1, const CtEvent& e2) {
1071 return e1.x_start_min < e2.x_start_min;
1072 });
1073 for (int start = 0; start + 1 < events.size(); ++start) {
1074 // Skip to the next start_min value.
1075 if (start > 0 &&
1076 events[start].x_start_min == events[start - 1].x_start_min) {
1077 continue;
1078 }
1079
1080 const IntegerValue sequence_start_min = events[start].x_start_min;
1081 std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
1082
1083 // We look at event that start before sequence_start_min, but are forced
1084 // to cross this time point. In that case, we replace this event by a
1085 // truncated event starting at sequence_start_min. To do this, we reduce
1086 // the size_min, align the start_min with the sequence_start_min, and
1087 // scale the energy down accordingly.
1088 if (use_lifting) {
1089 for (int before = 0; before < start; ++before) {
1090 if (events[before].x_start_min + events[before].x_size_min >
1091 sequence_start_min) {
1092 // Build the vector of energies as the vector of sizes.
1093 CtEvent event = events[before]; // Copy.
1094 event.lifted = true;
1095 const IntegerValue old_size_min = event.x_size_min;
1096 event.x_size_min =
1097 event.x_size_min + event.x_start_min - sequence_start_min;
1098 event.x_start_min = sequence_start_min;
1099 // We can rescale the energy min correctly.
1100 //
1101 // Let's take the example of a rectangle of size 2 * 20 that overlaps
1102 // sequence start min by 1, and that can rotate by 90 degrees.
1103 // The energy min is 40, size min is 2, size_max is 20.
1104 // If the rectangle is horizontal, the lifted energy is:
1105 // (20 - 1) * 2 = 38
1106 // If the rectangle is vertical, the lifted energy is:
1107 // (2 - 1) * 20 = 20
1108 // The min of the two is always reached when size = size_min.
1109 event.energy_min = event.energy_min * event.x_size_min / old_size_min;
1110 residual_tasks.push_back(event);
1111 }
1112 }
1113 }
1114
1115 std::sort(residual_tasks.begin(), residual_tasks.end(),
1116 [](const CtEvent& e1, const CtEvent& e2) {
1117 return e1.x_lp_end < e2.x_lp_end;
1118 });
1119
1120 int best_end = -1;
1121 double best_efficacy = 0.01;
1122 IntegerValue best_min_contrib(0);
1123 IntegerValue sum_duration(0);
1124 IntegerValue sum_square_duration(0);
1125 IntegerValue best_size_divisor(0);
1126 double unscaled_lp_contrib = 0;
1127 IntegerValue current_start_min(kMaxIntegerValue);
1128 IntegerValue y_start_min = kMaxIntegerValue;
1129 IntegerValue y_end_max = kMinIntegerValue;
1130
1131 for (int i = 0; i < residual_tasks.size(); ++i) {
1132 const CtEvent& event = residual_tasks[i];
1133 DCHECK_GE(event.x_start_min, sequence_start_min);
1134 const IntegerValue energy = event.energy_min;
1135 sum_duration += energy;
1136 sum_square_duration += energy * energy;
1137 unscaled_lp_contrib += event.x_lp_end * ToDouble(energy);
1138 current_start_min = std::min(current_start_min, event.x_start_min);
1139 y_start_min = std::min(y_start_min, event.y_start_min);
1140 y_end_max = std::max(y_end_max, event.y_end_max);
1141
1142 const IntegerValue size_divisor = y_end_max - y_start_min;
1143
1144 // We compute the cuts with all the sizes actually equal to
1145 // size_min * demand_min / size_divisor
1146 // but to keep the computation in the integer domain, we multiply by
1147 // size_divisor where needed instead.
1148 const IntegerValue min_contrib =
1149 (sum_duration * sum_duration + sum_square_duration) / 2 +
1150 current_start_min * sum_duration * size_divisor;
1151 const double efficacy = (ToDouble(min_contrib) -
1152 unscaled_lp_contrib * ToDouble(size_divisor)) /
1153 std::sqrt(ToDouble(sum_square_duration));
1154 // TODO(user): Check overflow and ignore if too big.
1155 if (efficacy > best_efficacy) {
1156 best_efficacy = efficacy;
1157 best_end = i;
1158 best_min_contrib = min_contrib;
1159 best_size_divisor = size_divisor;
1160 }
1161 }
1162 if (best_end != -1) {
1163 LinearConstraintBuilder cut(model, best_min_contrib, kMaxIntegerValue);
1164 bool is_lifted = false;
1165 bool use_energy = false;
1166 for (int i = 0; i <= best_end; ++i) {
1167 const CtEvent& event = residual_tasks[i];
1168 is_lifted |= event.lifted;
1169 use_energy |= event.use_energy;
1170 cut.AddTerm(event.x_end, event.energy_min * best_size_divisor);
1171 }
1172 std::string full_name = cut_name;
1173 if (is_lifted) full_name.append("_lifted");
1174 if (use_energy) full_name.append("_energy");
1175 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
1176 }
1177 }
1178 top_n_cuts.TransferToManager(lp_values, manager);
1179}
1180
1182 const std::vector<IntervalVariable>& intervals, Model* model) {
1183 CutGenerator result;
1184
1186 new SchedulingConstraintHelper(intervals, model);
1187 model->TakeOwnership(helper);
1188
1189 AddIntegerVariableFromIntervals(helper, model, &result.vars);
1191
1192 Trail* trail = model->GetOrCreate<Trail>();
1193
1194 result.generate_cuts =
1195 [trail, helper, model](
1197 LinearConstraintManager* manager) {
1198 if (trail->CurrentDecisionLevel() > 0) return true;
1199
1200 auto generate_cuts = [&lp_values, model, manager,
1201 helper](const std::string& cut_name) {
1202 std::vector<CtEvent> events;
1203 for (int index = 0; index < helper->NumTasks(); ++index) {
1204 if (!helper->IsPresent(index)) continue;
1205 const IntegerValue size_min = helper->SizeMin(index);
1206 if (size_min > 0) {
1207 const AffineExpression end_expr = helper->Ends()[index];
1208 CtEvent event;
1209 event.x_start_min = helper->StartMin(index);
1210 event.x_size_min = size_min;
1211 event.x_end = end_expr;
1212 event.x_lp_end = end_expr.LpValue(lp_values);
1213 event.y_start_min = IntegerValue(0);
1214 event.y_end_max = IntegerValue(1);
1215 event.energy_min = size_min;
1216 events.push_back(event);
1217 }
1218 }
1219 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
1220 /*use_lifting=*/false, model, manager);
1221 };
1222 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1223 generate_cuts("NoOverlapCompletionTime");
1224 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
1225 generate_cuts("NoOverlapCompletionTimeMirror");
1226 return true;
1227 };
1228 return result;
1229}
1230
1232 const std::vector<IntervalVariable>& intervals,
1234 const std::vector<AffineExpression>& demands,
1235 const std::vector<LinearExpression>& energies, Model* model) {
1236 CutGenerator result;
1237
1239 new SchedulingConstraintHelper(intervals, model);
1240 model->TakeOwnership(helper);
1241
1242 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1243 AppendVariablesToCumulativeCut(capacity, demands, integer_trail, &result);
1244
1245 AddIntegerVariableFromIntervals(helper, model, &result.vars);
1247
1248 Trail* trail = model->GetOrCreate<Trail>();
1249
1250 result.generate_cuts =
1251 [trail, integer_trail, helper, demands, energies, capacity, model](
1253 LinearConstraintManager* manager) {
1254 if (trail->CurrentDecisionLevel() > 0) return true;
1255
1256 const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
1257 auto generate_cuts = [&lp_values, model, manager, helper, capacity_max,
1258 integer_trail, &demands,
1259 &energies](const std::string& cut_name) {
1260 std::vector<CtEvent> events;
1261 for (int index = 0; index < helper->NumTasks(); ++index) {
1262 if (!helper->IsPresent(index)) continue;
1263 if (helper->SizeMin(index) > 0 &&
1264 integer_trail->LowerBound(demands[index]) > 0) {
1265 const AffineExpression end_expr = helper->Ends()[index];
1266 const IntegerValue size_min = helper->SizeMin(index);
1267 const IntegerValue demand_min =
1268 integer_trail->LowerBound(demands[index]);
1269
1270 CtEvent event;
1271 event.x_start_min = helper->StartMin(index);
1272 event.x_size_min = size_min;
1273 event.x_end = end_expr;
1274 event.x_lp_end = end_expr.LpValue(lp_values);
1275 event.y_start_min = IntegerValue(0);
1276 event.y_end_max = IntegerValue(capacity_max);
1277 event.energy_min = size_min * demand_min;
1278 // TODO(user): Investigate and re-enable a correct version.
1279 if (/*DISABLES_CODE*/ (false) &&
1280 ProductIsLinearized(energies[index])) {
1281 const IntegerValue linearized_energy =
1282 LinExprLowerBound(energies[index], *integer_trail);
1283 if (linearized_energy > event.energy_min) {
1284 event.energy_min = linearized_energy;
1285 event.use_energy = true;
1286 }
1287 }
1288 events.push_back(event);
1289 }
1290 }
1291 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
1292 /*use_lifting=*/true, model, manager);
1293 };
1294 if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1295 generate_cuts("CumulativeCompletionTime");
1296 if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
1297 generate_cuts("CumulativeCompletionTimeMirror");
1298 return true;
1299 };
1300 return result;
1301}
1302
1304 const std::vector<IntervalVariable>& x_intervals,
1305 const std::vector<IntervalVariable>& y_intervals, Model* model) {
1306 CutGenerator result;
1307
1308 SchedulingConstraintHelper* x_helper =
1309 new SchedulingConstraintHelper(x_intervals, model);
1310 model->TakeOwnership(x_helper);
1311
1312 SchedulingConstraintHelper* y_helper =
1313 new SchedulingConstraintHelper(y_intervals, model);
1314 model->TakeOwnership(y_helper);
1315
1316 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
1317 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
1319
1320 Trail* trail = model->GetOrCreate<Trail>();
1321
1322 result.generate_cuts =
1323 [trail, x_helper, y_helper, model](
1325 LinearConstraintManager* manager) {
1326 if (trail->CurrentDecisionLevel() > 0) return true;
1327
1328 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1329 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1330
1331 const int num_rectangles = x_helper->NumTasks();
1332 std::vector<int> active_rectangles;
1333 std::vector<IntegerValue> cached_areas(num_rectangles);
1334 std::vector<Rectangle> cached_rectangles(num_rectangles);
1335 for (int rect = 0; rect < num_rectangles; ++rect) {
1336 if (!y_helper->IsPresent(rect) || !y_helper->IsPresent(rect))
1337 continue;
1338
1339 cached_areas[rect] =
1340 x_helper->SizeMin(rect) * y_helper->SizeMin(rect);
1341 if (cached_areas[rect] == 0) continue;
1342
1343 // TODO(user): It might be possible/better to use some shifted value
1344 // here, but for now this code is not in the hot spot, so better be
1345 // defensive and only do connected components on really disjoint
1346 // rectangles.
1347 Rectangle& rectangle = cached_rectangles[rect];
1348 rectangle.x_min = x_helper->StartMin(rect);
1349 rectangle.x_max = x_helper->EndMax(rect);
1350 rectangle.y_min = y_helper->StartMin(rect);
1351 rectangle.y_max = y_helper->EndMax(rect);
1352
1353 active_rectangles.push_back(rect);
1354 }
1355
1356 if (active_rectangles.size() <= 1) return true;
1357
1358 std::vector<absl::Span<int>> components =
1360 cached_rectangles, absl::MakeSpan(active_rectangles));
1361 for (absl::Span<int> rectangles : components) {
1362 if (rectangles.size() <= 1) continue;
1363
1364 auto generate_cuts = [&lp_values, model, manager, &rectangles,
1365 &cached_areas](
1366 const std::string& cut_name,
1368 SchedulingConstraintHelper* y_helper) {
1369 std::vector<CtEvent> events;
1370
1371 for (const int rect : rectangles) {
1372 const AffineExpression x_end_expr = x_helper->Ends()[rect];
1373 CtEvent event;
1374 event.x_start_min = x_helper->ShiftedStartMin(rect);
1375 event.x_size_min = x_helper->SizeMin(rect);
1376 event.x_end = x_end_expr;
1377 event.x_lp_end = x_end_expr.LpValue(lp_values);
1378 event.y_start_min = y_helper->ShiftedStartMin(rect);
1379 event.y_end_max = y_helper->ShiftedEndMax(rect);
1380 event.energy_min =
1381 x_helper->SizeMin(rect) * y_helper->SizeMin(rect);
1382 events.push_back(event);
1383 }
1384
1385 GenerateCompletionTimeCuts(cut_name, lp_values, std::move(events),
1386 /*use_lifting=*/true, model, manager);
1387 };
1388
1389 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1390 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1391 generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper);
1392 generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper);
1393 if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
1394 if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
1395 generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
1396 generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
1397 }
1398 return true;
1399 };
1400 return result;
1401}
1402
1403} // namespace sat
1404} // 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:892
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:896
#define DCHECK(condition)
Definition: base/logging.h:890
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
Definition: diffn_util.cc:495
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1453
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1449
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1445
AffineExpression Size(IntervalVariable i) const
Definition: intervals.h:97
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="")
LiteralIndex Index() const
Definition: sat_base.h:87
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:339
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:341
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:307
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:340
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
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
static double ToDouble(double f)
Definition: lp_types.h:69
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
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:1640
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)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:40
void LinearizeInnerProduct(const std::vector< AffineExpression > &left, const std::vector< AffineExpression > &right, Model *model, std::vector< LinearExpression > *energies)
void GenerateNoOverlap2dEnergyCut(const std::vector< LinearExpression > &energies, absl::Span< int > rectangles, const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model, IntegerTrail *integer_trail, IntegerEncoder *encoder, LinearConstraintManager *manager, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AppendVariablesToCumulativeCut(const AffineExpression &capacity, const std::vector< AffineExpression > &demands, IntegerTrail *integer_trail, CutGenerator *result)
double ComputeEnergyLp(const EnergyEvent &e, const absl::StrongVector< IntegerVariable, double > &lp_values, IntegerTrail *integer_trail)
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)
bool ProductIsLinearized(const LinearExpression &expr)
void GenerateEnergeticCuts(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, const AffineExpression capacity, bool events_are_2d, Model *model, LinearConstraintManager *manager)
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:77
Collection of objects used to extend the Constraint Solver library.
int64_t demand
Definition: resource.cc:125
int64_t energy
Definition: resource.cc:354
int64_t time
Definition: resource.cc:1693
int64_t capacity
Rev< int64_t > start_max
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:278
const std::string DebugString() const
Definition: integer.h:284
std::vector< IntegerVariable > vars
Definition: cuts.h:48
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:52
std::optional< LinearExpression > energy
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const