Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
diffn_cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <string>
19#include <tuple>
20#include <utility>
21#include <vector>
22
23#include "absl/base/attributes.h"
24#include "absl/log/check.h"
25#include "absl/strings/str_cat.h"
26#include "absl/strings/string_view.h"
27#include "absl/types/span.h"
30#include "ortools/sat/cuts.h"
33#include "ortools/sat/integer.h"
37#include "ortools/sat/model.h"
41#include "ortools/sat/util.h"
43
44namespace operations_research {
45namespace sat {
46
47namespace {
48
49// Minimum amount of violation of the cut constraint by the solution. This
50// is needed to avoid numerical issues and adding cuts with minor effect.
51const double kMinCutViolation = 1e-4;
52
53} // namespace
54
56 const SchedulingConstraintHelper* x_helper)
57 : x_start_min(x_helper->StartMin(t)),
58 x_start_max(x_helper->StartMax(t)),
59 x_end_min(x_helper->EndMin(t)),
60 x_end_max(x_helper->EndMax(t)),
61 x_size_min(x_helper->SizeMin(t)) {}
62
65 : DiffnBaseEvent(t, x_helper) {}
66
67 // We need this for linearizing the energy in some cases.
69
70 // If set, this event is optional and its presence is controlled by this.
72
73 // A linear expression which is a valid lower bound on the total energy of
74 // this event. We also cache the activity of the expression to not recompute
75 // it all the time.
78
79 // True if linearized_energy is not exact and a McCormick relaxation.
80 bool energy_is_quadratic = false;
81
82 // Used to minimize the increase on the y axis for rectangles.
83 double y_spread = 0.0;
84
85 // The actual value of the presence literal of the interval(s) is checked
86 // when the event is created. A value of kNoLiteralIndex indicates that either
87 // the interval was not optional, or that its presence literal is true at
88 // level zero.
90
91 // Computes the mandatory minimal overlap of the interval with the time window
92 // [start, end].
93 IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const {
94 return std::max(std::min({x_end_min - start, end - x_start_max, x_size_min,
95 end - start}),
96 IntegerValue(0));
97 }
98
99 // This method expects all the other fields to have been filled before.
100 // It must be called before the EnergyEvent is used.
101 ABSL_MUST_USE_RESULT bool FillEnergyLp(
102 AffineExpression x_size,
104 Model* model) {
105 LinearConstraintBuilder tmp_energy(model);
106 if (IsPresent()) {
107 if (!decomposed_energy.empty()) {
108 if (!tmp_energy.AddDecomposedProduct(decomposed_energy)) return false;
109 } else {
110 tmp_energy.AddQuadraticLowerBound(x_size, y_size,
111 model->GetOrCreate<IntegerTrail>(),
113 }
114 } else {
116 energy_min)) {
117 return false;
118 }
119 }
120 linearized_energy = tmp_energy.BuildExpression();
122 return true;
123 }
124
125 std::string DebugString() const {
126 return absl::StrCat(
127 "DiffnEnergyEvent(x_start_min = ", x_start_min.value(),
128 ", x_start_max = ", x_start_max.value(),
129 ", x_end_min = ", x_end_min.value(),
130 ", x_end_max = ", x_end_max.value(), ", y_min = ", y_min.value(),
131 ", y_max = ", y_max.value(), ", y_size = ", y_size, ", energy = ",
132 decomposed_energy.empty()
133 ? "{}"
134 : absl::StrCat(decomposed_energy.size(), " terms"),
135 ", presence_literal_index = ", presence_literal_index.value(), ")");
136 }
137};
138
140 absl::Span<const std::vector<LiteralValueValue>> energies,
141 absl::Span<const int> rectangles, absl::string_view cut_name, Model* model,
144 SchedulingDemandHelper* y_demands_helper) {
145 std::vector<DiffnEnergyEvent> events;
146 const auto& lp_values = manager->LpValues();
147 for (const int rect : rectangles) {
148 if (y_helper->SizeMax(rect) == 0 || x_helper->SizeMax(rect) == 0) {
149 continue;
150 }
151
152 DiffnEnergyEvent e(rect, x_helper);
153 e.y_min = y_helper->StartMin(rect);
154 e.y_max = y_helper->EndMax(rect);
155 e.y_size = y_helper->Sizes()[rect];
156 e.decomposed_energy = energies[rect];
158 x_helper->IsPresent(rect)
159 ? (y_helper->IsPresent(rect)
161 : y_helper->PresenceLiteral(rect).Index())
162 : x_helper->PresenceLiteral(rect).Index();
163 e.y_size_min = y_helper->SizeMin(rect);
164 e.energy_min = y_demands_helper->EnergyMin(rect);
165 e.energy_is_quadratic = y_demands_helper->EnergyIsQuadratic(rect);
166
167 // We can always skip events.
168 if (!e.FillEnergyLp(x_helper->Sizes()[rect], lp_values, model)) continue;
169 events.push_back(e);
170 }
171
172 if (events.empty()) return;
173
174 // Compute y_spread.
175 double average_d = 0.0;
176 for (const auto& e : events) {
177 average_d += ToDouble(e.y_min + e.y_max);
178 }
179 const double average = average_d / 2.0 / static_cast<double>(events.size());
180 for (auto& e : events) {
181 e.y_spread = std::abs(ToDouble(e.y_max) - average) +
182 std::abs(average - ToDouble(e.y_min));
183 }
184
185 TopNCuts top_n_cuts(5);
186
187 std::sort(events.begin(), events.end(),
188 [](const DiffnEnergyEvent& a, const DiffnEnergyEvent& b) {
189 return std::tie(a.x_start_min, a.y_spread, a.x_end_max) <
190 std::tie(b.x_start_min, b.y_spread, b.x_end_max);
191 });
192
193 // The sum of all energies can be used to stop iterating early.
194 double sum_of_all_energies = 0.0;
195 for (const auto& e : events) {
196 sum_of_all_energies += e.linearized_energy_lp_value;
197 }
198
199 CapacityProfile capacity_profile;
200 for (int i1 = 0; i1 + 1 < events.size(); ++i1) {
201 // For each start time, we will keep the most violated cut generated while
202 // scanning the residual intervals.
203 int max_violation_end_index = -1;
204 double max_relative_violation = 1.0 + kMinCutViolation;
205 IntegerValue max_violation_window_start(0);
206 IntegerValue max_violation_window_end(0);
207 IntegerValue max_violation_y_min(0);
208 IntegerValue max_violation_y_max(0);
209 IntegerValue max_violation_area(0);
210 bool max_violation_use_precise_area = false;
211
212 // Accumulate intervals, areas, energies and check for potential cuts.
213 double energy_lp = 0.0;
214 IntegerValue window_min = kMaxIntegerValue;
215 IntegerValue window_max = kMinIntegerValue;
216 IntegerValue y_min = kMaxIntegerValue;
217 IntegerValue y_max = kMinIntegerValue;
218 capacity_profile.Clear();
219
220 // We sort all tasks (x_start_min(task) >= x_start_min(start_index) by
221 // increasing end max.
222 std::vector<DiffnEnergyEvent> residual_events(events.begin() + i1,
223 events.end());
224 std::sort(residual_events.begin(), residual_events.end(),
225 [](const DiffnEnergyEvent& a, const DiffnEnergyEvent& b) {
226 return std::tie(a.x_end_max, a.y_spread) <
227 std::tie(b.x_end_max, b.y_spread);
228 });
229 // Let's process residual tasks and evaluate the violation of the cut at
230 // each step. We follow the same structure as the cut creation code below.
231 for (int i2 = 0; i2 < residual_events.size(); ++i2) {
232 const DiffnEnergyEvent& e = residual_events[i2];
233 energy_lp += e.linearized_energy_lp_value;
234 window_min = std::min(window_min, e.x_start_min);
235 window_max = std::max(window_max, e.x_end_max);
236 y_min = std::min(y_min, e.y_min);
237 y_max = std::max(y_max, e.y_max);
238 capacity_profile.AddRectangle(e.x_start_min, e.x_end_max, e.y_min,
239 e.y_max);
240
241 // Dominance rule. If the next interval also fits in
242 // [window_min, window_max]*[y_min, y_max], the cut will be stronger with
243 // the next interval/rectangle.
244 if (i2 + 1 < residual_events.size() &&
245 residual_events[i2 + 1].x_start_min >= window_min &&
246 residual_events[i2 + 1].x_end_max <= window_max &&
247 residual_events[i2 + 1].y_min >= y_min &&
248 residual_events[i2 + 1].y_max <= y_max) {
249 continue;
250 }
251
252 // Checks the current area vs the sum of all energies.
253 // The area is capacity_profile.GetBoundingArea().
254 // We can compare it to the bounding box area:
255 // (window_max - window_min) * (y_max - y_min).
256 bool use_precise_area = false;
257 IntegerValue precise_area(0);
258 double area_lp = 0.0;
259 const IntegerValue bbox_area =
260 (window_max - window_min) * (y_max - y_min);
261 precise_area = capacity_profile.GetBoundingArea();
262 use_precise_area = precise_area < bbox_area;
263 area_lp = ToDouble(std::min(precise_area, bbox_area));
264
265 if (area_lp >= sum_of_all_energies) {
266 break;
267 }
268
269 // Compute the violation of the potential cut.
270 const double relative_violation = energy_lp / area_lp;
271 if (relative_violation > max_relative_violation) {
272 max_violation_end_index = i2;
273 max_relative_violation = relative_violation;
274 max_violation_window_start = window_min;
275 max_violation_window_end = window_max;
276 max_violation_y_min = y_min;
277 max_violation_y_max = y_max;
278 max_violation_area = std::min(precise_area, bbox_area);
279 max_violation_use_precise_area = use_precise_area;
280 }
281 }
282
283 if (max_violation_end_index == -1) continue;
284
285 // A maximal violated cut has been found.
286 // Build it and add it to the pool.
287 bool add_opt_to_name = false;
288 bool add_quadratic_to_name = false;
289 bool add_energy_to_name = false;
290 LinearConstraintBuilder cut(model, kMinIntegerValue, max_violation_area);
291 for (int i2 = 0; i2 <= max_violation_end_index; ++i2) {
292 const DiffnEnergyEvent& event = residual_events[i2];
293 cut.AddLinearExpression(event.linearized_energy);
294 if (!event.IsPresent()) add_opt_to_name = true;
295 if (event.energy_is_quadratic) add_quadratic_to_name = true;
296 if (event.energy_min > event.x_size_min * event.y_size_min) {
297 add_energy_to_name = true;
298 }
299 }
300 std::string full_name(cut_name);
301 if (add_opt_to_name) full_name.append("_optional");
302 if (add_quadratic_to_name) full_name.append("_quadratic");
303 if (add_energy_to_name) full_name.append("_energy");
304 if (max_violation_use_precise_area) full_name.append("_precise");
305 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
306 }
307 top_n_cuts.TransferToManager(manager);
308}
309
311 NoOverlap2DConstraintHelper* helper, Model* model) {
312 CutGenerator result;
313 result.only_run_at_level_zero = true;
315 &helper->x_helper(), model, &result.vars,
318 &helper->y_helper(), model, &result.vars,
321 ProductDecomposer* product_decomposer =
323 result.generate_cuts = [helper, model, product_decomposer](
324 LinearConstraintManager* manager) {
325 const int num_rectangles = helper->NumBoxes();
326 std::vector<std::vector<LiteralValueValue>> energies(num_rectangles);
327 // TODO(user): We could compute this once and for all in the helper.
328 for (int i = 0; i < num_rectangles; ++i) {
329 energies[i] = product_decomposer->TryToDecompose(
330 helper->x_helper().Sizes()[i], helper->y_helper().Sizes()[i]);
331 }
332 if (!helper->SynchronizeAndSetDirection(true, true, false)) return false;
333 SchedulingDemandHelper* x_demands_helper = &helper->x_demands_helper();
334 SchedulingDemandHelper* y_demands_helper = &helper->y_demands_helper();
335 if (!x_demands_helper->CacheAllEnergyValues()) return true;
336 if (!y_demands_helper->CacheAllEnergyValues()) return true;
337
338 std::vector<int> rectangles;
339 rectangles.reserve(num_rectangles);
340 for (const auto& component :
342 for (const int rect : component) {
343 rectangles.clear();
344 if (helper->IsAbsent(rect)) continue;
345 // We do not consider rectangles controlled by 2 different unassigned
346 // enforcement literals.
347 if (!helper->x_helper().IsPresent(rect) &&
348 !helper->y_helper().IsPresent(rect) &&
349 helper->x_helper().PresenceLiteral(rect) !=
350 helper->y_helper().PresenceLiteral(rect)) {
351 continue;
352 }
353
354 rectangles.push_back(rect);
355 }
356
357 if (rectangles.size() <= 1) continue;
358
359 GenerateNoOverlap2dEnergyCut(energies, rectangles, "NoOverlap2dXEnergy",
360 model, manager, &helper->x_helper(),
361 &helper->y_helper(), y_demands_helper);
362 GenerateNoOverlap2dEnergyCut(energies, rectangles, "NoOverlap2dYEnergy",
363 model, manager, &helper->y_helper(),
364 &helper->x_helper(), x_demands_helper);
365 }
366 return true;
367 };
368 return result;
369}
370
372 : DiffnBaseEvent(t, x_helper) {}
373
374std::string DiffnCtEvent::DebugString() const {
375 return absl::StrCat(
376 "DiffnCtEvent(x_end = ", x_end, ", x_start_min = ", x_start_min.value(),
377 ", x_start_max = ", x_start_max.value(),
378 ", x_size_min = ", x_size_min.value(), ", x_lp_end = ", x_lp_end,
379 ", y_min = ", y_min.value(), ", y_max = ", y_max.value(),
380 ", y_size_min = ", y_size_min.value(),
381 ", energy_min = ", energy_min.value(), ", use_energy = ", use_energy,
382 ", lifted = ", lifted);
383}
384
385// We generate the cut from the Smith's rule from:
386// M. Queyranne, Structure of a simple scheduling polyhedron,
387// Mathematical Programming 58 (1993), 263–285
388//
389// The original cut is:
390// sum(end_min_i * duration_min_i) >=
391// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
392//
393// Let's build a figure where each horizontal rectangle represent a task. It
394// ends at the end of the task, and its height is the duration of the task.
395// For a given order, we pack each rectangle to the left while not overlapping,
396// that is one rectangle starts when the previous one ends.
397//
398// e1
399// -----
400// :\ | s1
401// : \| e2
402// -------------
403// :\ |
404// : \ | s2
405// : \| e3
406// ----------------
407// : \| s3
408// ----------------
409//
410// We can notice that the total area is independent of the order of tasks.
411// The first term of the rhs is the area above the diagonal.
412// The second term of the rhs is the area below the diagonal.
413//
414// We apply the following changes (see the code for cumulative constraints):
415// - we strengthen this cuts by noticing that if all tasks starts after S,
416// then replacing end_min_i by (end_min_i - S) is still valid.
417// - we lift rectangles that start before the start of the sequence, but must
418// overlap with it.
419// - we apply the same transformation that was applied to the cumulative
420// constraint to use the no_overlap cut in the no_overlap_2d setting.
421// - we use a limited complexity subset-sum to compute reachable capacity
422// - we look at a set of intervals starting after a given start_min, sorted by
423// relative (end_lp - start_min).
424void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name,
425 std::vector<DiffnCtEvent> events,
426 bool use_lifting, Model* model,
427 LinearConstraintManager* manager) {
428 TopNCuts top_n_cuts(5);
429
430 // Sort by start min to bucketize by start_min.
431 std::sort(events.begin(), events.end(),
432 [](const DiffnCtEvent& e1, const DiffnCtEvent& e2) {
433 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
434 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
435 });
436 for (int start = 0; start + 1 < events.size(); ++start) {
437 // Skip to the next bucket (of start_min).
438 if (start > 0 &&
439 events[start].x_start_min == events[start - 1].x_start_min) {
440 continue;
441 }
442
443 const IntegerValue sequence_start_min = events[start].x_start_min;
444 std::vector<DiffnCtEvent> residual_tasks(events.begin() + start,
445 events.end());
446
447 // We look at event that start before sequence_start_min, but are forced
448 // to cross this time point. In that case, we replace this event by a
449 // truncated event starting at sequence_start_min. To do this, we reduce
450 // the size_min, align the start_min with the sequence_start_min, and
451 // scale the energy down accordingly.
452 if (use_lifting) {
453 for (int before = 0; before < start; ++before) {
454 if (events[before].x_start_min + events[before].x_size_min >
455 sequence_start_min) {
456 // Build the vector of energies as the vector of sizes.
457 DiffnCtEvent event = events[before]; // Copy.
458 event.lifted = true;
459 event.energy_min = ComputeEnergyMinInWindow(
460 event.x_start_min, event.x_start_max, event.x_end_min,
461 event.x_end_max, event.x_size_min, event.y_size_min,
462 event.decomposed_energy, sequence_start_min, event.x_end_max);
463 event.x_size_min =
464 event.x_size_min + event.x_start_min - sequence_start_min;
465 event.x_start_min = sequence_start_min;
466 if (event.energy_min > event.x_size_min * event.y_size_min) {
467 event.use_energy = true;
468 }
469 DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
470 if (event.energy_min <= 0) continue;
471 residual_tasks.push_back(event);
472 }
473 }
474 }
475
476 std::sort(residual_tasks.begin(), residual_tasks.end(),
477 [](const DiffnCtEvent& e1, const DiffnCtEvent& e2) {
478 return e1.x_lp_end < e2.x_lp_end;
479 });
480
481 // Best cut so far for this loop.
482 int best_end = -1;
483 double best_efficacy = 0.01;
484 IntegerValue best_min_total_area = 0;
485 bool best_use_subset_sum = false;
486
487 // Used in the first term of the rhs of the equation.
488 IntegerValue sum_event_areas = 0;
489 // Used in the second term of the rhs of the equation.
490 IntegerValue sum_energy = 0;
491 // For normalization.
492 IntegerValue sum_square_energy = 0;
493
494 double lp_contrib = 0.0;
495 IntegerValue current_start_min(kMaxIntegerValue);
496 IntegerValue y_min_of_subset = kMaxIntegerValue;
497 IntegerValue y_max_of_subset = kMinIntegerValue;
498 IntegerValue sum_of_y_size_min = 0;
499
500 bool use_dp = true;
502
503 for (int i = 0; i < residual_tasks.size(); ++i) {
504 const DiffnCtEvent& event = residual_tasks[i];
505 DCHECK_GE(event.x_start_min, sequence_start_min);
506 // Make sure we do not overflow.
507 if (!AddTo(event.energy_min, &sum_energy)) break;
508 if (!AddProductTo(event.energy_min, event.x_size_min, &sum_event_areas)) {
509 break;
510 }
511 if (!AddSquareTo(event.energy_min, &sum_square_energy)) break;
512 if (!AddTo(event.y_size_min, &sum_of_y_size_min)) break;
513
514 lp_contrib += event.x_lp_end * ToDouble(event.energy_min);
515 current_start_min = std::min(current_start_min, event.x_start_min);
516
517 // For the capacity, we use the worse |y_max - y_min| and if all the tasks
518 // so far have a fixed demand with a gcd > 1, we can round it down.
519 y_min_of_subset = std::min(y_min_of_subset, event.y_min);
520 y_max_of_subset = std::max(y_max_of_subset, event.y_max);
521 if (!event.y_size_is_fixed) use_dp = false;
522 if (use_dp) {
523 if (i == 0) {
524 dp.Reset((y_max_of_subset - y_min_of_subset).value());
525 } else {
526 // TODO(user): Can we increase the bound dynamically ?
527 if (y_max_of_subset - y_min_of_subset > dp.Bound()) {
528 use_dp = false;
529 }
530 }
531 }
532 if (use_dp) {
533 dp.Add(event.y_size_min.value());
534 }
535
536 const IntegerValue reachable_capacity =
537 use_dp ? IntegerValue(dp.CurrentMax())
538 : y_max_of_subset - y_min_of_subset;
539
540 // If we have not reached capacity, there can be no cuts on ends.
541 if (sum_of_y_size_min <= reachable_capacity) continue;
542
543 // Do we have a violated cut ?
544 const IntegerValue square_sum_energy = CapProdI(sum_energy, sum_energy);
545 if (AtMinOrMaxInt64I(square_sum_energy)) break;
546 const IntegerValue rhs_second_term =
547 CeilRatio(square_sum_energy, reachable_capacity);
548
549 IntegerValue min_total_area = CapAddI(sum_event_areas, rhs_second_term);
550 if (AtMinOrMaxInt64I(min_total_area)) break;
551 min_total_area = CeilRatio(min_total_area, 2);
552
553 // shift contribution by current_start_min.
554 if (!AddProductTo(sum_energy, current_start_min, &min_total_area)) break;
555
556 // The efficacy of the cut is the normalized violation of the above
557 // equation. We will normalize by the sqrt of the sum of squared energies.
558 const double efficacy = (ToDouble(min_total_area) - lp_contrib) /
559 std::sqrt(ToDouble(sum_square_energy));
560
561 // For a given start time, we only keep the best cut.
562 // The reason is that if the cut is strongly violated, we can get a
563 // sequence of violated cuts as we add more tasks. These new cuts will
564 // be less violated, but will not bring anything useful to the LP
565 // relaxation. At the same time, this sequence of cuts can push out
566 // other cuts from a disjoint set of tasks.
567 if (efficacy > best_efficacy) {
568 best_efficacy = efficacy;
569 best_end = i;
570 best_min_total_area = min_total_area;
571 best_use_subset_sum =
572 reachable_capacity < y_max_of_subset - y_min_of_subset;
573 }
574 }
575 if (best_end != -1) {
576 LinearConstraintBuilder cut(model, best_min_total_area, kMaxIntegerValue);
577 bool is_lifted = false;
578 bool add_energy_to_name = false;
579 for (int i = 0; i <= best_end; ++i) {
580 const DiffnCtEvent& event = residual_tasks[i];
581 is_lifted |= event.lifted;
582 add_energy_to_name |= event.use_energy;
583 cut.AddTerm(event.x_end, event.energy_min);
584 }
585 std::string full_name(cut_name);
586 if (is_lifted) full_name.append("_lifted");
587 if (add_energy_to_name) full_name.append("_energy");
588 if (best_use_subset_sum) full_name.append("_subsetsum");
589 top_n_cuts.AddCut(cut.Build(), full_name, manager->LpValues());
590 }
591 }
592 top_n_cuts.TransferToManager(manager);
593}
594
596 NoOverlap2DConstraintHelper* helper, Model* model) {
597 CutGenerator result;
598 result.only_run_at_level_zero = true;
600 &helper->x_helper(), model, &result.vars,
603 &helper->y_helper(), model, &result.vars,
606
607 auto* product_decomposer = model->GetOrCreate<ProductDecomposer>();
608 result.generate_cuts = [helper, product_decomposer,
609 model](LinearConstraintManager* manager) {
610 if (!helper->SynchronizeAndSetDirection()) {
611 return false;
612 }
613
614 const int num_rectangles = helper->NumBoxes();
615 std::vector<int> rectangles;
616 rectangles.reserve(num_rectangles);
617 const SchedulingConstraintHelper* x_helper = &helper->x_helper();
618 const SchedulingConstraintHelper* y_helper = &helper->y_helper();
619 for (const auto& component :
620 helper->connected_components().AsVectorOfSpan()) {
621 rectangles.clear();
622 if (component.size() <= 1) continue;
623 for (int rect : component) {
624 if (!helper->IsPresent(rect)) continue;
625 if (x_helper->SizeMin(rect) == 0 || y_helper->SizeMin(rect) == 0) {
626 continue;
627 }
628 rectangles.push_back(rect);
629 }
630
631 auto generate_cuts = [product_decomposer, manager, model, helper,
632 &rectangles](absl::string_view cut_name) {
633 std::vector<DiffnCtEvent> events;
634
635 const SchedulingConstraintHelper* x_helper = &helper->x_helper();
636 const SchedulingConstraintHelper* y_helper = &helper->y_helper();
637 const auto& lp_values = manager->LpValues();
638 for (const int rect : rectangles) {
639 DiffnCtEvent event(rect, x_helper);
640 event.x_end = x_helper->Ends()[rect];
641 event.x_lp_end = event.x_end.LpValue(lp_values);
642 event.y_min = y_helper->StartMin(rect);
643 event.y_max = y_helper->EndMax(rect);
644 event.y_size_min = y_helper->SizeMin(rect);
645
646 // TODO(user): Use improved energy from demands helper.
647 event.energy_min = event.x_size_min * event.y_size_min;
648 event.decomposed_energy = product_decomposer->TryToDecompose(
649 x_helper->Sizes()[rect], y_helper->Sizes()[rect]);
650 events.push_back(event);
651 }
652
653 GenerateNoOvelap2dCompletionTimeCuts(cut_name, std::move(events),
654 /*use_lifting=*/true, model,
655 manager);
656 };
657
658 if (!helper->SynchronizeAndSetDirection(true, true, false)) {
659 return false;
660 }
661 generate_cuts("NoOverlap2dXCompletionTime");
662 if (!helper->SynchronizeAndSetDirection(true, true, true)) {
663 return false;
664 }
665 generate_cuts("NoOverlap2dYCompletionTime");
666 if (!helper->SynchronizeAndSetDirection(false, false, false)) {
667 return false;
668 }
669 generate_cuts("NoOverlap2dXCompletionTime");
670 if (!helper->SynchronizeAndSetDirection(false, false, true)) {
671 return false;
672 }
673 generate_cuts("NoOverlap2dYCompletionTime");
674 }
675 return true;
676 };
677 return result;
678}
679
680} // namespace sat
681} // namespace operations_research
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
std::vector< absl::Span< const V > > AsVectorOfSpan() const
Definition util.h:1019
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddLinearExpression(const LinearExpression &expr)
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(absl::Span< const LiteralValueValue > product)
const util_intops::StrongVector< IntegerVariable, double > & LpValues()
LiteralIndex Index() const
Definition sat_base.h:92
const CompactVectorVector< int > & connected_components() const
bool SynchronizeAndSetDirection(bool x_is_forward_after_swap=true, bool y_is_forward_after_swap=true, bool swap_x_and_y=false)
std::vector< LiteralValueValue > TryToDecompose(const AffineExpression &left, const AffineExpression &right)
absl::Span< const AffineExpression > Sizes() const
void TransferToManager(LinearConstraintManager *manager)
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
bool AddSquareTo(IntegerValue a, IntegerValue *result)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool AddTo(IntegerValue a, IntegerValue *result)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
CutGenerator CreateNoOverlap2dEnergyCutGenerator(NoOverlap2DConstraintHelper *helper, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void GenerateNoOverlap2dEnergyCut(absl::Span< const std::vector< LiteralValueValue > > energies, absl::Span< const int > rectangles, absl::string_view cut_name, Model *model, LinearConstraintManager *manager, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *y_demands_helper)
void AddIntegerVariableFromIntervals(const SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars, int mask)
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(NoOverlap2DConstraintHelper *helper, Model *model)
bool AtMinOrMaxInt64I(IntegerValue t)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
double ToDouble(IntegerValue value)
void GenerateNoOvelap2dCompletionTimeCuts(absl::string_view cut_name, std::vector< DiffnCtEvent > events, bool use_lifting, Model *model, LinearConstraintManager *manager)
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, absl::Span< const LiteralValueValue > filtered_energy, IntegerValue window_start, IntegerValue window_end)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
Definition cuts.h:60
std::vector< IntegerVariable > vars
Definition cuts.h:59
std::vector< LiteralValueValue > decomposed_energy
Definition diffn_cuts.h:74
DiffnBaseEvent(int t, const SchedulingConstraintHelper *x_helper)
Definition diffn_cuts.cc:55
DiffnCtEvent(int t, const SchedulingConstraintHelper *x_helper)
DiffnEnergyEvent(int t, const SchedulingConstraintHelper *x_helper)
Definition diffn_cuts.cc:64
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const
Definition diffn_cuts.cc:93
ABSL_MUST_USE_RESULT bool FillEnergyLp(AffineExpression x_size, const util_intops::StrongVector< IntegerVariable, double > &lp_values, Model *model)