Google OR-Tools v9.11
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-2024 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 <functional>
19#include <string>
20#include <tuple>
21#include <utility>
22#include <vector>
23
24#include "absl/base/attributes.h"
25#include "absl/log/check.h"
26#include "absl/strings/str_cat.h"
27#include "absl/strings/string_view.h"
28#include "absl/types/span.h"
31#include "ortools/sat/cuts.h"
34#include "ortools/sat/integer.h"
38#include "ortools/sat/model.h"
40#include "ortools/sat/util.h"
42
43namespace operations_research {
44namespace sat {
45
46namespace {
47
48// Minimum amount of violation of the cut constraint by the solution. This
49// is needed to avoid numerical issues and adding cuts with minor effect.
50const double kMinCutViolation = 1e-4;
51
52} // namespace
53
55 : x_start_min(x_helper->StartMin(t)),
56 x_start_max(x_helper->StartMax(t)),
57 x_end_min(x_helper->EndMin(t)),
58 x_end_max(x_helper->EndMax(t)),
59 x_size_min(x_helper->SizeMin(t)) {}
60
63 : DiffnBaseEvent(t, x_helper) {}
64
65 // We need this for linearizing the energy in some cases.
67
68 // If set, this event is optional and its presence is controlled by this.
70
71 // A linear expression which is a valid lower bound on the total energy of
72 // this event. We also cache the activity of the expression to not recompute
73 // it all the time.
76
77 // True if linearized_energy is not exact and a McCormick relaxation.
78 bool energy_is_quadratic = false;
79
80 // Used to minimize the increase on the y axis for rectangles.
81 double y_spread = 0.0;
82
83 // The actual value of the presence literal of the interval(s) is checked
84 // when the event is created. A value of kNoLiteralIndex indicates that either
85 // the interval was not optional, or that its presence literal is true at
86 // level zero.
88
89 // Computes the mandatory minimal overlap of the interval with the time window
90 // [start, end].
91 IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const {
92 return std::max(std::min({x_end_min - start, end - x_start_max, x_size_min,
93 end - start}),
94 IntegerValue(0));
95 }
96
97 // This method expects all the other fields to have been filled before.
98 // It must be called before the EnergyEvent is used.
99 ABSL_MUST_USE_RESULT bool FillEnergyLp(
100 AffineExpression x_size,
102 Model* model) {
103 LinearConstraintBuilder tmp_energy(model);
104 if (IsPresent()) {
105 if (!decomposed_energy.empty()) {
106 if (!tmp_energy.AddDecomposedProduct(decomposed_energy)) return false;
107 } else {
108 tmp_energy.AddQuadraticLowerBound(x_size, y_size,
109 model->GetOrCreate<IntegerTrail>(),
111 }
112 } else {
114 energy_min)) {
115 return false;
116 }
117 }
118 linearized_energy = tmp_energy.BuildExpression();
120 return true;
121 }
122
123 std::string DebugString() const {
124 return absl::StrCat(
125 "DiffnEnergyEvent(x_start_min = ", x_start_min.value(),
126 ", x_start_max = ", x_start_max.value(),
127 ", x_end_min = ", x_end_min.value(),
128 ", x_end_max = ", x_end_max.value(), ", y_min = ", y_min.value(),
129 ", y_max = ", y_max.value(), ", y_size = ", y_size.DebugString(),
130 ", energy = ",
131 decomposed_energy.empty()
132 ? "{}"
133 : absl::StrCat(decomposed_energy.size(), " terms"),
134 ", presence_literal_index = ", presence_literal_index.value(), ")");
135 }
136};
137
139 absl::Span<const std::vector<LiteralValueValue>> energies,
140 absl::Span<int> rectangles, absl::string_view cut_name, Model* model,
143 SchedulingDemandHelper* y_demands_helper) {
144 std::vector<DiffnEnergyEvent> events;
145 const auto& lp_values = manager->LpValues();
146 for (const int rect : rectangles) {
147 if (y_helper->SizeMax(rect) == 0 || x_helper->SizeMax(rect) == 0) {
148 continue;
149 }
150
151 DiffnEnergyEvent e(rect, x_helper);
152 e.y_min = y_helper->StartMin(rect);
153 e.y_max = y_helper->EndMax(rect);
154 e.y_size = y_helper->Sizes()[rect];
155 e.decomposed_energy = energies[rect];
157 x_helper->IsPresent(rect)
158 ? (y_helper->IsPresent(rect)
160 : y_helper->PresenceLiteral(rect).Index())
161 : x_helper->PresenceLiteral(rect).Index();
162 e.y_size_min = y_helper->SizeMin(rect);
163 e.energy_min = y_demands_helper->EnergyMin(rect);
164 e.energy_is_quadratic = y_demands_helper->EnergyIsQuadratic(rect);
165
166 // We can always skip events.
167 if (!e.FillEnergyLp(x_helper->Sizes()[rect], lp_values, model)) continue;
168 events.push_back(e);
169 }
170
171 if (events.empty()) return;
172
173 // Compute y_spread.
174 double average_d = 0.0;
175 for (const auto& e : events) {
176 average_d += ToDouble(e.y_min + e.y_max);
177 }
178 const double average = average_d / 2.0 / static_cast<double>(events.size());
179 for (auto& e : events) {
180 e.y_spread = std::abs(ToDouble(e.y_max) - average) +
181 std::abs(average - ToDouble(e.y_min));
182 }
183
184 TopNCuts top_n_cuts(5);
185
186 std::sort(events.begin(), events.end(),
187 [](const DiffnEnergyEvent& a, const DiffnEnergyEvent& b) {
188 return std::tie(a.x_start_min, a.y_spread, a.x_end_max) <
189 std::tie(b.x_start_min, b.y_spread, b.x_end_max);
190 });
191
192 // The sum of all energies can be used to stop iterating early.
193 double sum_of_all_energies = 0.0;
194 for (const auto& e : events) {
195 sum_of_all_energies += e.linearized_energy_lp_value;
196 }
197
198 CapacityProfile capacity_profile;
199 for (int i1 = 0; i1 + 1 < events.size(); ++i1) {
200 // For each start time, we will keep the most violated cut generated while
201 // scanning the residual intervals.
202 int max_violation_end_index = -1;
203 double max_relative_violation = 1.0 + kMinCutViolation;
204 IntegerValue max_violation_window_start(0);
205 IntegerValue max_violation_window_end(0);
206 IntegerValue max_violation_y_min(0);
207 IntegerValue max_violation_y_max(0);
208 IntegerValue max_violation_area(0);
209 bool max_violation_use_precise_area = false;
210
211 // Accumulate intervals, areas, energies and check for potential cuts.
212 double energy_lp = 0.0;
213 IntegerValue window_min = kMaxIntegerValue;
214 IntegerValue window_max = kMinIntegerValue;
215 IntegerValue y_min = kMaxIntegerValue;
216 IntegerValue y_max = kMinIntegerValue;
217 capacity_profile.Clear();
218
219 // We sort all tasks (x_start_min(task) >= x_start_min(start_index) by
220 // increasing end max.
221 std::vector<DiffnEnergyEvent> residual_events(events.begin() + i1,
222 events.end());
223 std::sort(residual_events.begin(), residual_events.end(),
224 [](const DiffnEnergyEvent& a, const DiffnEnergyEvent& b) {
225 return std::tie(a.x_end_max, a.y_spread) <
226 std::tie(b.x_end_max, b.y_spread);
227 });
228 // Let's process residual tasks and evaluate the violation of the cut at
229 // each step. We follow the same structure as the cut creation code below.
230 for (int i2 = 0; i2 < residual_events.size(); ++i2) {
231 const DiffnEnergyEvent& e = residual_events[i2];
232 energy_lp += e.linearized_energy_lp_value;
233 window_min = std::min(window_min, e.x_start_min);
234 window_max = std::max(window_max, e.x_end_max);
235 y_min = std::min(y_min, e.y_min);
236 y_max = std::max(y_max, e.y_max);
237 capacity_profile.AddRectangle(e.x_start_min, e.x_end_max, e.y_min,
238 e.y_max);
239
240 // Dominance rule. If the next interval also fits in
241 // [window_min, window_max]*[y_min, y_max], the cut will be stronger with
242 // the next interval/rectangle.
243 if (i2 + 1 < residual_events.size() &&
244 residual_events[i2 + 1].x_start_min >= window_min &&
245 residual_events[i2 + 1].x_end_max <= window_max &&
246 residual_events[i2 + 1].y_min >= y_min &&
247 residual_events[i2 + 1].y_max <= y_max) {
248 continue;
249 }
250
251 // Checks the current area vs the sum of all energies.
252 // The area is capacity_profile.GetBoundingArea().
253 // We can compare it to the bounding box area:
254 // (window_max - window_min) * (y_max - y_min).
255 bool use_precise_area = false;
256 IntegerValue precise_area(0);
257 double area_lp = 0.0;
258 const IntegerValue bbox_area =
259 (window_max - window_min) * (y_max - y_min);
260 precise_area = capacity_profile.GetBoundingArea();
261 use_precise_area = precise_area < bbox_area;
262 area_lp = ToDouble(std::min(precise_area, bbox_area));
263
264 if (area_lp >= sum_of_all_energies) {
265 break;
266 }
267
268 // Compute the violation of the potential cut.
269 const double relative_violation = energy_lp / area_lp;
270 if (relative_violation > max_relative_violation) {
271 max_violation_end_index = i2;
272 max_relative_violation = relative_violation;
273 max_violation_window_start = window_min;
274 max_violation_window_end = window_max;
275 max_violation_y_min = y_min;
276 max_violation_y_max = y_max;
277 max_violation_area = std::min(precise_area, bbox_area);
278 max_violation_use_precise_area = use_precise_area;
279 }
280 }
281
282 if (max_violation_end_index == -1) continue;
283
284 // A maximal violated cut has been found.
285 // Build it and add it to the pool.
286 bool add_opt_to_name = false;
287 bool add_quadratic_to_name = false;
288 bool add_energy_to_name = false;
289 LinearConstraintBuilder cut(model, kMinIntegerValue, max_violation_area);
290 for (int i2 = 0; i2 <= max_violation_end_index; ++i2) {
291 const DiffnEnergyEvent& event = residual_events[i2];
292 cut.AddLinearExpression(event.linearized_energy);
293 if (!event.IsPresent()) add_opt_to_name = true;
294 if (event.energy_is_quadratic) add_quadratic_to_name = true;
295 if (event.energy_min > event.x_size_min * event.y_size_min) {
296 add_energy_to_name = true;
297 }
298 }
299 std::string full_name(cut_name);
300 if (add_opt_to_name) full_name.append("_optional");
301 if (add_quadratic_to_name) full_name.append("_quadratic");
302 if (add_energy_to_name) full_name.append("_energy");
303 if (max_violation_use_precise_area) full_name.append("_precise");
304 top_n_cuts.AddCut(cut.Build(), full_name, lp_values);
305 }
306 top_n_cuts.TransferToManager(manager);
307}
308
311 SchedulingDemandHelper* x_demands_helper,
312 SchedulingDemandHelper* y_demands_helper,
313 const std::vector<std::vector<LiteralValueValue>>& energies, Model* model) {
314 CutGenerator result;
315 result.only_run_at_level_zero = true;
316 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
317 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
319
320 result.generate_cuts = [x_helper, y_helper, x_demands_helper,
321 y_demands_helper, model,
322 energies](LinearConstraintManager* manager) {
323 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
324 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
325 x_demands_helper->CacheAllEnergyValues();
326 y_demands_helper->CacheAllEnergyValues();
327
328 const int num_rectangles = x_helper->NumTasks();
329 std::vector<int> active_rectangles;
330 std::vector<Rectangle> cached_rectangles(num_rectangles);
331 for (int rect = 0; rect < num_rectangles; ++rect) {
332 if (y_helper->IsAbsent(rect) || y_helper->IsAbsent(rect)) continue;
333 // We do not consider rectangles controlled by 2 different unassigned
334 // enforcement literals.
335 if (!x_helper->IsPresent(rect) && !y_helper->IsPresent(rect) &&
336 x_helper->PresenceLiteral(rect) != y_helper->PresenceLiteral(rect)) {
337 continue;
338 }
339
340 // TODO(user): It might be possible/better to use some shifted value
341 // here, but for now this code is not in the hot spot, so better be
342 // defensive and only do connected components on really disjoint
343 // rectangles.
344 Rectangle& rectangle = cached_rectangles[rect];
345 rectangle.x_min = x_helper->StartMin(rect);
346 rectangle.x_max = x_helper->EndMax(rect);
347 rectangle.y_min = y_helper->StartMin(rect);
348 rectangle.y_max = y_helper->EndMax(rect);
349
350 active_rectangles.push_back(rect);
351 }
352
353 if (active_rectangles.size() <= 1) return true;
354
355 std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
356 cached_rectangles, absl::MakeSpan(active_rectangles));
357
358 // Forward pass. No need to do a backward pass.
359 for (absl::Span<int> rectangles : components) {
360 if (rectangles.size() <= 1) continue;
361
362 GenerateNoOverlap2dEnergyCut(energies, rectangles, "NoOverlap2dXEnergy",
363 model, manager, x_helper, y_helper,
364 y_demands_helper);
365 GenerateNoOverlap2dEnergyCut(energies, rectangles, "NoOverlap2dYEnergy",
366 model, manager, y_helper, x_helper,
367 x_demands_helper);
368 }
369
370 return true;
371 };
372 return result;
373}
374
377
378std::string DiffnCtEvent::DebugString() const {
379 return absl::StrCat("DiffnCtEvent(x_end = ", x_end.DebugString(),
380 ", x_start_min = ", x_start_min.value(),
381 ", x_start_max = ", x_start_max.value(),
382 ", x_size_min = ", x_size_min.value(),
383 ", x_lp_end = ", x_lp_end, ", y_min = ", y_min.value(),
384 ", y_max = ", y_max.value(),
385 ", y_size_min = ", y_size_min.value(),
386 ", energy_min = ", energy_min.value(),
387 ", use_energy = ", use_energy, ", lifted = ", lifted);
388}
389
390// We generate the cut from the Smith's rule from:
391// M. Queyranne, Structure of a simple scheduling polyhedron,
392// Mathematical Programming 58 (1993), 263–285
393//
394// The original cut is:
395// sum(end_min_i * duration_min_i) >=
396// (sum(duration_min_i^2) + sum(duration_min_i)^2) / 2
397// We strengthen this cuts by noticing that if all tasks starts after S,
398// then replacing end_min_i by (end_min_i - S) is still valid.
399//
400// A second difference is that we look at a set of intervals starting
401// after a given start_min, sorted by relative (end_lp - start_min).
402//
403// TODO(user): merge with Packing cuts.
405 absl::string_view cut_name, std::vector<DiffnCtEvent> events,
406 bool use_lifting, bool skip_low_sizes, Model* model,
407 LinearConstraintManager* manager) {
408 TopNCuts top_n_cuts(5);
409
410 // Sort by start min to bucketize by start_min.
411 std::sort(events.begin(), events.end(),
412 [](const DiffnCtEvent& e1, const DiffnCtEvent& e2) {
413 return std::tie(e1.x_start_min, e1.y_size_min, e1.x_lp_end) <
414 std::tie(e2.x_start_min, e2.y_size_min, e2.x_lp_end);
415 });
416 for (int start = 0; start + 1 < events.size(); ++start) {
417 // Skip to the next start_min value.
418 if (start > 0 &&
419 events[start].x_start_min == events[start - 1].x_start_min) {
420 continue;
421 }
422
423 const IntegerValue sequence_start_min = events[start].x_start_min;
424 std::vector<DiffnCtEvent> residual_tasks(events.begin() + start,
425 events.end());
426
427 // We look at event that start before sequence_start_min, but are forced
428 // to cross this time point. In that case, we replace this event by a
429 // truncated event starting at sequence_start_min. To do this, we reduce
430 // the size_min, align the start_min with the sequence_start_min, and
431 // scale the energy down accordingly.
432 if (use_lifting) {
433 for (int before = 0; before < start; ++before) {
434 if (events[before].x_start_min + events[before].x_size_min >
435 sequence_start_min) {
436 // Build the vector of energies as the vector of sizes.
437 DiffnCtEvent event = events[before]; // Copy.
438 event.lifted = true;
439 event.energy_min = ComputeEnergyMinInWindow(
440 event.x_start_min, event.x_start_max, event.x_end_min,
441 event.x_end_max, event.x_size_min, event.y_size_min,
442 event.decomposed_energy, sequence_start_min, event.x_end_max);
443 event.x_size_min =
444 event.x_size_min + event.x_start_min - sequence_start_min;
445 event.x_start_min = sequence_start_min;
446 if (event.energy_min > event.x_size_min * event.y_size_min) {
447 event.use_energy = true;
448 }
449 DCHECK_GE(event.energy_min, event.x_size_min * event.y_size_min);
450 if (event.energy_min <= 0) continue;
451 residual_tasks.push_back(event);
452 }
453 }
454 }
455
456 std::sort(residual_tasks.begin(), residual_tasks.end(),
457 [](const DiffnCtEvent& e1, const DiffnCtEvent& e2) {
458 return e1.x_lp_end < e2.x_lp_end;
459 });
460
461 int best_end = -1;
462 double best_efficacy = 0.01;
463 IntegerValue best_min_contrib(0);
464 IntegerValue sum_duration(0);
465 IntegerValue sum_square_duration(0);
466 IntegerValue best_capacity(0);
467 double unscaled_lp_contrib = 0.0;
468 IntegerValue current_start_min(kMaxIntegerValue);
469 IntegerValue y_min = kMaxIntegerValue;
470 IntegerValue y_max = kMinIntegerValue;
471
472 bool use_dp = true;
474 for (int i = 0; i < residual_tasks.size(); ++i) {
475 const DiffnCtEvent& event = residual_tasks[i];
476 DCHECK_GE(event.x_start_min, sequence_start_min);
477 const IntegerValue energy = event.energy_min;
478 sum_duration += energy;
479 if (!AddProductTo(energy, energy, &sum_square_duration)) break;
480
481 unscaled_lp_contrib += event.x_lp_end * ToDouble(energy);
482 current_start_min = std::min(current_start_min, event.x_start_min);
483
484 // This is competing with the brute force approach. Skip cases covered
485 // by the other code.
486 if (skip_low_sizes && i < 7) continue;
487
488 // For the capacity, we use the worse |y_max - y_min| and if all the tasks
489 // so far have a fixed demand with a gcd > 1, we can round it down.
490 //
491 // TODO(user): Use dynamic programming to compute all possible values for
492 // the sum of demands as long as the involved numbers are small or the
493 // number of tasks are small.
494 y_min = std::min(y_min, event.y_min);
495 y_max = std::max(y_max, event.y_max);
496 if (!event.y_size_is_fixed) use_dp = false;
497 if (use_dp) {
498 if (i == 0) {
499 dp.Reset((y_max - y_min).value());
500 } else {
501 if (y_max - y_min != dp.Bound()) {
502 use_dp = false;
503 }
504 }
505 }
506 if (use_dp) {
507 dp.Add(event.y_size_min.value());
508 }
509
510 const IntegerValue capacity =
511 use_dp ? IntegerValue(dp.CurrentMax()) : y_max - y_min;
512
513 // We compute the cuts like if it was a disjunctive cut with all the
514 // duration actually equal to energy / capacity. But to keep the
515 // computation in the integer domain, we multiply by capacity
516 // everywhere instead.
517 IntegerValue min_contrib = 0;
518 if (!AddProductTo(sum_duration, sum_duration, &min_contrib)) break;
519 if (!AddTo(sum_square_duration, &min_contrib)) break;
520 min_contrib = min_contrib / 2; // The above is the double of the area.
521
522 const IntegerValue intermediate = CapProdI(sum_duration, capacity);
523 if (AtMinOrMaxInt64I(intermediate)) break;
524 const IntegerValue offset = CapProdI(current_start_min, intermediate);
525 if (AtMinOrMaxInt64I(offset)) break;
526 if (!AddTo(offset, &min_contrib)) break;
527
528 // We compute the efficacity in the unscaled domain where the l2 norm of
529 // the cuts is exactly the sqrt of the sum of squared duration.
530 const double efficacy =
531 (ToDouble(min_contrib) / ToDouble(capacity) - unscaled_lp_contrib) /
532 std::sqrt(ToDouble(sum_square_duration));
533
534 // TODO(user): Check overflow and ignore if too big.
535 if (efficacy > best_efficacy) {
536 best_efficacy = efficacy;
537 best_end = i;
538 best_min_contrib = min_contrib;
539 best_capacity = capacity;
540 }
541 }
542 if (best_end != -1) {
543 LinearConstraintBuilder cut(model, best_min_contrib, kMaxIntegerValue);
544 bool is_lifted = false;
545 bool add_energy_to_name = false;
546 for (int i = 0; i <= best_end; ++i) {
547 const DiffnCtEvent& event = residual_tasks[i];
548 is_lifted |= event.lifted;
549 add_energy_to_name |= event.use_energy;
550 cut.AddTerm(event.x_end, event.energy_min * best_capacity);
551 }
552 std::string full_name(cut_name);
553 if (is_lifted) full_name.append("_lifted");
554 if (add_energy_to_name) full_name.append("_energy");
555 top_n_cuts.AddCut(cut.Build(), full_name, manager->LpValues());
556 }
557 }
558 top_n_cuts.TransferToManager(manager);
559}
560
561// TODO(user): Use demands_helper and decomposed energy.
564 Model* model) {
565 CutGenerator result;
566 result.only_run_at_level_zero = true;
567 AddIntegerVariableFromIntervals(x_helper, model, &result.vars);
568 AddIntegerVariableFromIntervals(y_helper, model, &result.vars);
570
571 auto* product_decomposer = model->GetOrCreate<ProductDecomposer>();
572 result.generate_cuts = [x_helper, y_helper, product_decomposer,
573 model](LinearConstraintManager* manager) {
574 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
575 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
576
577 const int num_rectangles = x_helper->NumTasks();
578 std::vector<int> active_rectangles;
579 std::vector<IntegerValue> cached_areas(num_rectangles);
580 std::vector<Rectangle> cached_rectangles(num_rectangles);
581 for (int rect = 0; rect < num_rectangles; ++rect) {
582 if (!y_helper->IsPresent(rect) || !y_helper->IsPresent(rect)) continue;
583
584 cached_areas[rect] = x_helper->SizeMin(rect) * y_helper->SizeMin(rect);
585 if (cached_areas[rect] == 0) continue;
586
587 // TODO(user): It might be possible/better to use some shifted value
588 // here, but for now this code is not in the hot spot, so better be
589 // defensive and only do connected components on really disjoint
590 // rectangles.
591 Rectangle& rectangle = cached_rectangles[rect];
592 rectangle.x_min = x_helper->StartMin(rect);
593 rectangle.x_max = x_helper->EndMax(rect);
594 rectangle.y_min = y_helper->StartMin(rect);
595 rectangle.y_max = y_helper->EndMax(rect);
596
597 active_rectangles.push_back(rect);
598 }
599
600 if (active_rectangles.size() <= 1) return true;
601
602 std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
603 cached_rectangles, absl::MakeSpan(active_rectangles));
604 for (absl::Span<int> rectangles : components) {
605 if (rectangles.size() <= 1) continue;
606
607 auto generate_cuts = [product_decomposer, manager, model, &rectangles](
608 absl::string_view cut_name,
610 SchedulingConstraintHelper* y_helper) {
611 std::vector<DiffnCtEvent> events;
612
613 const auto& lp_values = manager->LpValues();
614 for (const int rect : rectangles) {
615 DiffnCtEvent event(rect, x_helper);
616 event.x_end = x_helper->Ends()[rect];
617 event.x_lp_end = event.x_end.LpValue(lp_values);
618 event.y_min = y_helper->StartMin(rect);
619 event.y_max = y_helper->EndMax(rect);
620 event.y_size_min = y_helper->SizeMin(rect);
621
622 // TODO(user): Use improved energy from demands helper.
623 event.energy_min = event.x_size_min * event.y_size_min;
624 event.decomposed_energy = product_decomposer->TryToDecompose(
625 x_helper->Sizes()[rect], y_helper->Sizes()[rect]);
626 events.push_back(event);
627 }
628
630 cut_name, std::move(events),
631 /*use_lifting=*/false,
632 /*skip_low_sizes=*/false, model, manager);
633 };
634
635 if (!x_helper->SynchronizeAndSetTimeDirection(true)) return false;
636 if (!y_helper->SynchronizeAndSetTimeDirection(true)) return false;
637 generate_cuts("NoOverlap2dXCompletionTime", x_helper, y_helper);
638 generate_cuts("NoOverlap2dYCompletionTime", y_helper, x_helper);
639 if (!x_helper->SynchronizeAndSetTimeDirection(false)) return false;
640 if (!y_helper->SynchronizeAndSetTimeDirection(false)) return false;
641 generate_cuts("NoOverlap2dXCompletionTimeMirror", x_helper, y_helper);
642 generate_cuts("NoOverlap2dYCompletionTimeMirror", y_helper, x_helper);
643 }
644 return true;
645 };
646 return result;
647}
648
649} // namespace sat
650} // namespace operations_research
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
Adds a rectangle to the current shape.
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 AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(absl::Span< const LiteralValueValue > product)
const util_intops::StrongVector< IntegerVariable, double > & LpValues()
To simplify CutGenerator api.
LiteralIndex Index() const
Definition sat_base.h:91
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
Definition util.cc:501
Helper class to express a product as a linear constraint.
int NumTasks() const
Returns the number of task.
Definition intervals.h:293
absl::Span< const AffineExpression > Sizes() const
Definition intervals.h:481
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition intervals.cc:483
void TransferToManager(LinearConstraintManager *manager)
Empty the local pool and add all its content to the manager.
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
Adds a cut to the local pool.
int64_t a
Definition table.cc:44
int64_t value
GRBmodel * model
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Computes result += a * b, and return false iff there is an overflow.
Definition integer.h:169
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool AddTo(IntegerValue a, IntegerValue *result)
Definition integer.h:160
const LiteralIndex kNoLiteralIndex(-1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, Model *model)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles, absl::Span< int > active_rectangles)
void AddIntegerVariableFromIntervals(SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars)
Cuts helpers.
void GenerateNoOverlap2dEnergyCut(absl::Span< const std::vector< LiteralValueValue > > energies, absl::Span< int > rectangles, absl::string_view cut_name, Model *model, LinearConstraintManager *manager, SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *y_demands_helper)
CutGenerator CreateNoOverlap2dEnergyCutGenerator(SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *x_demands_helper, SchedulingDemandHelper *y_demands_helper, const std::vector< std::vector< LiteralValueValue > > &energies, Model *model)
bool AtMinOrMaxInt64I(IntegerValue t)
Definition integer.h:121
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
Definition integer.h:105
double ToDouble(IntegerValue value)
Definition integer.h:73
void GenerateNoOvelap2dCompletionTimeCutsWithEnergy(absl::string_view cut_name, std::vector< DiffnCtEvent > events, bool use_lifting, bool skip_low_sizes, 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)
Definition intervals.cc:865
In SWIG mode, we don't want anything besides these top-level includes.
int64_t energy
Definition resource.cc:355
double average
The average absolute value of the finite values.
std::optional< int64_t > end
int64_t start
std::vector< IntegerVariable > vars
Definition cuts.h:55
std::function< bool(LinearConstraintManager *manager)> generate_cuts
Definition cuts.h:56
Internal methods and data structures, useful for testing.
Definition diffn_cuts.h:57
std::vector< LiteralValueValue > decomposed_energy
Definition diffn_cuts.h:76
IntegerValue x_start_min
Cache of the intervals bound on the x direction.
Definition diffn_cuts.h:61
IntegerValue y_min
Useful for no_overlap_2d or cumulative.
Definition diffn_cuts.h:67
DiffnBaseEvent(int t, SchedulingConstraintHelper *x_helper)
Definition diffn_cuts.cc:54
IntegerValue energy_min
The energy min of this event.
Definition diffn_cuts.h:72
DiffnCtEvent(int t, SchedulingConstraintHelper *x_helper)
AffineExpression x_end
The lp value of the end of the x interval.
Definition diffn_cuts.h:88
AffineExpression y_size
We need this for linearizing the energy in some cases.
Definition diffn_cuts.cc:66
bool energy_is_quadratic
True if linearized_energy is not exact and a McCormick relaxation.
Definition diffn_cuts.cc:78
LiteralIndex presence_literal_index
If set, this event is optional and its presence is controlled by this.
Definition diffn_cuts.cc:69
double y_spread
Used to minimize the increase on the y axis for rectangles.
Definition diffn_cuts.cc:81
DiffnEnergyEvent(int t, SchedulingConstraintHelper *x_helper)
Definition diffn_cuts.cc:62
IntegerValue GetMinOverlap(IntegerValue start, IntegerValue end) const
Definition diffn_cuts.cc:91
ABSL_MUST_USE_RESULT bool FillEnergyLp(AffineExpression x_size, const util_intops::StrongVector< IntegerVariable, double > &lp_values, Model *model)
Definition diffn_cuts.cc:99
double LpValue(const util_intops::StrongVector< IntegerVariable, double > &lp_values) const
Return[s] the evaluation of the linear expression.