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