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"
28 #include "ortools/base/stl_util.h"
30 #include "ortools/sat/diffn_util.h"
32 #include "ortools/sat/integer.h"
33 #include "ortools/sat/intervals.h"
36 #include "ortools/sat/sat_base.h"
37 #include "ortools/sat/util.h"
39 
40 namespace operations_research {
41 namespace sat {
42 
43 namespace {
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.
47 const double kMinCutViolation = 1e-4;
48 
49 // Returns the lp value of a Literal.
50 double 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 
63 void 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 
97 struct EnergyEvent {
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 {
129  return std::max(std::min({x_end_min - start, end - x_start_max, x_size_min,
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 
382  const AffineExpression& capacity,
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,
397  const AffineExpression& capacity,
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)) {
442  e.presence_literal_index = helper->PresenceLiteral(i).Index();
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 {
458  e.presence_literal_index = helper->PresenceLiteral(i).Index();
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 
640  SchedulingConstraintHelper* x_helper =
641  new SchedulingConstraintHelper(x_intervals, model);
642  model->TakeOwnership(x_helper);
643 
644  SchedulingConstraintHelper* y_helper =
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,
716  const AffineExpression& capacity,
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,
845  Model* model, LinearConstraintManager* manager) {
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,
891  const AffineExpression& capacity,
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.
980 struct 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 
1159  SchedulingConstraintHelper* helper =
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,
1207  const AffineExpression& capacity,
1208  const std::vector<AffineExpression>& demands,
1209  const std::vector<LinearExpression>& energies, Model* model) {
1210  CutGenerator result;
1211 
1212  SchedulingConstraintHelper* helper =
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,
1341  SchedulingConstraintHelper* x_helper,
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
CutGenerator CreateNoOverlapEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
int64_t min
Definition: alldiff_cst.cc:139
IntegerValue LinExprLowerBound(const LinearExpression &expr, const IntegerTrail &integer_trail)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
AffineExpression Size(IntervalVariable i) const
Definition: intervals.h:94
std::vector< IntegerVariable > vars
Definition: cuts.h:43
LiteralIndex Index() const
Definition: sat_base.h:86
void AddLinearExpression(const LinearExpression &expr)
GRBmodel * model
void AddTerm(IntegerVariable var, IntegerValue coeff)
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:272
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition: integer.h:1630
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:895
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateNoOverlap2dEnergyCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
int64_t b
double ToDouble(IntegerValue value)
Definition: integer.h:71
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)
int64_t max
Definition: alldiff_cst.cc:140
const std::string DebugString() const
Definition: integer.h:278
Rev< int64_t > start_max
IntegerValue MinOverlap(IntegerValue start, IntegerValue end) const
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:891
Rev< int64_t > end_min
int64_t demand
Definition: resource.cc:125
int64_t energy
Definition: resource.cc:354
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
int64_t capacity
int index
Definition: pack.cc:509
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:894
CutGenerator CreateNoOverlapPrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:26
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)
#define DCHECK(condition)
Definition: base/logging.h:889
void LinearizeInnerProduct(const std::vector< AffineExpression > &left, const std::vector< AffineExpression > &right, Model *model, std::vector< LinearExpression > *energies)
CutGenerator CreateCumulativeTimeTableCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, 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)
CutGenerator CreateCumulativeEnergyCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, const std::vector< LinearExpression > &energies, Model *model)
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void AppendVariablesToCumulativeCut(const AffineExpression &capacity, const std::vector< AffineExpression > &demands, IntegerTrail *integer_trail, CutGenerator *result)
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 CreateCumulativeCompletionTimeCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, const std::vector< LinearExpression > &energies, Model *model)
Collection of objects used to extend the Constraint Solver library.
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
int64_t time
Definition: resource.cc:1691
const LiteralIndex kNoLiteralIndex(-1)
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:47
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(const std::vector< IntervalVariable > &intervals, Model *model)
std::optional< LinearExpression > energy
bool ProductIsLinearized(const LinearExpression &expr)
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1443
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:338
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail)
int64_t a
CutGenerator CreateCumulativePrecedenceCutGenerator(const std::vector< IntervalVariable > &intervals, const AffineExpression &capacity, const std::vector< AffineExpression > &demands, Model *model)