OR-Tools  9.6
scheduling_cuts.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdlib>
19 #include <functional>
20 #include <limits>
21 #include <numeric>
22 #include <optional>
23 #include <string>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27 
28 #include "absl/log/check.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_join.h"
31 #include "absl/types/span.h"
32 #include "ortools/base/logging.h"
33 #include "ortools/base/stl_util.h"
35 #include "ortools/sat/cuts.h"
36 #include "ortools/sat/diffn_util.h"
38 #include "ortools/sat/integer.h"
39 #include "ortools/sat/intervals.h"
42 #include "ortools/sat/model.h"
43 #include "ortools/sat/sat_base.h"
44 #include "ortools/sat/util.h"
46 
47 namespace operations_research {
48 namespace sat {
49 
50 namespace {
51 
52 // Minimum amount of violation of the cut constraint by the solution. This
53 // is needed to avoid numerical issues and adding cuts with minor effect.
54 const double kMinCutViolation = 1e-4;
55 
56 void AddIntegerVariableFromIntervals(SchedulingConstraintHelper* helper,
57  Model* model,
58  std::vector<IntegerVariable>* vars) {
59  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
60  for (int t = 0; t < helper->NumTasks(); ++t) {
61  if (helper->Starts()[t].var != kNoIntegerVariable) {
62  vars->push_back(helper->Starts()[t].var);
63  }
64  if (helper->Sizes()[t].var != kNoIntegerVariable) {
65  vars->push_back(helper->Sizes()[t].var);
66  }
67  if (helper->Ends()[t].var != kNoIntegerVariable) {
68  vars->push_back(helper->Ends()[t].var);
69  }
70  if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
71  !helper->IsPresent(t)) {
72  const Literal l = helper->PresenceLiteral(t);
73  IntegerVariable view = kNoIntegerVariable;
74  if (!encoder->LiteralOrNegationHasView(l, &view)) {
75  view = model->Add(NewIntegerVariableFromLiteral(l));
76  }
77  vars->push_back(view);
78  }
79  }
80 }
81 
82 } // namespace
83 
85  : x_start_min(x_helper->StartMin(t)),
86  x_start_max(x_helper->StartMax(t)),
87  x_end_min(x_helper->EndMin(t)),
88  x_end_max(x_helper->EndMax(t)),
89  x_size_min(x_helper->SizeMin(t)) {}
90 
93  : BaseEvent(t, x_helper) {}
94 
95  // We need this for linearizing the energy in some cases.
97 
98  // If set, this event is optional and its presence is controlled by this.
100 
101  // A linear expression which is a valid lower bound on the total energy of
102  // this event. We also cache the activity of the expression to not recompute
103  // it all the time.
106 
107  // True if linearized_energy is not exact and a McCormick relaxation.
108  bool energy_is_quadratic = false;
109 
110  // Used to minimize the increase on the y axis for rectangles.
111  double y_spread = 0.0;
112 
113  // The actual value of the presence literal of the interval(s) is checked
114  // when the event is created. A value of kNoLiteralIndex indicates that either
115  // the interval was not optional, or that its presence literal is true at
116  // level zero.
118 
119  // Computes the mandatory minimal overlap of the interval with the time window
120  // [start, end].
121  IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const {
123  end - start}),
124  IntegerValue(0));
125  }
126 
127  // This method expects all the other fields to have been filled before.
128  // It must be called before the EnergyEvent is used.
129  ABSL_MUST_USE_RESULT bool FillEnergyLp(
130  AffineExpression x_size,
132  Model* model) {
133  LinearConstraintBuilder tmp_energy(model);
134  if (IsPresent()) {
135  if (!decomposed_energy.empty()) {
136  if (!tmp_energy.AddDecomposedProduct(decomposed_energy)) return false;
137  } else {
138  tmp_energy.AddQuadraticLowerBound(x_size, y_size,
139  model->GetOrCreate<IntegerTrail>(),
141  }
142  } else {
144  energy_min)) {
145  return false;
146  }
147  }
148  linearized_energy = tmp_energy.BuildExpression();
150  return true;
151  }
152 
153  std::string DebugString() const {
154  return absl::StrCat(
155  "EnergyEvent(x_start_min = ", x_start_min.value(),
156  ", x_start_max = ", x_start_max.value(),
157  ", x_end_min = ", x_end_min.value(),
158  ", x_end_max = ", x_end_max.value(), ", y_min = ", y_min.value(),
159  ", y_max = ", y_max.value(), ", y_size = ", y_size.DebugString(),
160  ", energy = ",
161  decomposed_energy.empty()
162  ? "{}"
163  : absl::StrCat(decomposed_energy.size(), " terms"),
164  ", presence_literal_index = ", presence_literal_index.value(), ")");
165  }
166 };
167 
168 namespace {
169 
170 // Compute the energetic contribution of a task in a given time window, and
171 // add it to the cut. It returns false if it tried to generate the cut, and
172 // failed.
173 ABSL_MUST_USE_RESULT bool AddOneEvent(
174  const EnergyEvent& event, IntegerValue window_start,
175  IntegerValue window_end, LinearConstraintBuilder* cut,
176  bool* add_energy_to_name = nullptr, bool* add_quadratic_to_name = nullptr,
177  bool* add_opt_to_name = nullptr, bool* add_lifted_to_name = nullptr) {
178  DCHECK(cut != nullptr);
179 
180  if (event.x_end_min <= window_start || event.x_start_max >= window_end) {
181  return true; // Event can move outside the time window.
182  }
183 
184  if (event.x_start_min >= window_start && event.x_end_max <= window_end) {
185  // Event is always contained by the time window.
186  cut->AddLinearExpression(event.linearized_energy);
187 
188  if (event.energy_is_quadratic && add_quadratic_to_name != nullptr) {
189  *add_quadratic_to_name = true;
190  }
191  if (add_energy_to_name != nullptr &&
192  event.energy_min > event.x_size_min * event.y_size_min) {
193  *add_energy_to_name = true;
194  }
195  if (!event.IsPresent() && add_opt_to_name != nullptr) {
196  *add_opt_to_name = true;
197  }
198  } else { // The event has a mandatory overlap with the time window.
199  const IntegerValue min_overlap =
200  event.GetMinOverlap(window_start, window_end);
201  if (min_overlap <= 0) return true;
202  if (add_lifted_to_name != nullptr) *add_lifted_to_name = true;
203 
204  if (event.IsPresent()) {
205  const std::vector<LiteralValueValue>& energy = event.decomposed_energy;
206  if (energy.empty()) {
207  cut->AddTerm(event.y_size, min_overlap);
208  } else {
209  const IntegerValue window_size = window_end - window_start;
210  for (const auto [lit, fixed_size, fixed_demand] : energy) {
211  const IntegerValue alt_end_min =
212  std::max(event.x_end_min, event.x_start_min + fixed_size);
213  const IntegerValue alt_start_max =
214  std::min(event.x_start_max, event.x_end_max - fixed_size);
215  const IntegerValue energy_min =
216  fixed_demand *
217  std::min({alt_end_min - window_start, window_end - alt_start_max,
218  fixed_size, window_size});
219  if (energy_min == 0) continue;
220  if (!cut->AddLiteralTerm(lit, energy_min)) return false;
221  }
222  if (add_energy_to_name != nullptr) *add_energy_to_name = true;
223  }
224  } else {
225  if (add_opt_to_name != nullptr) *add_opt_to_name = true;
226  const IntegerValue min_energy = ComputeEnergyMinInWindow(
227  event.x_start_min, event.x_start_max, event.x_end_min,
228  event.x_end_max, event.x_size_min, event.y_size_min,
229  event.decomposed_energy, window_start, window_end);
230  if (min_energy > event.x_size_min * event.y_size_min &&
231  add_energy_to_name != nullptr) {
232  *add_energy_to_name = true;
233  }
234  if (!cut->AddLiteralTerm(Literal(event.presence_literal_index),
235  min_energy)) {
236  return false;
237  }
238  }
239  }
240  return true;
241 }
242 
243 // Returns the list of all possible demand values for the given event.
244 // It returns an empty vector is the number of values is too large.
245 std::vector<int64_t> FindPossibleDemands(const EnergyEvent& event,
246  const VariablesAssignment& assignment,
247  IntegerTrail* integer_trail) {
248  std::vector<int64_t> possible_demands;
249  if (event.decomposed_energy.empty()) {
250  if (integer_trail->IsFixed(event.y_size)) {
251  possible_demands.push_back(
252  integer_trail->FixedValue(event.y_size).value());
253  } else {
254  if (integer_trail->InitialVariableDomain(event.y_size.var).Size() >
255  1000000) {
256  return {};
257  }
258  for (const int64_t var_value :
259  integer_trail->InitialVariableDomain(event.y_size.var).Values()) {
260  possible_demands.push_back(event.y_size.ValueAt(var_value).value());
261  }
262  }
263  } else {
264  for (const auto [lit, fixed_size, fixed_demand] : event.decomposed_energy) {
265  if (assignment.LiteralIsFalse(lit)) continue;
266  possible_demands.push_back(fixed_demand.value());
267  }
268  }
269  return possible_demands;
270 }
271 
272 // Will scan all event, compute the cumulated energy of all events, and returns
273 // whether it exceeds available_energy_lp.
274 bool CutIsEfficient(
275  const std::vector<EnergyEvent>& events, IntegerValue window_start,
276  IntegerValue window_end, double available_energy_lp,
278  Model* model) {
279  // Scan all events and sum their energetic contributions.
280  double energy_from_events_lp = 0.0;
281  LinearConstraintBuilder tmp_energy(model);
282  for (const EnergyEvent& event : events) {
283  tmp_energy.Clear();
284  if (!AddOneEvent(event, window_start, window_end, &tmp_energy)) {
285  return false;
286  }
287  energy_from_events_lp += tmp_energy.BuildExpression().LpValue(lp_values);
288  }
289 
290  return energy_from_events_lp >=
291  available_energy_lp * (1.0 + kMinCutViolation);
292 }
293 
294 } // namespace
295 
296 // This cumulative energetic cut generator will split the cumulative span in 2
297 // regions.
298 //
299 // In the region before the min of the makespan, we will compute a more
300 // precise reachable profile and have a better estimation of the energy
301 // available between two time point. the improvement can come from two sources:
302 // - subset sum indicates that the max capacity cannot be reached.
303 // - sum of demands < max capacity.
304 //
305 // In the region after the min of the makespan, we will use
306 // fixed_capacity * (makespan - makespan_min)
307 // as the available energy.
309  const std::string& cut_name,
311  std::vector<EnergyEvent> events, IntegerValue capacity,
313  LinearConstraintManager* manager) {
314  // Checks the precondition of the code.
315  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
316  DCHECK(integer_trail->IsFixed(capacity));
317 
318  const VariablesAssignment& assignment =
319  model->GetOrCreate<Trail>()->Assignment();
320 
321  // Currently, we look at all the possible time windows, and will push all cuts
322  // in the TopNCuts object. From our observations, this generator creates only
323  // a few cuts for a given run.
324  //
325  // The complexity of this loop is n^3. if we follow the latest research, we
326  // could implement this in n log^2(n). Still, this is not visible in the
327  // profile as we only this method at the root node,
328  struct OverloadedTimeWindowWithMakespan {
329  IntegerValue start;
330  IntegerValue end;
331  IntegerValue fixed_energy_rhs; // Can be complemented by the makespan.
332  bool use_makespan = false;
333  bool use_subset_sum = false;
334  };
335 
336  std::vector<OverloadedTimeWindowWithMakespan> overloaded_time_windows;
337  // Compute relevant time points.
338  // TODO(user): We could reduce this set.
339  // TODO(user): we can compute the max usage between makespan_min and
340  // makespan_max.
341  std::vector<IntegerValue> time_points;
342  std::vector<std::vector<int64_t>> possible_demands(events.size());
343  const IntegerValue makespan_min = integer_trail->LowerBound(makespan);
344  IntegerValue max_end_min = kMinIntegerValue; // Used to abort early.
345  IntegerValue max_end_max = kMinIntegerValue; // Used as a sentinel.
346  for (int i = 0; i < events.size(); ++i) {
347  const EnergyEvent& event = events[i];
348  if (event.x_start_min < makespan_min) {
349  time_points.push_back(event.x_start_min);
350  }
351  if (event.x_start_max < makespan_min) {
352  time_points.push_back(event.x_start_max);
353  }
354  if (event.x_end_min < makespan_min) {
355  time_points.push_back(event.x_end_min);
356  }
357  if (event.x_end_max < makespan_min) {
358  time_points.push_back(event.x_end_max);
359  }
360  max_end_min = std::max(max_end_min, event.x_end_min);
361  max_end_max = std::max(max_end_max, event.x_end_max);
362  possible_demands[i] = FindPossibleDemands(event, assignment, integer_trail);
363  }
364  time_points.push_back(makespan_min);
365  time_points.push_back(max_end_max);
366  gtl::STLSortAndRemoveDuplicates(&time_points);
367 
368  const int num_time_points = time_points.size();
369  absl::flat_hash_map<IntegerValue, IntegerValue> reachable_capacity_ending_at;
370 
371  MaxBoundedSubsetSum reachable_capacity_subset_sum(capacity.value());
372  for (int i = 1; i < num_time_points; ++i) {
373  const IntegerValue window_start = time_points[i - 1];
374  const IntegerValue window_end = time_points[i];
375  reachable_capacity_subset_sum.Reset(capacity.value());
376  for (int i = 0; i < events.size(); ++i) {
377  const EnergyEvent& event = events[i];
378  if (event.x_start_min >= window_end || event.x_end_max <= window_start) {
379  continue;
380  }
381  if (possible_demands[i].empty()) { // Number of values was too large.
382  // In practice, it stops the DP as the upper bound is reached.
383  reachable_capacity_subset_sum.Add(capacity.value());
384  } else {
385  reachable_capacity_subset_sum.AddChoices(possible_demands[i]);
386  }
387  if (reachable_capacity_subset_sum.CurrentMax() == capacity.value()) break;
388  }
389  reachable_capacity_ending_at[window_end] =
390  reachable_capacity_subset_sum.CurrentMax();
391  }
392 
393  const double capacity_lp = ToDouble(capacity);
394  const double makespan_lp = makespan.LpValue(lp_values);
395  const double makespan_min_lp = ToDouble(makespan_min);
396  for (int i = 0; i + 1 < num_time_points; ++i) {
397  // Checks the time limit if the problem is too big.
398  if (events.size() > 50 && time_limit->LimitReached()) return;
399 
400  const IntegerValue window_start = time_points[i];
401  // After max_end_min, all tasks can fit before window_start.
402  if (window_start >= max_end_min) break;
403 
404  IntegerValue cumulated_max_energy = 0;
405  IntegerValue cumulated_max_energy_before_makespan_min = 0;
406  bool use_subset_sum = false;
407  bool use_subset_sum_before_makespan_min = false;
408 
409  for (int j = i + 1; j < num_time_points; ++j) {
410  const IntegerValue strip_start = time_points[j - 1];
411  const IntegerValue window_end = time_points[j];
412  const IntegerValue max_reachable_capacity_in_current_strip =
413  reachable_capacity_ending_at[window_end];
414  DCHECK_LE(max_reachable_capacity_in_current_strip, capacity);
415 
416  // Update states for the name of the generated cut.
417  if (max_reachable_capacity_in_current_strip < capacity) {
418  use_subset_sum = true;
419  if (window_end <= makespan_min) {
420  use_subset_sum_before_makespan_min = true;
421  }
422  }
423 
424  const IntegerValue energy_in_strip =
425  (window_end - strip_start) * max_reachable_capacity_in_current_strip;
426  cumulated_max_energy += energy_in_strip;
427  if (window_end <= makespan_min) {
428  cumulated_max_energy_before_makespan_min += energy_in_strip;
429  }
430 
431  if (window_start >= makespan_min) {
432  DCHECK_EQ(cumulated_max_energy_before_makespan_min, 0);
433  }
434  DCHECK_LE(cumulated_max_energy, capacity * (window_end - window_start));
435  const double max_energy_up_to_makespan_lp =
436  strip_start >= makespan_min
437  ? ToDouble(cumulated_max_energy_before_makespan_min) +
438  (makespan_lp - makespan_min_lp) * capacity_lp
439  : std::numeric_limits<double>::infinity();
440 
441  // We prefer using the makespan as the cut will tighten itself when the
442  // objective value is improved.
443  //
444  // We reuse the min cut violation to allow some slack in the comparison
445  // between the two computed energy values.
446  const bool use_makespan =
447  max_energy_up_to_makespan_lp <=
448  ToDouble(cumulated_max_energy) + kMinCutViolation;
449  const double available_energy_lp = use_makespan
450  ? max_energy_up_to_makespan_lp
451  : ToDouble(cumulated_max_energy);
452  if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
453  lp_values, model)) {
454  OverloadedTimeWindowWithMakespan w;
455  w.start = window_start;
456  w.end = window_end;
457  w.fixed_energy_rhs = use_makespan
458  ? cumulated_max_energy_before_makespan_min
459  : cumulated_max_energy;
460  w.use_makespan = use_makespan;
461  w.use_subset_sum =
462  use_makespan ? use_subset_sum_before_makespan_min : use_subset_sum;
463  overloaded_time_windows.push_back(std::move(w));
464  }
465  }
466  }
467 
468  if (overloaded_time_windows.empty()) return;
469 
470  VLOG(2) << "GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity: "
471  << events.size() << " events, " << time_points.size()
472  << " time points, " << overloaded_time_windows.size()
473  << " overloads detected";
474 
475  TopNCuts top_n_cuts(5);
476  for (const auto& w : overloaded_time_windows) {
477  bool cut_generated = true;
478  bool add_opt_to_name = false;
479  bool add_lifted_to_name = false;
480  bool add_quadratic_to_name = false;
481  bool add_energy_to_name = false;
482  LinearConstraintBuilder cut(model, kMinIntegerValue, w.fixed_energy_rhs);
483 
484  if (w.use_makespan) { // Add the energy from makespan_min to makespan.
485  cut.AddConstant(makespan_min * capacity);
486  cut.AddTerm(makespan, -capacity);
487  }
488 
489  // Add contributions from all events.
490  for (const EnergyEvent& event : events) {
491  if (!AddOneEvent(event, w.start, w.end, &cut, &add_energy_to_name,
492  &add_quadratic_to_name, &add_opt_to_name,
493  &add_lifted_to_name)) {
494  cut_generated = false;
495  break; // Exit the event loop.
496  }
497  }
498 
499  if (cut_generated) {
500  std::string full_name = cut_name;
501  if (add_opt_to_name) full_name.append("_optional");
502  if (add_quadratic_to_name) full_name.append("_quadratic");
503  if (add_lifted_to_name) full_name.append("_lifted");
504  if (add_energy_to_name) full_name.append("_energy");
505  if (w.use_makespan) full_name.append("_makespan");
506  if (w.use_subset_sum) full_name.append("_subsetsum");
507  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
508  }
509  }
510 
511  top_n_cuts.TransferToManager(lp_values, manager);
512 }
513 
515  const std::string& cut_name,
517  std::vector<EnergyEvent> events, const AffineExpression capacity,
519  double max_possible_energy_lp = 0.0;
520  for (const EnergyEvent& event : events) {
521  max_possible_energy_lp += event.linearized_energy_lp_value;
522  }
523 
524  // Currently, we look at all the possible time windows, and will push all cuts
525  // in the TopNCuts object. From our observations, this generator creates only
526  // a few cuts for a given run.
527  //
528  // The complexity of this loop is n^3. if we follow the latest research, we
529  // could implement this in n log^2(n). Still, this is not visible in the
530  // profile as we only this method at the root node,
531  struct OverloadedTimeWindow {
532  IntegerValue start;
533  IntegerValue end;
534  };
535  std::vector<OverloadedTimeWindow> overloaded_time_windows;
536  const double capacity_lp = capacity.LpValue(lp_values);
537 
538  // Compute relevant time points.
539  // TODO(user): We could reduce this set.
540  absl::btree_set<IntegerValue> time_points_set;
541  IntegerValue max_end_min = kMinIntegerValue;
542  for (const EnergyEvent& event : events) {
543  time_points_set.insert(event.x_start_min);
544  time_points_set.insert(event.x_start_max);
545  time_points_set.insert(event.x_end_min);
546  time_points_set.insert(event.x_end_max);
547  max_end_min = std::max(max_end_min, event.x_end_min);
548  }
549  const std::vector<IntegerValue> time_points(time_points_set.begin(),
550  time_points_set.end());
551  const int num_time_points = time_points.size();
552 
553  for (int i = 0; i + 1 < num_time_points; ++i) {
554  // Checks the time limit if the problem is too big.
555  if (events.size() > 50 && time_limit->LimitReached()) return;
556 
557  const IntegerValue window_start = time_points[i];
558  // After max_end_min, all tasks can fit before window_start.
559  if (window_start >= max_end_min) break;
560 
561  for (int j = i + 1; j < num_time_points; ++j) {
562  const IntegerValue window_end = time_points[j];
563  const double available_energy_lp =
564  ToDouble(window_end - window_start) * capacity_lp;
565  if (available_energy_lp >= max_possible_energy_lp) break;
566  if (CutIsEfficient(events, window_start, window_end, available_energy_lp,
567  lp_values, model)) {
568  overloaded_time_windows.push_back({window_start, window_end});
569  }
570  }
571  }
572 
573  if (overloaded_time_windows.empty()) return;
574 
575  VLOG(2) << "GenerateCumulativeEnergeticCuts: " << events.size() << " events, "
576  << time_points.size() << " time points, "
577  << overloaded_time_windows.size() << " overloads detected";
578 
579  TopNCuts top_n_cuts(5);
580  for (const auto& [window_start, window_end] : overloaded_time_windows) {
581  bool cut_generated = true;
582  bool add_opt_to_name = false;
583  bool add_lifted_to_name = false;
584  bool add_quadratic_to_name = false;
585  bool add_energy_to_name = false;
586  LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
587 
588  // Compute the max energy available for the tasks.
589  cut.AddTerm(capacity, window_start - window_end);
590 
591  // Add all contributions.
592  for (const EnergyEvent& event : events) {
593  if (!AddOneEvent(event, window_start, window_end, &cut,
594  &add_energy_to_name, &add_quadratic_to_name,
595  &add_opt_to_name, &add_lifted_to_name)) {
596  cut_generated = false;
597  break; // Exit the event loop.
598  }
599  }
600 
601  if (cut_generated) {
602  std::string full_name = cut_name;
603  if (add_opt_to_name) full_name.append("_optional");
604  if (add_quadratic_to_name) full_name.append("_quadratic");
605  if (add_lifted_to_name) full_name.append("_lifted");
606  if (add_energy_to_name) full_name.append("_energy");
607  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
608  }
609  }
610 
611  top_n_cuts.TransferToManager(lp_values, manager);
612 }
613 
615  SchedulingDemandHelper* demands_helper,
616  Model* model,
617  std::vector<IntegerVariable>* vars) {
618  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
619  for (const AffineExpression& demand_expr : demands_helper->Demands()) {
620  if (!integer_trail->IsFixed(demand_expr)) {
621  vars->push_back(demand_expr.var);
622  }
623  }
624  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
625  for (const auto& product : demands_helper->DecomposedEnergies()) {
626  for (const auto& lit_val_val : product) {
627  IntegerVariable view = kNoIntegerVariable;
628  if (!encoder->LiteralOrNegationHasView(lit_val_val.literal, &view)) {
629  view = model->Add(NewIntegerVariableFromLiteral(lit_val_val.literal));
630  }
631  vars->push_back(view);
632  }
633  }
634 
635  if (!integer_trail->IsFixed(capacity)) {
636  vars->push_back(capacity.var);
637  }
638 }
639 
641  SchedulingConstraintHelper* helper, SchedulingDemandHelper* demands_helper,
642  const AffineExpression& capacity,
643  const std::optional<AffineExpression>& makespan, Model* model) {
644  CutGenerator result;
645  result.only_run_at_level_zero = true;
646  AppendVariablesToCumulativeCut(capacity, demands_helper, model, &result.vars);
647  AddIntegerVariableFromIntervals(helper, model, &result.vars);
649  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
650  TimeLimit* time_limit = model->GetOrCreate<TimeLimit>();
651 
652  result.generate_cuts =
653  [makespan, capacity, demands_helper, helper, integer_trail, time_limit,
655  LinearConstraintManager* manager) {
656  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
657  demands_helper->CacheAllEnergyValues();
658 
659  std::vector<EnergyEvent> events;
660  for (int i = 0; i < helper->NumTasks(); ++i) {
661  if (helper->IsAbsent(i)) continue;
662  // TODO(user): use level 0 bounds ?
663  if (demands_helper->DemandMax(i) == 0 || helper->SizeMin(i) == 0) {
664  continue;
665  }
666 
667  EnergyEvent e(i, helper);
668  e.y_size = demands_helper->Demands()[i];
669  e.y_size_min = demands_helper->DemandMin(i);
670  e.decomposed_energy = demands_helper->DecomposedEnergies()[i];
671  e.energy_min = demands_helper->EnergyMin(i);
672  e.energy_is_quadratic = demands_helper->EnergyIsQuadratic(i);
673  if (!helper->IsPresent(i)) {
675  }
676  // We can always skip events.
677  if (!e.FillEnergyLp(helper->Sizes()[i], lp_values, model)) continue;
678  events.push_back(e);
679  }
680 
681  if (makespan.has_value() && integer_trail->IsFixed(capacity)) {
683  "CumulativeEnergyM", lp_values, events,
684  integer_trail->FixedValue(capacity), makespan.value(), time_limit,
685  model, manager);
686 
687  } else {
688  GenerateCumulativeEnergeticCuts("CumulativeEnergy", lp_values, events,
689  capacity, time_limit, model, manager);
690  }
691  return true;
692  };
693 
694  return result;
695 }
696 
699  const std::optional<AffineExpression>& makespan, Model* model) {
700  CutGenerator result;
701  result.only_run_at_level_zero = true;
702  AddIntegerVariableFromIntervals(helper, model, &result.vars);
704  TimeLimit* time_limit = model->GetOrCreate<TimeLimit>();
705 
706  result.generate_cuts =
707  [makespan, helper, time_limit, model](
709  LinearConstraintManager* manager) {
710  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
711 
712  std::vector<EnergyEvent> events;
713  for (int i = 0; i < helper->NumTasks(); ++i) {
714  if (helper->IsAbsent(i)) continue;
715  if (helper->SizeMin(i) == 0) continue;
716 
717  EnergyEvent e(i, helper);
718  e.y_size = IntegerValue(1);
719  e.y_size_min = IntegerValue(1);
720  e.energy_min = e.x_size_min;
721  if (!helper->IsPresent(i)) {
723  }
724  // We can always skip events.
725  if (!e.FillEnergyLp(helper->Sizes()[i], lp_values, model)) continue;
726  events.push_back(e);
727  }
728 
729  if (makespan.has_value()) {
731  "NoOverlapEnergyM", lp_values, events,
732  /*capacity=*/IntegerValue(1), makespan.value(), time_limit, model,
733  manager);
734  } else {
735  GenerateCumulativeEnergeticCuts("NoOverlapEnergy", lp_values, events,
736  /*capacity=*/IntegerValue(1),
737  time_limit, model, manager);
738  }
739  return true;
740  };
741  return result;
742 }
743 
745  const std::vector<std::vector<LiteralValueValue>>& energies,
746  absl::Span<int> rectangles, const std::string& cut_name,
749  SchedulingConstraintHelper* y_helper,
750  SchedulingDemandHelper* y_demands_helper) {
751  std::vector<EnergyEvent> events;
752  for (const int rect : rectangles) {
753  if (y_helper->SizeMax(rect) == 0 || x_helper->SizeMax(rect) == 0) {
754  continue;
755  }
756 
757  EnergyEvent e(rect, x_helper);
758  e.y_min = y_helper->StartMin(rect);
759  e.y_max = y_helper->EndMax(rect);
760  e.y_size = y_helper->Sizes()[rect];
761  e.decomposed_energy = energies[rect];
763  x_helper->IsPresent(rect)
764  ? (y_helper->IsPresent(rect)
766  : y_helper->PresenceLiteral(rect).Index())
767  : x_helper->PresenceLiteral(rect).Index();
768  e.y_size_min = y_helper->SizeMin(rect);
769  e.energy_min = y_demands_helper->EnergyMin(rect);
770  e.energy_is_quadratic = y_demands_helper->EnergyIsQuadratic(rect);
771 
772  // We can always skip events.
773  if (!e.FillEnergyLp(x_helper->Sizes()[rect], lp_values, model)) continue;
774  events.push_back(e);
775  }
776 
777  if (events.empty()) return;
778 
779  // Compute y_spread.
780  double average_d = 0.0;
781  for (const auto& e : events) {
782  average_d += ToDouble(e.y_min + e.y_max);
783  }
784  const double average = average_d / 2.0 / static_cast<double>(events.size());
785  for (auto& e : events) {
786  e.y_spread = std::abs(ToDouble(e.y_max) - average) +
787  std::abs(average - ToDouble(e.y_min));
788  }
789 
790  TopNCuts top_n_cuts(5);
791 
792  std::sort(events.begin(), events.end(),
793  [](const EnergyEvent& a, const EnergyEvent& b) {
794  return std::tie(a.x_start_min, a.y_spread, a.x_end_max) <
795  std::tie(b.x_start_min, b.y_spread, b.x_end_max);
796  });
797 
798  // The sum of all energies can be used to stop iterating early.
799  double sum_of_all_energies = 0.0;
800  for (const auto& e : events) {
801  sum_of_all_energies += e.linearized_energy_lp_value;
802  }
803 
804  CapacityProfile capacity_profile;
805  for (int i1 = 0; i1 + 1 < events.size(); ++i1) {
806  // For each start time, we will keep the most violated cut generated while
807  // scanning the residual intervals.
808  int max_violation_end_index = -1;
809  double max_relative_violation = 1.0 + kMinCutViolation;
810  IntegerValue max_violation_window_start(0);
811  IntegerValue max_violation_window_end(0);
812  IntegerValue max_violation_y_min(0);
813  IntegerValue max_violation_y_max(0);
814  IntegerValue max_violation_area(0);
815  bool max_violation_use_precise_area = false;
816 
817  // Accumulate intervals, areas, energies and check for potential cuts.
818  double energy_lp = 0.0;
819  IntegerValue window_min = kMaxIntegerValue;
820  IntegerValue window_max = kMinIntegerValue;
821  IntegerValue y_min = kMaxIntegerValue;
822  IntegerValue y_max = kMinIntegerValue;
823  capacity_profile.Clear();
824 
825  // We sort all tasks (x_start_min(task) >= x_start_min(start_index) by
826  // increasing end max.
827  std::vector<EnergyEvent> residual_events(events.begin() + i1, events.end());
828  std::sort(residual_events.begin(), residual_events.end(),
829  [](const EnergyEvent& a, const EnergyEvent& b) {
830  return std::tie(a.x_end_max, a.y_spread) <
831  std::tie(b.x_end_max, b.y_spread);
832  });
833  // Let's process residual tasks and evaluate the violation of the cut at
834  // each step. We follow the same structure as the cut creation code below.
835  for (int i2 = 0; i2 < residual_events.size(); ++i2) {
836  const EnergyEvent& e = residual_events[i2];
837  energy_lp += e.linearized_energy_lp_value;
838  window_min = std::min(window_min, e.x_start_min);
839  window_max = std::max(window_max, e.x_end_max);
840  y_min = std::min(y_min, e.y_min);
841  y_max = std::max(y_max, e.y_max);
842  capacity_profile.AddRectangle(e.x_start_min, e.x_end_max, e.y_min,
843  e.y_max);
844 
845  // Dominance rule. If the next interval also fits in
846  // [window_min, window_max]*[y_min, y_max], the cut will be stronger with
847  // the next interval/rectangle.
848  if (i2 + 1 < residual_events.size() &&
849  residual_events[i2 + 1].x_start_min >= window_min &&
850  residual_events[i2 + 1].x_end_max <= window_max &&
851  residual_events[i2 + 1].y_min >= y_min &&
852  residual_events[i2 + 1].y_max <= y_max) {
853  continue;
854  }
855 
856  // Checks the current area vs the sum of all energies.
857  // The area is capacity_profile.GetBoundingArea().
858  // We can compare it to the bounding box area:
859  // (window_max - window_min) * (y_max - y_min).
860  bool use_precise_area = false;
861  IntegerValue precise_area(0);
862  double area_lp = 0.0;
863  const IntegerValue bbox_area =
864  (window_max - window_min) * (y_max - y_min);
865  precise_area = capacity_profile.GetBoundingArea();
866  use_precise_area = precise_area < bbox_area;
867  area_lp = ToDouble(std::min(precise_area, bbox_area));
868 
869  if (area_lp >= sum_of_all_energies) {
870  break;
871  }
872 
873  // Compute the violation of the potential cut.
874  const double relative_violation = energy_lp / area_lp;
875  if (relative_violation > max_relative_violation) {
876  max_violation_end_index = i2;
877  max_relative_violation = relative_violation;
878  max_violation_window_start = window_min;
879  max_violation_window_end = window_max;
880  max_violation_y_min = y_min;
881  max_violation_y_max = y_max;
882  max_violation_area = std::min(precise_area, bbox_area);
883  max_violation_use_precise_area = use_precise_area;
884  }
885  }
886 
887  if (max_violation_end_index == -1) continue;
888 
889  // A maximal violated cut has been found.
890  // Build it and add it to the pool.
891  bool add_opt_to_name = false;
892  bool add_quadratic_to_name = false;
893  bool add_energy_to_name = false;
894  LinearConstraintBuilder cut(model, kMinIntegerValue, max_violation_area);
895  for (int i2 = 0; i2 <= max_violation_end_index; ++i2) {
896  const EnergyEvent& event = residual_events[i2];
897  cut.AddLinearExpression(event.linearized_energy);
898  if (!event.IsPresent()) add_opt_to_name = true;
899  if (event.energy_is_quadratic) add_quadratic_to_name = true;
900  if (event.energy_min > event.x_size_min * event.y_size_min) {
901  add_energy_to_name = true;
902  }
903  }
904  std::string full_name = cut_name;
905  if (add_opt_to_name) full_name.append("_optional");
906  if (add_quadratic_to_name) full_name.append("_quadratic");
907  if (add_energy_to_name) full_name.append("_energy");
908  if (max_violation_use_precise_area) full_name.append("_precise");
909  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
910  }
911  top_n_cuts.TransferToManager(lp_values, manager);
912 }
913 
915  const std::vector<IntervalVariable>& x_intervals,
916  const std::vector<IntervalVariable>& y_intervals, Model* model) {
917  SchedulingConstraintHelper* x_helper =
918  model->GetOrCreate<IntervalsRepository>()->GetOrCreateHelper(x_intervals);
919  SchedulingConstraintHelper* y_helper =
920  model->GetOrCreate<IntervalsRepository>()->GetOrCreateHelper(y_intervals);
921 
922  CutGenerator result;
923  result.only_run_at_level_zero = true;
924  AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
925  AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
927 
928  SchedulingDemandHelper* x_demands_helper =
929  new SchedulingDemandHelper(x_helper->Sizes(), y_helper, model);
930  model->TakeOwnership(x_demands_helper);
931  SchedulingDemandHelper* y_demands_helper =
932  new SchedulingDemandHelper(y_helper->Sizes(), x_helper, model);
933  model->TakeOwnership(y_demands_helper);
934 
935  std::vector<std::vector<LiteralValueValue>> energies;
936  const int num_rectangles = x_intervals.size();
937  for (int i = 0; i < num_rectangles; ++i) {
938  energies.push_back(TryToDecomposeProduct(x_helper->Sizes()[i],
939  y_helper->Sizes()[i], model));
940  }
941 
942  result.generate_cuts =
943  [x_helper, y_helper, x_demands_helper, y_demands_helper, model, energies](
945  LinearConstraintManager* manager) {
946  if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
947  if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
948  x_demands_helper->CacheAllEnergyValues();
949  y_demands_helper->CacheAllEnergyValues();
950 
951  const int num_rectangles = x_helper->NumTasks();
952  std::vector<int> active_rectangles;
953  std::vector<Rectangle> cached_rectangles(num_rectangles);
954  for (int rect = 0; rect < num_rectangles; ++rect) {
955  if (y_helper->IsAbsent(rect) || y_helper->IsAbsent(rect)) continue;
956  // We do not consider rectangles controlled by 2 different unassigned
957  // enforcement literals.
958  if (!x_helper->IsPresent(rect) && !y_helper->IsPresent(rect) &&
959  x_helper->PresenceLiteral(rect) !=
960  y_helper->PresenceLiteral(rect)) {
961  continue;
962  }
963 
964  // TODO(user): It might be possible/better to use some shifted value
965  // here, but for now this code is not in the hot spot, so better be
966  // defensive and only do connected components on really disjoint
967  // rectangles.
968  Rectangle& rectangle = cached_rectangles[rect];
969  rectangle.x_min = x_helper->StartMin(rect);
970  rectangle.x_max = x_helper->EndMax(rect);
971  rectangle.y_min = y_helper->StartMin(rect);
972  rectangle.y_max = y_helper->EndMax(rect);
973 
974  active_rectangles.push_back(rect);
975  }
976 
977  if (active_rectangles.size() <= 1) return true;
978 
979  std::vector<absl::Span<int>> components =
981  cached_rectangles, absl::MakeSpan(active_rectangles));
982 
983  // Forward pass. No need to do a backward pass.
984  for (absl::Span<int> rectangles : components) {
985  if (rectangles.size() <= 1) continue;
986 
988  energies, rectangles, "NoOverlap2dXEnergy", lp_values, model,
989  manager, x_helper, y_helper, y_demands_helper);
991  energies, rectangles, "NoOverlap2dYEnergy", lp_values, model,
992  manager, y_helper, x_helper, x_demands_helper);
993  }
994 
995  return true;
996  };
997  return result;
998 }
999 
1001  SchedulingConstraintHelper* helper, SchedulingDemandHelper* demands_helper,
1002  const AffineExpression& capacity, Model* model) {
1003  CutGenerator result;
1004  result.only_run_at_level_zero = true;
1005  AppendVariablesToCumulativeCut(capacity, demands_helper, model, &result.vars);
1006  AddIntegerVariableFromIntervals(helper, model, &result.vars);
1008 
1009  struct TimeTableEvent {
1010  int interval_index;
1011  IntegerValue time;
1013  double demand_lp = 0.0;
1014  bool is_positive = false;
1015  bool use_energy = false;
1016  bool is_optional = false;
1017  };
1018 
1019  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1020  result.generate_cuts =
1021  [helper, capacity, demands_helper, integer_trail, model](
1023  LinearConstraintManager* manager) {
1024  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1025  demands_helper->CacheAllEnergyValues();
1026 
1027  TopNCuts top_n_cuts(5);
1028  std::vector<TimeTableEvent> events;
1029  const double capacity_lp = capacity.LpValue(lp_values);
1030 
1031  // Iterate through the intervals. If start_max < end_min, the demand
1032  // is mandatory.
1033  for (int i = 0; i < helper->NumTasks(); ++i) {
1034  if (helper->IsAbsent(i)) continue;
1035 
1036  const IntegerValue start_max = helper->StartMax(i);
1037  const IntegerValue end_min = helper->EndMin(i);
1038 
1039  if (start_max >= end_min) continue;
1040 
1041  TimeTableEvent e1;
1042  e1.interval_index = i;
1043  e1.time = start_max;
1044  {
1045  LinearConstraintBuilder builder(model);
1046  // Ignore the interval if the linearized demand fails.
1047  if (!demands_helper->AddLinearizedDemand(i, &builder)) continue;
1048  e1.demand = builder.BuildExpression();
1049  }
1050  e1.demand_lp = e1.demand.LpValue(lp_values);
1051  e1.is_positive = true;
1052  e1.use_energy = !demands_helper->DecomposedEnergies()[i].empty();
1053  e1.is_optional = !helper->IsPresent(i);
1054 
1055  TimeTableEvent e2 = e1;
1056  e2.time = end_min;
1057  e2.is_positive = false;
1058 
1059  events.push_back(e1);
1060  events.push_back(e2);
1061  }
1062 
1063  // Sort events by time.
1064  // It is also important that all positive event with the same time as
1065  // negative events appear after for the correctness of the algo below.
1066  std::sort(events.begin(), events.end(),
1067  [](const TimeTableEvent& i, const TimeTableEvent& j) {
1068  if (i.time == j.time) {
1069  if (i.is_positive == j.is_positive) {
1070  return i.interval_index < j.interval_index;
1071  }
1072  return !i.is_positive;
1073  }
1074  return i.time < j.time;
1075  });
1076 
1077  double sum_of_demand_lp = 0.0;
1078  bool positive_event_added_since_last_check = false;
1079  for (int i = 0; i < events.size(); ++i) {
1080  const TimeTableEvent& e = events[i];
1081  if (e.is_positive) {
1082  positive_event_added_since_last_check = true;
1083  sum_of_demand_lp += e.demand_lp;
1084  continue;
1085  }
1086 
1087  if (positive_event_added_since_last_check) {
1088  // Reset positive event added. We do not want to create cuts for
1089  // each negative event in sequence.
1090  positive_event_added_since_last_check = false;
1091 
1092  if (sum_of_demand_lp >= capacity_lp + kMinCutViolation) {
1093  // Create cut.
1094  bool use_energy = false;
1095  bool use_optional = false;
1097  IntegerValue(0));
1098  cut.AddTerm(capacity, IntegerValue(-1));
1099  // The i-th event, which is a negative event, follows a positive
1100  // event. We must ignore it in our cut generation.
1101  DCHECK(!events[i].is_positive);
1102  const IntegerValue time_point = events[i - 1].time;
1103 
1104  for (int j = 0; j < i; ++j) {
1105  const TimeTableEvent& cut_event = events[j];
1106  const int t = cut_event.interval_index;
1107  DCHECK_LE(helper->StartMax(t), time_point);
1108  if (!cut_event.is_positive || helper->EndMin(t) <= time_point) {
1109  continue;
1110  }
1111 
1112  cut.AddLinearExpression(cut_event.demand, IntegerValue(1));
1113  use_energy |= cut_event.use_energy;
1114  use_optional |= cut_event.is_optional;
1115  }
1116 
1117  std::string cut_name = "CumulativeTimeTable";
1118  if (use_optional) cut_name += "_optional";
1119  if (use_energy) cut_name += "_energy";
1120  top_n_cuts.AddCut(cut.Build(), cut_name, lp_values);
1121  }
1122  }
1123 
1124  // The demand_lp was added in case of a positive event. We need to
1125  // remove it for a negative event.
1126  sum_of_demand_lp -= e.demand_lp;
1127  }
1128  top_n_cuts.TransferToManager(lp_values, manager);
1129  return true;
1130  };
1131  return result;
1132 }
1133 
1134 // Cached Information about one interval.
1135 // Note that everything must correspond to level zero bounds, otherwise the
1136 // generated cut are not valid.
1137 
1140  : start_min(helper->StartMin(t)),
1141  start_max(helper->StartMax(t)),
1142  start(helper->Starts()[t]),
1143  end_min(helper->EndMin(t)),
1144  end_max(helper->EndMax(t)),
1145  end(helper->Ends()[t]),
1146  size_min(helper->SizeMin(t)) {}
1147 
1148  IntegerValue start_min;
1149  IntegerValue start_max;
1151  IntegerValue end_min;
1152  IntegerValue end_max;
1154  IntegerValue size_min;
1155 
1156  IntegerValue demand_min;
1157 };
1158 
1160  const std::string& cut_name,
1162  std::vector<CachedIntervalData> events, IntegerValue capacity_max,
1163  Model* model, LinearConstraintManager* manager) {
1164  TopNCuts top_n_cuts(5);
1165  const int num_events = events.size();
1166  if (num_events <= 1) return;
1167 
1168  std::sort(events.begin(), events.end(),
1169  [](const CachedIntervalData& e1, const CachedIntervalData& e2) {
1170  return e1.start_min < e2.start_min ||
1171  (e1.start_min == e2.start_min && e1.end_max < e2.end_max);
1172  });
1173 
1174  // Balas disjunctive cuts on 2 tasks a and b:
1175  // start_1 * (duration_1 + start_min_1 - start_min_2) +
1176  // start_2 * (duration_2 + start_min_2 - start_min_1) >=
1177  // duration_1 * duration_2 +
1178  // start_min_1 * duration_2 +
1179  // start_min_2 * duration_1
1180  // From: David L. Applegate, William J. Cook:
1181  // A Computational Study of the Job-Shop Scheduling Problem. 149-156
1182  // INFORMS Journal on Computing, Volume 3, Number 1, Winter 1991
1183  const auto add_balas_disjunctive_cut =
1184  [&](const std::string& local_cut_name, IntegerValue start_min_1,
1185  IntegerValue duration_min_1, AffineExpression start_1,
1186  IntegerValue start_min_2, IntegerValue duration_min_2,
1187  AffineExpression start_2) {
1188  // Checks hypothesis from the cut.
1189  if (start_min_2 >= start_min_1 + duration_min_1 ||
1190  start_min_1 >= start_min_2 + duration_min_2) {
1191  return;
1192  }
1193  const IntegerValue coeff_1 = duration_min_1 + start_min_1 - start_min_2;
1194  const IntegerValue coeff_2 = duration_min_2 + start_min_2 - start_min_1;
1195  const IntegerValue rhs = duration_min_1 * duration_min_2 +
1196  duration_min_1 * start_min_2 +
1197  duration_min_2 * start_min_1;
1198 
1199  if (ToDouble(coeff_1) * start_1.LpValue(lp_values) +
1200  ToDouble(coeff_2) * start_2.LpValue(lp_values) <=
1201  ToDouble(rhs) - kMinCutViolation) {
1203  cut.AddTerm(start_1, coeff_1);
1204  cut.AddTerm(start_2, coeff_2);
1205  top_n_cuts.AddCut(cut.Build(), local_cut_name, lp_values);
1206  }
1207  };
1208 
1209  for (int i = 0; i + 1 < num_events; ++i) {
1210  const CachedIntervalData& e1 = events[i];
1211  for (int j = i + 1; j < num_events; ++j) {
1212  const CachedIntervalData& e2 = events[j];
1213  if (e2.start_min >= e1.end_max) break; // Break out of the index2 loop.
1214 
1215  // Encode only the interesting pairs.
1216  if (e1.demand_min + e2.demand_min <= capacity_max) continue;
1217 
1218  const bool interval_1_can_precede_2 = e1.end_min <= e2.start_max;
1219  const bool interval_2_can_precede_1 = e2.end_min <= e1.start_max;
1220 
1221  if (interval_1_can_precede_2 && !interval_2_can_precede_1 &&
1222  e1.end.LpValue(lp_values) >=
1223  e2.start.LpValue(lp_values) + kMinCutViolation) {
1224  // interval1.end <= interval2.start
1225  LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
1226  cut.AddTerm(e1.end, IntegerValue(1));
1227  cut.AddTerm(e2.start, IntegerValue(-1));
1228  top_n_cuts.AddCut(cut.Build(),
1229  absl::StrCat(cut_name, "DetectedPrecedence"),
1230  lp_values);
1231  } else if (interval_2_can_precede_1 && !interval_1_can_precede_2 &&
1232  e2.end.LpValue(lp_values) >=
1233  e1.start.LpValue(lp_values) + kMinCutViolation) {
1234  // interval2.end <= interval1.start
1235  LinearConstraintBuilder cut(model, kMinIntegerValue, IntegerValue(0));
1236  cut.AddTerm(e2.end, IntegerValue(1));
1237  cut.AddTerm(e1.start, IntegerValue(-1));
1238  top_n_cuts.AddCut(cut.Build(),
1239  absl::StrCat(cut_name, "DetectedPrecedence"),
1240  lp_values);
1241  } else {
1242  add_balas_disjunctive_cut(absl::StrCat(cut_name, "DisjunctionOnStart"),
1243  e1.start_min, e1.size_min, e1.start,
1244  e2.start_min, e2.size_min, e2.start);
1245  add_balas_disjunctive_cut(absl::StrCat(cut_name, "DisjunctionOnEnd"),
1246  -e1.end_max, e1.size_min, e1.end.Negated(),
1247  -e2.end_max, e2.size_min, e2.end.Negated());
1248  }
1249  }
1250  }
1251 
1252  top_n_cuts.TransferToManager(lp_values, manager);
1253 }
1254 
1256  SchedulingConstraintHelper* helper, SchedulingDemandHelper* demands_helper,
1257  const AffineExpression& capacity, Model* model) {
1258  CutGenerator result;
1259  result.only_run_at_level_zero = true;
1260  AppendVariablesToCumulativeCut(capacity, demands_helper, model, &result.vars);
1261  AddIntegerVariableFromIntervals(helper, model, &result.vars);
1263 
1264  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1265  result.generate_cuts =
1266  [integer_trail, helper, demands_helper, capacity, model](
1268  LinearConstraintManager* manager) {
1269  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1270 
1271  std::vector<CachedIntervalData> events;
1272  for (int t = 0; t < helper->NumTasks(); ++t) {
1273  if (!helper->IsPresent(t)) continue;
1274  CachedIntervalData event(t, helper);
1275  event.demand_min = demands_helper->DemandMin(t);
1276  events.push_back(event);
1277  }
1278 
1279  const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
1281  "Cumulative", lp_values, std::move(events), capacity_max, model,
1282  manager);
1283  return true;
1284  };
1285  return result;
1286 }
1287 
1290  CutGenerator result;
1291  result.only_run_at_level_zero = true;
1292  AddIntegerVariableFromIntervals(helper, model, &result.vars);
1294 
1295  result.generate_cuts =
1296  [helper, model](
1298  LinearConstraintManager* manager) {
1299  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1300 
1301  std::vector<CachedIntervalData> events;
1302  for (int t = 0; t < helper->NumTasks(); ++t) {
1303  if (!helper->IsPresent(t)) continue;
1304  CachedIntervalData event(t, helper);
1305  event.demand_min = IntegerValue(1);
1306  events.push_back(event);
1307  }
1308 
1310  "NoOverlap", lp_values, std::move(events), IntegerValue(1), model,
1311  manager);
1312  return true;
1313  };
1314 
1315  return result;
1316 }
1317 
1318 CtEvent::CtEvent(int t, SchedulingConstraintHelper* x_helper)
1319  : BaseEvent(t, x_helper) {}
1320 
1321 std::string CtEvent::DebugString() const {
1322  return absl::StrCat("CtEvent(x_end = ", x_end.DebugString(),
1323  ", x_start_min = ", x_start_min.value(),
1324  ", x_start_max = ", x_start_max.value(),
1325  ", x_size_min = ", x_size_min.value(),
1326  ", x_lp_end = ", x_lp_end, ", y_min = ", y_min.value(),
1327  ", y_max = ", y_max.value(),
1328  ", y_size_min = ", y_size_min.value(),
1329  ", energy_min = ", energy_min.value(),
1330  ", use_energy = ", use_energy, ", lifted = ", lifted);
1331 }
1332 
1333 namespace {
1334 
1335 // This functions packs all events in a cumulative of capacity 'capacity_max'
1336 // following the given permutation. It returns the sum of end mins and the sum
1337 // of end mins weighted by event.y_size_min.
1338 //
1339 // It ensures that if event_j is after event_i in the permutation, then event_j
1340 // starts exactly at the same time or after event_i.
1341 //
1342 // It returns false if one event cannot start before event.x_start_max.
1343 bool ComputeWeightedSumOfEndMinsForOnePermutation(
1344  const std::vector<PermutableEvent>& events, IntegerValue capacity_max,
1345  IntegerValue& sum_of_ends, IntegerValue& sum_of_weighted_ends,
1346  std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
1347  std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
1348  sum_of_ends = 0;
1349  sum_of_weighted_ends = 0;
1350 
1351  // The profile (and new profile) is a set of (time, capa_left) pairs, ordered
1352  // by increasing time and capa_left.
1353  profile.clear();
1354  profile.emplace_back(kMinIntegerValue, capacity_max);
1355  profile.emplace_back(kMaxIntegerValue, capacity_max);
1356  IntegerValue start_of_previous_task = kMinIntegerValue;
1357  for (const PermutableEvent& event : events) {
1358  const IntegerValue start_min =
1359  std::max(event.x_start_min, start_of_previous_task);
1360 
1361  // Iterate on the profile to find the step that contains start_min.
1362  // Then push until we find a step with enough capacity.
1363  int current = 0;
1364  while (profile[current + 1].first <= start_min ||
1365  profile[current].second < event.y_size_min) {
1366  ++current;
1367  }
1368 
1369  const IntegerValue actual_start =
1370  std::max(start_min, profile[current].first);
1371  start_of_previous_task = actual_start;
1372 
1373  // Compatible with the event._start_max ?
1374  if (actual_start > event.x_start_max) return false;
1375 
1376  const IntegerValue actual_end = actual_start + event.x_size_min;
1377  sum_of_ends += actual_end;
1378  sum_of_weighted_ends += event.y_size_min * actual_end;
1379 
1380  // No need to update the profile on the last loop.
1381  if (&event == &events.back()) break;
1382 
1383  // Update the profile.
1384  new_profile.clear();
1385  new_profile.push_back(
1386  {actual_start, profile[current].second - event.y_size_min});
1387  ++current;
1388 
1389  while (profile[current].first < actual_end) {
1390  new_profile.push_back(
1391  {profile[current].first, profile[current].second - event.y_size_min});
1392  ++current;
1393  }
1394 
1395  if (profile[current].first > actual_end) {
1396  new_profile.push_back(
1397  {actual_end, new_profile.back().second + event.y_size_min});
1398  }
1399  while (current < profile.size()) {
1400  new_profile.push_back(profile[current]);
1401  ++current;
1402  }
1403  profile.swap(new_profile);
1404  }
1405  return true;
1406 }
1407 
1408 } // namespace
1409 
1410 bool ComputeMinSumOfWeightedEndMins(std::vector<PermutableEvent>& events,
1411  IntegerValue capacity_max,
1412  IntegerValue& min_sum_of_end_mins,
1413  IntegerValue& min_sum_of_weighted_end_mins,
1414  IntegerValue unweighted_threshold,
1415  IntegerValue weighted_threshold) {
1416  int num_explored = 0;
1417  int num_pruned = 0;
1418  min_sum_of_end_mins = kMaxIntegerValue;
1419  min_sum_of_weighted_end_mins = kMaxIntegerValue;
1420 
1421  // Reusable storage for ComputeWeightedSumOfEndMinsForOnePermutation().
1422  std::vector<std::pair<IntegerValue, IntegerValue>> profile;
1423  std::vector<std::pair<IntegerValue, IntegerValue>> new_profile;
1424  do {
1425  IntegerValue sum_of_ends(0);
1426  IntegerValue sum_of_weighted_ends(0);
1427  if (ComputeWeightedSumOfEndMinsForOnePermutation(
1428  events, capacity_max, sum_of_ends, sum_of_weighted_ends, profile,
1429  new_profile)) {
1430  min_sum_of_end_mins = std::min(sum_of_ends, min_sum_of_end_mins);
1431  min_sum_of_weighted_end_mins =
1432  std::min(sum_of_weighted_ends, min_sum_of_weighted_end_mins);
1433  num_explored++;
1434  if (min_sum_of_end_mins <= unweighted_threshold &&
1435  min_sum_of_weighted_end_mins <= weighted_threshold) {
1436  break;
1437  }
1438  } else {
1439  num_pruned++;
1440  }
1441  } while (std::next_permutation(events.begin(), events.end()));
1442  VLOG(2) << "DP: size=" << events.size() << ", explored = " << num_explored
1443  << ", pruned = " << num_pruned
1444  << ", min_sum_of_end_mins = " << min_sum_of_end_mins
1445  << ", min_sum_of_weighted_end_mins = "
1446  << min_sum_of_weighted_end_mins;
1447  return num_explored > 0;
1448 }
1449 
1450 // TODO(user): Improve performance
1451 // - detect disjoint tasks (no need to crossover to the second part)
1452 // - better caching of explored states
1454  const std::string& cut_name,
1456  std::vector<CtEvent> events, IntegerValue capacity_max, Model* model,
1457  LinearConstraintManager* manager) {
1458  TopNCuts top_n_cuts(5);
1459  // Sort by start min to bucketize by start_min.
1460  std::sort(events.begin(), events.end(),
1461  [](const CtEvent& e1, const CtEvent& e2) {
1462  return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1463  std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1464  });
1465  std::vector<PermutableEvent> permutable_events;
1466  for (int start = 0; start + 1 < events.size(); ++start) {
1467  // Skip to the next start_min value.
1468  if (start > 0 &&
1469  events[start].x_start_min == events[start - 1].x_start_min) {
1470  continue;
1471  }
1472 
1473  const IntegerValue sequence_start_min = events[start].x_start_min;
1474  std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
1475 
1476  // We look at event that start before sequence_start_min, but are forced
1477  // to cross this time point. In that case, we replace this event by a
1478  // truncated event starting at sequence_start_min. To do this, we reduce
1479  // the size_min, and align the start_min with the sequence_start_min.
1480  for (int before = 0; before < start; ++before) {
1481  if (events[before].x_start_min + events[before].x_size_min >
1482  sequence_start_min) {
1483  residual_tasks.push_back(events[before]); // Copy.
1484  residual_tasks.back().lifted = true;
1485  }
1486  }
1487 
1488  std::sort(residual_tasks.begin(), residual_tasks.end(),
1489  [](const CtEvent& e1, const CtEvent& e2) {
1490  return e1.x_lp_end < e2.x_lp_end;
1491  });
1492 
1493  IntegerValue sum_of_durations(0);
1494  IntegerValue sum_of_energies(0);
1495  double sum_of_ends_lp = 0.0;
1496  double sum_of_weighted_ends_lp = 0.0;
1497  IntegerValue sum_of_demands(0);
1498 
1499  permutable_events.clear();
1500  for (int i = 0; i < std::min<int>(residual_tasks.size(), 7); ++i) {
1501  const CtEvent& event = residual_tasks[i];
1502  permutable_events.emplace_back(i, event);
1503  sum_of_ends_lp += event.x_lp_end;
1504  sum_of_weighted_ends_lp += event.x_lp_end * ToDouble(event.y_size_min);
1505  sum_of_demands += event.y_size_min;
1506  sum_of_durations += event.x_size_min;
1507  sum_of_energies += event.x_size_min * event.y_size_min;
1508 
1509  // Both cases with 1 or 2 tasks are trivial and independent of the order.
1510  // Also, if capacity is not exceeded, pushing all ends left is a valid LP
1511  // assignment.
1512  if (i <= 1 || sum_of_demands <= capacity_max) continue;
1513 
1514  IntegerValue min_sum_of_end_mins = kMaxIntegerValue;
1515  IntegerValue min_sum_of_weighted_end_mins = kMaxIntegerValue;
1516  for (int j = 0; j <= i; ++j) {
1517  // We re-index the elements, so we will start enumerating the
1518  // permutation from there. Note that if the previous i caused an abort
1519  // because of the threshold, we might abort right away again!
1520  permutable_events[j].index = j;
1521  }
1523  permutable_events, capacity_max, min_sum_of_end_mins,
1524  min_sum_of_weighted_end_mins,
1525  /*unweighted_threshold=*/
1526  std::floor(sum_of_ends_lp + kMinCutViolation),
1527  /*weighted_threshold=*/
1528  std::floor(sum_of_weighted_ends_lp + kMinCutViolation))) {
1529  break;
1530  }
1531 
1532  const double unweigthed_violation =
1533  (ToDouble(min_sum_of_end_mins) - sum_of_ends_lp) /
1534  ToDouble(sum_of_durations);
1535  const double weighted_violation =
1536  (ToDouble(min_sum_of_weighted_end_mins) - sum_of_weighted_ends_lp) /
1537  ToDouble(sum_of_energies);
1538 
1539  // Unweighted cuts.
1540  if (unweigthed_violation > weighted_violation &&
1541  unweigthed_violation > kMinCutViolation) {
1542  LinearConstraintBuilder cut(model, min_sum_of_end_mins,
1544  bool is_lifted = false;
1545  for (int j = 0; j <= i; ++j) {
1546  const CtEvent& event = residual_tasks[j];
1547  is_lifted |= event.lifted;
1548  cut.AddTerm(event.x_end, IntegerValue(1));
1549  }
1550  std::string full_name = cut_name;
1551  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
1552  }
1553 
1554  // Weighted cuts.
1555  if (weighted_violation >= unweigthed_violation &&
1556  weighted_violation > kMinCutViolation) {
1557  LinearConstraintBuilder cut(model, min_sum_of_weighted_end_mins,
1559  bool is_lifted = false;
1560  for (int j = 0; j <= i; ++j) {
1561  const CtEvent& event = residual_tasks[j];
1562  is_lifted |= event.lifted;
1563  cut.AddTerm(event.x_end, event.y_size_min);
1564  }
1565  std::string full_name = cut_name + "_weighted";
1566  if (is_lifted) full_name.append("_lifted");
1567  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
1568  }
1569  }
1570  }
1571  top_n_cuts.TransferToManager(lp_values, manager);
1572 }
1573 
1574 // We generate the cut from the Smith's rule from:
1575 // M. Queyranne, Structure of a simple scheduling polyhedron,
1576 // Mathematical Programming 58 (1993), 263–285
1577 //
1578 // The original cut is:
1579 // sum(end_min_i * duration_min_i) >=
1580 // (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
1581 // We strenghten this cuts by noticing that if all tasks starts after S,
1582 // then replacing end_min_i by (end_min_i - S) is still valid.
1583 //
1584 // A second difference is that we look at a set of intervals starting
1585 // after a given start_min, sorted by relative (end_lp - start_min).
1586 //
1587 // TODO(user): merge with Packing cuts.
1589  const std::string& cut_name,
1591  std::vector<CtEvent> events, bool use_lifting, bool skip_low_sizes,
1592  Model* model, LinearConstraintManager* manager) {
1593  TopNCuts top_n_cuts(5);
1594 
1595  // Sort by start min to bucketize by start_min.
1596  std::sort(events.begin(), events.end(),
1597  [](const CtEvent& e1, const CtEvent& e2) {
1598  return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
1599  std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
1600  });
1601  for (int start = 0; start + 1 < events.size(); ++start) {
1602  // Skip to the next start_min value.
1603  if (start > 0 &&
1604  events[start].x_start_min == events[start - 1].x_start_min) {
1605  continue;
1606  }
1607 
1608  const IntegerValue sequence_start_min = events[start].x_start_min;
1609  std::vector<CtEvent> residual_tasks(events.begin() + start, events.end());
1610 
1611  // We look at event that start before sequence_start_min, but are forced
1612  // to cross this time point. In that case, we replace this event by a
1613  // truncated event starting at sequence_start_min. To do this, we reduce
1614  // the size_min, align the start_min with the sequence_start_min, and
1615  // scale the energy down accordingly.
1616  if (use_lifting) {
1617  for (int before = 0; before < start; ++before) {
1618  if (events[before].x_start_min + events[before].x_size_min >
1619  sequence_start_min) {
1620  // Build the vector of energies as the vector of sizes.
1621  CtEvent event = events[before]; // Copy.
1622  event.lifted = true;
1623  event.energy_min = ComputeEnergyMinInWindow(
1624  event.x_start_min, event.x_start_max, event.x_end_min,
1625  event.x_end_max, event.x_size_min, event.y_size_min,
1626  event.decomposed_energy, sequence_start_min, event.x_end_max);
1627  event.x_size_min =
1628  event.x_size_min + event.x_start_min - sequence_start_min;
1629  event.x_start_min = sequence_start_min;
1630  if (event.energy_min > event.x_size_min * event.y_size_min) {
1631  event.use_energy = true;
1632  }
1633  DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
1634  if (event.energy_min <= 0) continue;
1635  residual_tasks.push_back(event);
1636  }
1637  }
1638  }
1639 
1640  std::sort(residual_tasks.begin(), residual_tasks.end(),
1641  [](const CtEvent& e1, const CtEvent& e2) {
1642  return e1.x_lp_end < e2.x_lp_end;
1643  });
1644 
1645  int best_end = -1;
1646  double best_efficacy = 0.01;
1647  IntegerValue best_min_contrib(0);
1648  IntegerValue sum_duration(0);
1649  IntegerValue sum_square_duration(0);
1650  IntegerValue best_capacity(0);
1651  double unscaled_lp_contrib = 0.0;
1652  IntegerValue current_start_min(kMaxIntegerValue);
1653  IntegerValue y_min = kMaxIntegerValue;
1654  IntegerValue y_max = kMinIntegerValue;
1655 
1656  bool use_dp = true;
1657  MaxBoundedSubsetSum dp(0);
1658  for (int i = 0; i < residual_tasks.size(); ++i) {
1659  const CtEvent& event = residual_tasks[i];
1660  DCHECK_GE(event.x_start_min, sequence_start_min);
1661  const IntegerValue energy = event.energy_min;
1662  sum_duration += energy;
1663  sum_square_duration += energy * energy;
1664  unscaled_lp_contrib += event.x_lp_end * ToDouble(energy);
1665  current_start_min = std::min(current_start_min, event.x_start_min);
1666 
1667  // This is competing with the brute force approach. Skip cases covered
1668  // by the other code.
1669  if (skip_low_sizes && i < 7) continue;
1670 
1671  // For the capacity, we use the worse |y_max - y_min| and if all the tasks
1672  // so far have a fixed demand with a gcd > 1, we can round it down.
1673  //
1674  // TODO(user): Use dynamic programming to compute all possible values for
1675  // the sum of demands as long as the involved numbers are small or the
1676  // number of tasks are small.
1677  y_min = std::min(y_min, event.y_min);
1678  y_max = std::max(y_max, event.y_max);
1679  if (!event.y_size_is_fixed) use_dp = false;
1680  if (use_dp) {
1681  if (i == 0) {
1682  dp.Reset((y_max - y_min).value());
1683  } else {
1684  if (y_max - y_min != dp.Bound()) {
1685  use_dp = false;
1686  }
1687  }
1688  }
1689  if (use_dp) {
1690  dp.Add(event.y_size_min.value());
1691  }
1692 
1693  const IntegerValue capacity =
1694  use_dp ? IntegerValue(dp.CurrentMax()) : y_max - y_min;
1695 
1696  // We compute the cuts like if it was a disjunctive cut with all the
1697  // duration actually equal to energy / capacity. But to keep the
1698  // computation in the integer domain, we multiply by capacity
1699  // everywhere instead.
1700  if (AtMinOrMaxInt64(
1701  CapAdd(CapProd(sum_duration.value(), sum_duration.value()),
1702  sum_square_duration.value()))) {
1703  break; // Overflow, we exit the loop.
1704  }
1705  const IntegerValue min_contrib =
1706  (sum_duration * sum_duration + sum_square_duration) / 2 +
1707  current_start_min * sum_duration * capacity;
1708 
1709  // We compute the efficacity in the unscaled domain where the l2 norm of
1710  // the cuts is exactly the sqrt of the sum of squared duration.
1711  const double efficacy =
1712  (ToDouble(min_contrib) / ToDouble(capacity) - unscaled_lp_contrib) /
1713  std::sqrt(ToDouble(sum_square_duration));
1714 
1715  // TODO(user): Check overflow and ignore if too big.
1716  if (efficacy > best_efficacy) {
1717  best_efficacy = efficacy;
1718  best_end = i;
1719  best_min_contrib = min_contrib;
1720  best_capacity = capacity;
1721  }
1722  }
1723  if (best_end != -1) {
1724  LinearConstraintBuilder cut(model, best_min_contrib, kMaxIntegerValue);
1725  bool is_lifted = false;
1726  bool add_energy_to_name = false;
1727  for (int i = 0; i <= best_end; ++i) {
1728  const CtEvent& event = residual_tasks[i];
1729  is_lifted |= event.lifted;
1730  add_energy_to_name |= event.use_energy;
1731  cut.AddTerm(event.x_end, event.energy_min * best_capacity);
1732  }
1733  std::string full_name = cut_name;
1734  if (is_lifted) full_name.append("_lifted");
1735  if (add_energy_to_name) full_name.append("_energy");
1736  top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
1737  }
1738  }
1739  top_n_cuts.TransferToManager(lp_values, manager);
1740 }
1741 
1744  CutGenerator result;
1745  result.only_run_at_level_zero = true;
1746  AddIntegerVariableFromIntervals(helper, model, &result.vars);
1748 
1749  result.generate_cuts =
1750  [helper, model](
1752  LinearConstraintManager* manager) {
1753  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1754 
1755  auto generate_cuts = [&lp_values, model, manager, helper](bool mirror) {
1756  std::vector<CtEvent> events;
1757  for (int index = 0; index < helper->NumTasks(); ++index) {
1758  if (!helper->IsPresent(index)) continue;
1759  const IntegerValue size_min = helper->SizeMin(index);
1760  if (size_min > 0) {
1761  const AffineExpression end_expr = helper->Ends()[index];
1762  CtEvent event(index, helper);
1763  event.x_end = end_expr;
1764  event.x_lp_end = end_expr.LpValue(lp_values);
1765  event.y_min = IntegerValue(0);
1766  event.y_max = IntegerValue(1);
1767  event.y_size_min = IntegerValue(1);
1768  event.energy_min = size_min;
1769  events.push_back(event);
1770  }
1771  }
1772 
1773  const std::string mirror_str = mirror ? "Mirror" : "";
1775  absl::StrCat("NoOverlapCompletionTimeExhaustive", mirror_str),
1776  lp_values, events, IntegerValue(1), model, manager);
1777 
1779  absl::StrCat("NoOverlapCompletionTimeQueyrane", mirror_str),
1780  lp_values, std::move(events),
1781  /*use_lifting=*/true, /*skip_low_sizes=*/true, model, manager);
1782  };
1783  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1784  generate_cuts(false);
1785  if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
1786  generate_cuts(true);
1787  return true;
1788  };
1789  return result;
1790 }
1791 
1793  SchedulingConstraintHelper* helper, SchedulingDemandHelper* demands_helper,
1794  const AffineExpression& capacity, Model* model) {
1795  CutGenerator result;
1796  result.only_run_at_level_zero = true;
1797  AppendVariablesToCumulativeCut(capacity, demands_helper, model, &result.vars);
1798  AddIntegerVariableFromIntervals(helper, model, &result.vars);
1800 
1801  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1802  result.generate_cuts =
1803  [integer_trail, helper, demands_helper, capacity, model](
1805  LinearConstraintManager* manager) {
1806  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1807  demands_helper->CacheAllEnergyValues();
1808 
1809  const IntegerValue capacity_max = integer_trail->UpperBound(capacity);
1810  auto generate_cuts = [&lp_values, model, manager, helper,
1811  demands_helper, capacity_max](bool mirror) {
1812  std::vector<CtEvent> events;
1813  for (int index = 0; index < helper->NumTasks(); ++index) {
1814  if (!helper->IsPresent(index)) continue;
1815  if (helper->SizeMin(index) > 0 &&
1816  demands_helper->DemandMin(index) > 0) {
1817  CtEvent event(index, helper);
1818  event.x_end = helper->Ends()[index];
1819  event.x_lp_end = event.x_end.LpValue(lp_values);
1820  event.y_min = IntegerValue(0);
1821  event.y_max = IntegerValue(capacity_max);
1822  event.y_size_min = demands_helper->DemandMin(index);
1823  event.energy_min = demands_helper->EnergyMin(index);
1824  event.decomposed_energy =
1825  demands_helper->DecomposedEnergies()[index];
1826  event.y_size_is_fixed = demands_helper->DemandIsFixed(index);
1827  events.push_back(event);
1828  }
1829  }
1830 
1831  const std::string mirror_str = mirror ? "Mirror" : "";
1833  absl::StrCat("CumulativeCompletionTimeExhaustive", mirror_str),
1834  lp_values, events, capacity_max, model, manager);
1835 
1837  absl::StrCat("CumulativeCompletionTimeQueyrane", mirror_str),
1838  lp_values, std::move(events),
1839  /*use_lifting=*/true, /*skip_low_sizes=*/true, model, manager);
1840  };
1841  if (!helper->SynchronizeAndSetTimeDirection(true)) return false;
1842  generate_cuts(false);
1843  if (!helper->SynchronizeAndSetTimeDirection(false)) return false;
1844  generate_cuts(true);
1845  return true;
1846  };
1847  return result;
1848 }
1849 
1850 // TODO(user): Use demands_helper and decomposed energy.
1852  const std::vector<IntervalVariable>& x_intervals,
1853  const std::vector<IntervalVariable>& y_intervals, Model* model) {
1854  SchedulingConstraintHelper* x_helper =
1855  model->GetOrCreate<IntervalsRepository>()->GetOrCreateHelper(x_intervals);
1856  SchedulingConstraintHelper* y_helper =
1857  model->GetOrCreate<IntervalsRepository>()->GetOrCreateHelper(y_intervals);
1858 
1859  CutGenerator result;
1860  result.only_run_at_level_zero = true;
1861  AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
1862  AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
1864 
1865  result.generate_cuts =
1866  [x_helper, y_helper, model](
1868  LinearConstraintManager* manager) {
1869  if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1870  if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1871 
1872  const int num_rectangles = x_helper->NumTasks();
1873  std::vector<int> active_rectangles;
1874  std::vector<IntegerValue> cached_areas(num_rectangles);
1875  std::vector<Rectangle> cached_rectangles(num_rectangles);
1876  for (int rect = 0; rect < num_rectangles; ++rect) {
1877  if (!y_helper->IsPresent(rect) || !y_helper->IsPresent(rect))
1878  continue;
1879 
1880  cached_areas[rect] =
1881  x_helper->SizeMin(rect) * y_helper->SizeMin(rect);
1882  if (cached_areas[rect] == 0) continue;
1883 
1884  // TODO(user): It might be possible/better to use some shifted value
1885  // here, but for now this code is not in the hot spot, so better be
1886  // defensive and only do connected components on really disjoint
1887  // rectangles.
1888  Rectangle& rectangle = cached_rectangles[rect];
1889  rectangle.x_min = x_helper->StartMin(rect);
1890  rectangle.x_max = x_helper->EndMax(rect);
1891  rectangle.y_min = y_helper->StartMin(rect);
1892  rectangle.y_max = y_helper->EndMax(rect);
1893 
1894  active_rectangles.push_back(rect);
1895  }
1896 
1897  if (active_rectangles.size() <= 1) return true;
1898 
1899  std::vector<absl::Span<int>> components =
1901  cached_rectangles, absl::MakeSpan(active_rectangles));
1902  for (absl::Span<int> rectangles : components) {
1903  if (rectangles.size() <= 1) continue;
1904 
1905  auto generate_cuts = [&lp_values, model, manager, &rectangles,
1906  &cached_areas](
1907  const std::string& cut_name,
1908  SchedulingConstraintHelper* x_helper,
1909  SchedulingConstraintHelper* y_helper) {
1910  std::vector<CtEvent> events;
1911 
1912  for (const int rect : rectangles) {
1913  CtEvent event(rect, x_helper);
1914  event.x_end = x_helper->Ends()[rect];
1915  event.x_lp_end = event.x_end.LpValue(lp_values);
1916  event.y_min = y_helper->StartMin(rect);
1917  event.y_max = y_helper->EndMax(rect);
1918  event.y_size_min = y_helper->SizeMin(rect);
1919 
1920  // TODO(user): Use improved energy from demands helper.
1921  event.energy_min = event.x_size_min * event.y_size_min;
1922  event.decomposed_energy = TryToDecomposeProduct(
1923  x_helper->Sizes()[rect], y_helper->Sizes()[rect], model);
1924  events.push_back(event);
1925  }
1926 
1928  cut_name, lp_values, std::move(events),
1929  /*use_lifting=*/false, /*skip_low_sizes=*/false, model,
1930  manager);
1931  };
1932 
1933  if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
1934  if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
1935  generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper);
1936  generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper);
1937  if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
1938  if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
1939  generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
1940  generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
1941  }
1942  return true;
1943  };
1944  return result;
1945 }
1946 
1947 } // namespace sat
1948 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
An Assignment is a variable -> domains mapping, used to report solutions to the user.
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
Definition: diffn_util.cc:496
ABSL_MUST_USE_RESULT bool LiteralOrNegationHasView(Literal lit, IntegerVariable *view=nullptr, bool *view_is_direct=nullptr) const
Definition: integer.cc:559
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue FixedValue(IntegerVariable i) const
Definition: integer.h:1569
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(const std::vector< LiteralValueValue > &product)
void AddLinearExpression(const LinearExpression &expr)
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
LiteralIndex Index() const
Definition: sat_base.h:90
void AddChoices(absl::Span< const int64_t > choices)
Definition: sat/util.cc:440
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:330
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:375
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:374
ABSL_MUST_USE_RESULT bool AddLinearizedDemand(int t, LinearConstraintBuilder *builder) const
Definition: intervals.cc:858
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
Definition: intervals.h:565
const std::vector< AffineExpression > & Demands() const
Definition: intervals.h:521
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
int interval_index
ModelSharedTimeLimit * time_limit
int64_t value
GRBmodel * model
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
static double ToDouble(double f)
Definition: lp_types.h:73
CutGenerator CreateCumulativeEnergyCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, const std::optional< AffineExpression > &makespan, Model *model)
bool ComputeMinSumOfWeightedEndMins(std::vector< PermutableEvent > &events, IntegerValue capacity_max, IntegerValue &min_sum_of_end_mins, IntegerValue &min_sum_of_weighted_end_mins, IntegerValue unweighted_threshold, IntegerValue weighted_threshold)
CutGenerator CreateNoOverlap2dEnergyCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
void GenerateShortCompletionTimeCutsWithExactBound(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CtEvent > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:41
void GenerateCumulativeEnergeticCutsWithMakespanAndFixedCapacity(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, IntegerValue capacity, AffineExpression makespan, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
CutGenerator CreateCumulativeCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void GenerateCutsBetweenPairOfNonOverlappingTasks(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CachedIntervalData > events, IntegerValue capacity_max, Model *model, LinearConstraintManager *manager)
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition: integer.h:1752
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(const std::vector< IntervalVariable > &x_intervals, const std::vector< IntervalVariable > &y_intervals, Model *model)
void GenerateCompletionTimeCutsWithEnergy(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< CtEvent > events, bool use_lifting, bool skip_low_sizes, Model *model, LinearConstraintManager *manager)
CutGenerator CreateCumulativeTimeTableCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, const std::vector< LiteralValueValue > &filtered_energy, IntegerValue window_start, IntegerValue window_end)
Definition: intervals.cc:647
void AppendVariablesToCumulativeCut(const AffineExpression &capacity, SchedulingDemandHelper *demands_helper, Model *model, std::vector< IntegerVariable > *vars)
void GenerateNoOverlap2dEnergyCut(const std::vector< std::vector< LiteralValueValue >> &energies, absl::Span< int > rectangles, const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model, LinearConstraintManager *manager, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *y_demands_helper)
void GenerateCumulativeEnergeticCuts(const std::string &cut_name, const absl::StrongVector< IntegerVariable, double > &lp_values, std::vector< EnergyEvent > events, const AffineExpression capacity, TimeLimit *time_limit, Model *model, LinearConstraintManager *manager)
std::vector< LiteralValueValue > TryToDecomposeProduct(const AffineExpression &left, const AffineExpression &right, Model *model)
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
double ToDouble(IntegerValue value)
Definition: integer.h:77
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
Collection of objects used to extend the Constraint Solver library.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
int64_t demand
Definition: resource.cc:126
int64_t energy
Definition: resource.cc:355
int64_t time
Definition: resource.cc:1694
int64_t capacity
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
AffineExpression Negated() const
Definition: integer.h:276
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
Definition: integer.h:296
const std::string DebugString() const
Definition: integer.h:304
std::vector< LiteralValueValue > decomposed_energy
BaseEvent(int t, SchedulingConstraintHelper *x_helper)
CachedIntervalData(int t, SchedulingConstraintHelper *helper)
std::vector< IntegerVariable > vars
Definition: cuts.h:50
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:54
EnergyEvent(int t, SchedulingConstraintHelper *x_helper)
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const
ABSL_MUST_USE_RESULT bool FillEnergyLp(AffineExpression x_size, const absl::StrongVector< IntegerVariable, double > &lp_values, Model *model)
double LpValue(const absl::StrongVector< IntegerVariable, double > &lp_values) const
#define VLOG(verboselevel)
Definition: vlog.h:39