Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
diffn_util.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 <stddef.h>
17
18#include <algorithm>
19#include <array>
20#include <cmath>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <optional>
25#include <ostream>
26#include <sstream>
27#include <string>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32#include "absl/container/flat_hash_set.h"
33#include "absl/container/inlined_vector.h"
34#include "absl/log/check.h"
35#include "absl/random/bit_gen_ref.h"
36#include "absl/types/span.h"
39#include "ortools/sat/integer.h"
41#include "ortools/sat/util.h"
44
45namespace operations_research {
46namespace sat {
47
48bool Rectangle::IsDisjoint(const Rectangle& other) const {
49 return x_min >= other.x_max || other.x_min >= x_max || y_min >= other.y_max ||
50 other.y_min >= y_max;
51}
52
53absl::InlinedVector<Rectangle, 4> Rectangle::SetDifference(
54 const Rectangle& other) const {
55 const Rectangle intersect = Intersect(other);
56 if (intersect.SizeX() == 0) {
57 return {*this};
58 }
59
60 //-------------------
61 //| | 4 | |
62 //| |---------| |
63 //| 1 | other | 2 |
64 //| |---------| |
65 //| | 3 | |
66 //-------------------
67 absl::InlinedVector<Rectangle, 4> result;
68 if (x_min < intersect.x_min) {
69 // Piece 1
70 result.push_back({.x_min = x_min,
71 .x_max = intersect.x_min,
72 .y_min = y_min,
73 .y_max = y_max});
74 }
75 if (x_max > intersect.x_max) {
76 // Piece 2
77 result.push_back({.x_min = intersect.x_max,
78 .x_max = x_max,
79 .y_min = y_min,
80 .y_max = y_max});
81 }
82 if (y_min < intersect.y_min) {
83 // Piece 3
84 result.push_back({.x_min = intersect.x_min,
85 .x_max = intersect.x_max,
86 .y_min = y_min,
87 .y_max = intersect.y_min});
88 }
89 if (y_max > intersect.y_max) {
90 // Piece 4
91 result.push_back({.x_min = intersect.x_min,
92 .x_max = intersect.x_max,
93 .y_min = intersect.y_max,
94 .y_max = y_max});
95 }
96
97 return result;
98}
99
100std::vector<absl::Span<int>> GetOverlappingRectangleComponents(
101 absl::Span<const Rectangle> rectangles, absl::Span<int> active_rectangles) {
102 if (active_rectangles.empty()) return {};
103
104 std::vector<absl::Span<int>> result;
105 const int size = active_rectangles.size();
106 for (int start = 0; start < size;) {
107 // Find the component of active_rectangles[start].
108 int end = start + 1;
109 for (int i = start; i < end; i++) {
110 for (int j = end; j < size; ++j) {
111 if (!rectangles[active_rectangles[i]].IsDisjoint(
112 rectangles[active_rectangles[j]])) {
113 std::swap(active_rectangles[end++], active_rectangles[j]);
114 }
115 }
116 }
117 if (end > start + 1) {
118 result.push_back(active_rectangles.subspan(start, end - start));
119 }
120 start = end;
121 }
122 return result;
123}
124
125bool ReportEnergyConflict(Rectangle bounding_box, absl::Span<const int> boxes,
126 SchedulingConstraintHelper* x,
128 x->ClearReason();
129 y->ClearReason();
130 IntegerValue total_energy(0);
131 for (const int b : boxes) {
132 const IntegerValue x_min = x->ShiftedStartMin(b);
133 const IntegerValue x_max = x->ShiftedEndMax(b);
134 if (x_min < bounding_box.x_min || x_max > bounding_box.x_max) continue;
135 const IntegerValue y_min = y->ShiftedStartMin(b);
136 const IntegerValue y_max = y->ShiftedEndMax(b);
137 if (y_min < bounding_box.y_min || y_max > bounding_box.y_max) continue;
138
139 x->AddEnergyMinInIntervalReason(b, bounding_box.x_min, bounding_box.x_max);
140 y->AddEnergyMinInIntervalReason(b, bounding_box.y_min, bounding_box.y_max);
141
142 x->AddPresenceReason(b);
143 y->AddPresenceReason(b);
144
145 total_energy += x->SizeMin(b) * y->SizeMin(b);
146
147 // We abort early if a subset of boxes is enough.
148 // TODO(user): Also relax the box if possible.
149 if (total_energy > bounding_box.Area()) break;
150 }
151
152 CHECK_GT(total_energy, bounding_box.Area());
153 x->ImportOtherReasons(*y);
154 return x->ReportConflict();
155}
156
157bool BoxesAreInEnergyConflict(const std::vector<Rectangle>& rectangles,
158 const std::vector<IntegerValue>& energies,
159 absl::Span<const int> boxes,
160 Rectangle* conflict) {
161 // First consider all relevant intervals along the x axis.
162 std::vector<IntegerValue> x_starts;
163 std::vector<TaskTime> boxes_by_increasing_x_max;
164 for (const int b : boxes) {
165 x_starts.push_back(rectangles[b].x_min);
166 boxes_by_increasing_x_max.push_back({b, rectangles[b].x_max});
167 }
169 std::sort(boxes_by_increasing_x_max.begin(), boxes_by_increasing_x_max.end());
170
171 std::vector<IntegerValue> y_starts;
172 std::vector<IntegerValue> energy_sum;
173 std::vector<TaskTime> boxes_by_increasing_y_max;
174
175 std::vector<std::vector<int>> stripes(x_starts.size());
176 for (int i = 0; i < boxes_by_increasing_x_max.size(); ++i) {
177 const int b = boxes_by_increasing_x_max[i].task_index;
178 const IntegerValue x_min = rectangles[b].x_min;
179 const IntegerValue x_max = rectangles[b].x_max;
180 for (int j = 0; j < x_starts.size(); ++j) {
181 if (x_starts[j] > x_min) break;
182 stripes[j].push_back(b);
183
184 // Redo the same on the y coordinate for the current x interval
185 // which is [starts[j], x_max].
186 y_starts.clear();
187 boxes_by_increasing_y_max.clear();
188 for (const int b : stripes[j]) {
189 y_starts.push_back(rectangles[b].y_min);
190 boxes_by_increasing_y_max.push_back({b, rectangles[b].y_max});
191 }
193 std::sort(boxes_by_increasing_y_max.begin(),
194 boxes_by_increasing_y_max.end());
195
196 const IntegerValue x_size = x_max - x_starts[j];
197 energy_sum.assign(y_starts.size(), IntegerValue(0));
198 for (int i = 0; i < boxes_by_increasing_y_max.size(); ++i) {
199 const int b = boxes_by_increasing_y_max[i].task_index;
200 const IntegerValue y_min = rectangles[b].y_min;
201 const IntegerValue y_max = rectangles[b].y_max;
202 for (int j = 0; j < y_starts.size(); ++j) {
203 if (y_starts[j] > y_min) break;
204 energy_sum[j] += energies[b];
205 if (energy_sum[j] > x_size * (y_max - y_starts[j])) {
206 if (conflict != nullptr) {
207 *conflict = rectangles[b];
208 for (int k = 0; k < i; ++k) {
209 const int task_index = boxes_by_increasing_y_max[k].task_index;
210 if (rectangles[task_index].y_min >= y_starts[j]) {
211 conflict->GrowToInclude(rectangles[task_index]);
212 }
213 }
214 }
215 return true;
216 }
217 }
218 }
219 }
220 }
221 return false;
222}
223
224bool AnalyzeIntervals(bool transpose, absl::Span<const int> local_boxes,
225 absl::Span<const Rectangle> rectangles,
226 absl::Span<const IntegerValue> rectangle_energies,
227 IntegerValue* x_threshold, IntegerValue* y_threshold,
228 Rectangle* conflict) {
229 // First, we compute the possible x_min values (removing duplicates).
230 // We also sort the relevant tasks by their x_max.
231 //
232 // TODO(user): If the number of unique x_max is smaller than the number of
233 // unique x_min, it is better to do it the other way around.
234 std::vector<IntegerValue> starts;
235 std::vector<TaskTime> task_by_increasing_x_max;
236 for (const int t : local_boxes) {
237 const IntegerValue x_min =
238 transpose ? rectangles[t].y_min : rectangles[t].x_min;
239 const IntegerValue x_max =
240 transpose ? rectangles[t].y_max : rectangles[t].x_max;
241 starts.push_back(x_min);
242 task_by_increasing_x_max.push_back({t, x_max});
243 }
245
246 // Note that for the same end_max, the order change our heuristic to
247 // evaluate the max_conflict_height.
248 std::sort(task_by_increasing_x_max.begin(), task_by_increasing_x_max.end());
249
250 // The maximum y dimension of a bounding area for which there is a potential
251 // conflict.
252 IntegerValue max_conflict_height(0);
253
254 // This is currently only used for logging.
255 absl::flat_hash_set<std::pair<IntegerValue, IntegerValue>> stripes;
256
257 // All quantities at index j correspond to the interval [starts[j], x_max].
258 std::vector<IntegerValue> energies(starts.size(), IntegerValue(0));
259 std::vector<IntegerValue> y_mins(starts.size(), kMaxIntegerValue);
260 std::vector<IntegerValue> y_maxs(starts.size(), -kMaxIntegerValue);
261 std::vector<IntegerValue> energy_at_max_y(starts.size(), IntegerValue(0));
262 std::vector<IntegerValue> energy_at_min_y(starts.size(), IntegerValue(0));
263
264 // Sentinel.
265 starts.push_back(kMaxIntegerValue);
266
267 // Iterate over all boxes by increasing x_max values.
268 int first_j = 0;
269 const IntegerValue threshold = transpose ? *y_threshold : *x_threshold;
270 for (int i = 0; i < task_by_increasing_x_max.size(); ++i) {
271 const int t = task_by_increasing_x_max[i].task_index;
272
273 const IntegerValue energy = rectangle_energies[t];
274 IntegerValue x_min = rectangles[t].x_min;
275 IntegerValue x_max = rectangles[t].x_max;
276 IntegerValue y_min = rectangles[t].y_min;
277 IntegerValue y_max = rectangles[t].y_max;
278 if (transpose) {
279 std::swap(x_min, y_min);
280 std::swap(x_max, y_max);
281 }
282
283 // Add this box contribution to all the [starts[j], x_max] intervals.
284 while (first_j + 1 < starts.size() && x_max - starts[first_j] > threshold) {
285 ++first_j;
286 }
287 for (int j = first_j; starts[j] <= x_min; ++j) {
288 const IntegerValue old_energy_at_max = energy_at_max_y[j];
289 const IntegerValue old_energy_at_min = energy_at_min_y[j];
290
291 energies[j] += energy;
292
293 const bool is_disjoint = y_min >= y_maxs[j] || y_max <= y_mins[j];
294
295 if (y_min <= y_mins[j]) {
296 if (y_min < y_mins[j]) {
297 y_mins[j] = y_min;
298 energy_at_min_y[j] = energy;
299 } else {
300 energy_at_min_y[j] += energy;
301 }
302 }
303
304 if (y_max >= y_maxs[j]) {
305 if (y_max > y_maxs[j]) {
306 y_maxs[j] = y_max;
307 energy_at_max_y[j] = energy;
308 } else {
309 energy_at_max_y[j] += energy;
310 }
311 }
312
313 // If the new box is disjoint in y from the ones added so far, there
314 // cannot be a new conflict involving this box, so we skip until we add
315 // new boxes.
316 if (is_disjoint) continue;
317
318 const IntegerValue width = x_max - starts[j];
319 IntegerValue conflict_height = CeilRatio(energies[j], width) - 1;
320 if (y_max - y_min > conflict_height) continue;
321 if (conflict_height >= y_maxs[j] - y_mins[j]) {
322 // We have a conflict.
323 if (conflict != nullptr) {
324 *conflict = rectangles[t];
325 for (int k = 0; k < i; ++k) {
326 const int task_index = task_by_increasing_x_max[k].task_index;
327 const IntegerValue task_x_min = transpose
328 ? rectangles[task_index].y_min
329 : rectangles[task_index].x_min;
330 if (task_x_min < starts[j]) continue;
331 conflict->GrowToInclude(rectangles[task_index]);
332 }
333 }
334 return false;
335 }
336
337 // Because we currently do not have a conflict involving the new box, the
338 // only way to have one is to remove enough energy to reduce the y domain.
339 IntegerValue can_remove = std::min(old_energy_at_min, old_energy_at_max);
340 if (old_energy_at_min < old_energy_at_max) {
341 if (y_maxs[j] - y_min >=
342 CeilRatio(energies[j] - old_energy_at_min, width)) {
343 // In this case, we need to remove at least old_energy_at_max to have
344 // a conflict.
345 can_remove = old_energy_at_max;
346 }
347 } else if (old_energy_at_max < old_energy_at_min) {
348 if (y_max - y_mins[j] >=
349 CeilRatio(energies[j] - old_energy_at_max, width)) {
350 can_remove = old_energy_at_min;
351 }
352 }
353 conflict_height = CeilRatio(energies[j] - can_remove, width) - 1;
354
355 // If the new box height is above the conflict_height, do not count
356 // it now. We only need to consider conflict involving the new box.
357 if (y_max - y_min > conflict_height) continue;
358
359 if (VLOG_IS_ON(2)) stripes.insert({starts[j], x_max});
360 max_conflict_height = std::max(max_conflict_height, conflict_height);
361 }
362 }
363
364 VLOG(2) << " num_starts: " << starts.size() - 1 << "/" << local_boxes.size()
365 << " conflict_height: " << max_conflict_height
366 << " num_stripes:" << stripes.size() << " (<= " << threshold << ")";
367
368 if (transpose) {
369 *x_threshold = std::min(*x_threshold, max_conflict_height);
370 } else {
371 *y_threshold = std::min(*y_threshold, max_conflict_height);
372 }
373 return true;
374}
375
376absl::Span<int> FilterBoxesAndRandomize(
377 absl::Span<const Rectangle> cached_rectangles, absl::Span<int> boxes,
378 IntegerValue threshold_x, IntegerValue threshold_y,
379 absl::BitGenRef random) {
380 size_t new_size = 0;
381 for (const int b : boxes) {
382 const Rectangle& dim = cached_rectangles[b];
383 if (dim.x_max - dim.x_min > threshold_x) continue;
384 if (dim.y_max - dim.y_min > threshold_y) continue;
385 boxes[new_size++] = b;
386 }
387 if (new_size == 0) return {};
388 std::shuffle(&boxes[0], &boxes[0] + new_size, random);
389 return {&boxes[0], new_size};
390}
391
392absl::Span<int> FilterBoxesThatAreTooLarge(
393 absl::Span<const Rectangle> cached_rectangles,
394 absl::Span<const IntegerValue> energies, absl::Span<int> boxes) {
395 // Sort the boxes by increasing area.
396 std::sort(boxes.begin(), boxes.end(), [cached_rectangles](int a, int b) {
397 return cached_rectangles[a].Area() < cached_rectangles[b].Area();
398 });
399
400 IntegerValue total_energy(0);
401 for (const int box : boxes) total_energy += energies[box];
402
403 // Remove all the large boxes until we have one with area smaller than the
404 // energy of the boxes below.
405 int new_size = boxes.size();
406 while (new_size > 0 &&
407 cached_rectangles[boxes[new_size - 1]].Area() >= total_energy) {
408 --new_size;
409 total_energy -= energies[boxes[new_size]];
410 }
411 return boxes.subspan(0, new_size);
412}
413
414std::ostream& operator<<(std::ostream& out, const IndexedInterval& interval) {
415 return out << "[" << interval.start << ".." << interval.end << " (#"
416 << interval.index << ")]";
417}
418
419void ConstructOverlappingSets(bool already_sorted,
420 std::vector<IndexedInterval>* intervals,
421 std::vector<std::vector<int>>* result) {
422 result->clear();
423 if (already_sorted) {
424 DCHECK(std::is_sorted(intervals->begin(), intervals->end(),
426 } else {
427 std::sort(intervals->begin(), intervals->end(),
429 }
430 IntegerValue min_end_in_set = kMaxIntegerValue;
431 intervals->push_back({-1, kMaxIntegerValue, kMaxIntegerValue}); // Sentinel.
432 const int size = intervals->size();
433
434 // We do a line sweep. The "current" subset crossing the "line" at
435 // (time, time + 1) will be in (*intervals)[start_index, end_index) at the end
436 // of the loop block.
437 int start_index = 0;
438 for (int end_index = 0; end_index < size;) {
439 const IntegerValue time = (*intervals)[end_index].start;
440
441 // First, if there is some deletion, we will push the "old" set to the
442 // result before updating it. Otherwise, we will have a superset later, so
443 // we just continue for now.
444 if (min_end_in_set <= time) {
445 result->push_back({});
446 min_end_in_set = kMaxIntegerValue;
447 for (int i = start_index; i < end_index; ++i) {
448 result->back().push_back((*intervals)[i].index);
449 if ((*intervals)[i].end <= time) {
450 std::swap((*intervals)[start_index++], (*intervals)[i]);
451 } else {
452 min_end_in_set = std::min(min_end_in_set, (*intervals)[i].end);
453 }
454 }
455
456 // Do not output subset of size one.
457 if (result->back().size() == 1) result->pop_back();
458 }
459
460 // Add all the new intervals starting exactly at "time".
461 do {
462 min_end_in_set = std::min(min_end_in_set, (*intervals)[end_index].end);
463 ++end_index;
464 } while (end_index < size && (*intervals)[end_index].start == time);
465 }
466}
467
469 std::vector<IndexedInterval>* intervals,
470 std::vector<std::vector<int>>* components) {
471 components->clear();
472 if (intervals->empty()) return;
473 if (intervals->size() == 1) {
474 components->push_back({intervals->front().index});
475 return;
476 }
477
478 // For correctness, ComparatorByStart is enough, but in unit tests we want to
479 // verify this function against another implementation, and fully defined
480 // sorting with tie-breaking makes that much easier.
481 // If that becomes a performance bottleneck:
482 // - One may want to sort the list outside of this function, and simply
483 // have this function DCHECK that it's sorted by start.
484 // - One may use stable_sort() with ComparatorByStart().
485 std::sort(intervals->begin(), intervals->end(),
487
488 IntegerValue end_max_so_far = (*intervals)[0].end;
489 components->push_back({(*intervals)[0].index});
490 for (int i = 1; i < intervals->size(); ++i) {
491 const IndexedInterval& interval = (*intervals)[i];
492 if (interval.start >= end_max_so_far) {
493 components->push_back({interval.index});
494 } else {
495 components->back().push_back(interval.index);
496 }
497 end_max_so_far = std::max(end_max_so_far, interval.end);
498 }
499}
500
501std::vector<int> GetIntervalArticulationPoints(
502 std::vector<IndexedInterval>* intervals) {
503 std::vector<int> articulation_points;
504 if (intervals->size() < 3) return articulation_points; // Empty.
505 if (DEBUG_MODE) {
506 for (const IndexedInterval& interval : *intervals) {
507 DCHECK_LT(interval.start, interval.end);
508 }
509 }
510
511 std::sort(intervals->begin(), intervals->end(),
512 IndexedInterval::ComparatorByStart());
513
514 IntegerValue end_max_so_far = (*intervals)[0].end;
515 int index_of_max = 0;
516 IntegerValue prev_end_max = kMinIntegerValue; // Initialized as a sentinel.
517 for (int i = 1; i < intervals->size(); ++i) {
518 const IndexedInterval& interval = (*intervals)[i];
519 if (interval.start >= end_max_so_far) {
520 // New connected component.
521 end_max_so_far = interval.end;
522 index_of_max = i;
523 prev_end_max = kMinIntegerValue;
524 continue;
525 }
526 // Still the same connected component. Was the previous "max" an
527 // articulation point ?
528 if (prev_end_max != kMinIntegerValue && interval.start >= prev_end_max) {
529 // We might be re-inserting the same articulation point: guard against it.
530 if (articulation_points.empty() ||
531 articulation_points.back() != index_of_max) {
532 articulation_points.push_back(index_of_max);
533 }
534 }
535 // Update the max end.
536 if (interval.end > end_max_so_far) {
537 prev_end_max = end_max_so_far;
538 end_max_so_far = interval.end;
539 index_of_max = i;
540 } else if (interval.end > prev_end_max) {
541 prev_end_max = interval.end;
542 }
543 }
544 // Convert articulation point indices to IndexedInterval.index.
545 for (int& index : articulation_points) index = (*intervals)[index].index;
546 return articulation_points;
547}
548
549namespace {
550bool IsZeroOrPowerOfTwo(int value) { return (value & (value - 1)) == 0; }
551
552void AppendPairwiseRestriction(const ItemForPairwiseRestriction& item1,
553 const ItemForPairwiseRestriction& item2,
554 std::vector<PairwiseRestriction>* result) {
555 const int state =
556 // box1 can be left of box2.
557 (item1.x.end_min <= item2.x.start_max) +
558 // box1 can be right of box2.
559 2 * (item2.x.end_min <= item1.x.start_max) +
560 // box1 can be below box2.
561 4 * (item1.y.end_min <= item2.y.start_max) +
562 // box1 can be up of box2.
563 8 * (item2.y.end_min <= item1.y.start_max);
564
565 if (!IsZeroOrPowerOfTwo(state)) {
566 return;
567 }
568
569 switch (state) {
570 case 0:
571 // Conflict. The two boxes must overlap in both dimensions.
572 result->push_back(
573 {.first_index = item1.index,
574 .second_index = item2.index,
576 break;
577 case 1:
578 // box2 can only be after box1 on x.
579 if (item1.x.end_min > item2.x.start_min ||
580 item2.x.start_max < item1.x.end_max) {
581 result->push_back({.first_index = item1.index,
582 .second_index = item2.index,
583 .type = PairwiseRestriction::
584 PairwiseRestrictionType::FIRST_LEFT_OF_SECOND});
585 }
586 break;
587 case 2: // box1 an only be after box2 on x.
588 if (item2.x.end_min > item1.x.start_min ||
589 item1.x.start_max < item2.x.end_max) {
590 result->push_back({.first_index = item1.index,
591 .second_index = item2.index,
592 .type = PairwiseRestriction::
593 PairwiseRestrictionType::FIRST_RIGHT_OF_SECOND});
594 }
595 break;
596 case 4:
597 // box2 an only be after box1 on y.
598 if (item1.y.end_min > item2.y.start_min ||
599 item2.y.start_max < item1.y.end_max) {
600 result->push_back({.first_index = item1.index,
601 .second_index = item2.index,
602 .type = PairwiseRestriction::
603 PairwiseRestrictionType::FIRST_BELOW_SECOND});
604 }
605 break;
606 case 8: // box1 an only be after box2 on y.
607 if (item2.y.end_min > item1.y.start_min ||
608 item1.y.start_max < item2.y.end_max) {
609 result->push_back({.first_index = item1.index,
610 .second_index = item2.index,
611 .type = PairwiseRestriction::
612 PairwiseRestrictionType::FIRST_ABOVE_SECOND});
613 }
614 break;
615 default:
616 break;
617 }
618}
619} // namespace
620
622 absl::Span<const ItemForPairwiseRestriction> items,
623 std::vector<PairwiseRestriction>* result) {
624 for (int i1 = 0; i1 + 1 < items.size(); ++i1) {
625 for (int i2 = i1 + 1; i2 < items.size(); ++i2) {
626 AppendPairwiseRestriction(items[i1], items[i2], result);
627 }
628 }
629}
630
632 absl::Span<const ItemForPairwiseRestriction> items,
633 absl::Span<const ItemForPairwiseRestriction> other_items,
634 std::vector<PairwiseRestriction>* result) {
635 for (int i1 = 0; i1 < items.size(); ++i1) {
636 for (int i2 = 0; i2 < other_items.size(); ++i2) {
637 AppendPairwiseRestriction(items[i1], other_items[i2], result);
638 }
639 }
640}
641
643 events_.clear();
644 num_rectangles_added_ = 0;
645}
646
647void CapacityProfile::AddRectangle(IntegerValue x_min, IntegerValue x_max,
648 IntegerValue y_min, IntegerValue y_max) {
649 DCHECK_LE(x_min, x_max);
650 if (x_min == x_max) return;
651
652 events_.push_back(
653 StartRectangleEvent(num_rectangles_added_, x_min, y_min, y_max));
654 events_.push_back(EndRectangleEvent(num_rectangles_added_, x_max));
655 ++num_rectangles_added_;
656}
657
658void CapacityProfile::AddMandatoryConsumption(IntegerValue x_min,
659 IntegerValue x_max,
660 IntegerValue y_height) {
661 DCHECK_LE(x_min, x_max);
662 if (x_min == x_max) return;
663
664 events_.push_back(ChangeMandatoryProfileEvent(x_min, y_height));
665 events_.push_back(ChangeMandatoryProfileEvent(x_max, -y_height));
666}
667
669 std::vector<CapacityProfile::Rectangle>* result) {
670 std::sort(events_.begin(), events_.end());
671 IntegerPriorityQueue<QueueElement> min_pq(num_rectangles_added_);
672 IntegerPriorityQueue<QueueElement> max_pq(num_rectangles_added_);
673 IntegerValue mandatory_capacity(0);
674
675 result->clear();
676
677 result->push_back({kMinIntegerValue, IntegerValue(0)});
678
679 for (int i = 0; i < events_.size();) {
680 const IntegerValue current_time = events_[i].time;
681 for (; i < events_.size(); ++i) {
682 const Event& event = events_[i];
683 if (event.time != current_time) break;
684
685 switch (events_[i].type) {
686 case START_RECTANGLE: {
687 min_pq.Add({event.index, -event.y_min});
688 max_pq.Add({event.index, event.y_max});
689 break;
690 }
691 case END_RECTANGLE: {
692 min_pq.Remove(event.index);
693 max_pq.Remove(event.index);
694 break;
695 }
696 case CHANGE_MANDATORY_PROFILE: {
697 mandatory_capacity += event.y_min;
698 break;
699 }
700 }
701 }
702
703 DCHECK(!max_pq.IsEmpty() || mandatory_capacity == 0);
704 const IntegerValue new_height =
705 max_pq.IsEmpty()
706 ? IntegerValue(0)
707 : max_pq.Top().value + min_pq.Top().value - mandatory_capacity;
708 if (new_height != result->back().height) {
709 result->push_back({current_time, new_height});
710 }
711 }
712}
713
715 std::sort(events_.begin(), events_.end());
716 IntegerPriorityQueue<QueueElement> min_pq(num_rectangles_added_);
717 IntegerPriorityQueue<QueueElement> max_pq(num_rectangles_added_);
718
719 IntegerValue area(0);
720 IntegerValue previous_time = kMinIntegerValue;
721 IntegerValue previous_height(0);
722
723 for (int i = 0; i < events_.size();) {
724 const IntegerValue current_time = events_[i].time;
725 for (; i < events_.size(); ++i) {
726 const Event& event = events_[i];
727 if (event.time != current_time) break;
728
729 switch (event.type) {
730 case START_RECTANGLE: {
731 min_pq.Add({event.index, -event.y_min});
732 max_pq.Add({event.index, event.y_max});
733 break;
734 }
735 case END_RECTANGLE: {
736 min_pq.Remove(event.index);
737 max_pq.Remove(event.index);
738 break;
739 }
740 case CHANGE_MANDATORY_PROFILE: {
741 break;
742 }
743 }
744 }
745 const IntegerValue new_height =
746 max_pq.IsEmpty() ? IntegerValue(0)
747 : max_pq.Top().value + min_pq.Top().value;
748 if (previous_height != 0) {
749 area += previous_height * (current_time - previous_time);
750 }
751 previous_time = current_time;
752 previous_height = new_height;
753 }
754 return area;
755}
756
757IntegerValue Smallest1DIntersection(IntegerValue range_min,
758 IntegerValue range_max, IntegerValue size,
759 IntegerValue interval_min,
760 IntegerValue interval_max) {
761 // If the item is on the left of the range, we get the intersection between
762 // [range_min, range_min + size] and [interval_min, interval_max].
763 const IntegerValue overlap_on_left =
764 std::min(range_min + size, interval_max) -
765 std::max(range_min, interval_min);
766
767 // If the item is on the right of the range, we get the intersection between
768 // [range_max - size, range_max] and [interval_min, interval_max].
769 const IntegerValue overlap_on_right =
770 std::min(range_max, interval_max) -
771 std::max(range_max - size, interval_min);
772
773 return std::max(IntegerValue(0), std::min(overlap_on_left, overlap_on_right));
774}
775
777 const std::vector<RectangleInRange>& intervals)
778 : intervals_(intervals) {
779 minimum_energy_ = 0;
780 if (intervals_.empty()) {
781 return;
782 }
783 interval_points_sorted_by_x_.reserve(intervals_.size() * 4 + 2);
784 interval_points_sorted_by_y_.reserve(intervals_.size() * 4 + 2);
785
786 Rectangle bounding_box = {.x_min = std::numeric_limits<IntegerValue>::max(),
787 .x_max = std::numeric_limits<IntegerValue>::min(),
788 .y_min = std::numeric_limits<IntegerValue>::max(),
789 .y_max = std::numeric_limits<IntegerValue>::min()};
790
791 for (int i = 0; i < intervals_.size(); ++i) {
792 const RectangleInRange& interval = intervals_[i];
793 minimum_energy_ += interval.x_size * interval.y_size;
794
795 bounding_box.x_min =
796 std::min(bounding_box.x_min, interval.bounding_area.x_min);
797 bounding_box.x_max =
798 std::max(bounding_box.x_max, interval.bounding_area.x_max);
799 bounding_box.y_min =
800 std::min(bounding_box.y_min, interval.bounding_area.y_min);
801 bounding_box.y_max =
802 std::max(bounding_box.y_max, interval.bounding_area.y_max);
803
804 interval_points_sorted_by_x_.push_back({interval.bounding_area.x_min, i});
805 interval_points_sorted_by_x_.push_back(
806 {interval.bounding_area.x_min + interval.x_size, i});
807 interval_points_sorted_by_x_.push_back(
808 {interval.bounding_area.x_max - interval.x_size, i});
809 interval_points_sorted_by_x_.push_back({interval.bounding_area.x_max, i});
810
811 interval_points_sorted_by_y_.push_back({interval.bounding_area.y_min, i});
812 interval_points_sorted_by_y_.push_back(
813 {interval.bounding_area.y_min + interval.y_size, i});
814 interval_points_sorted_by_y_.push_back(
815 {interval.bounding_area.y_max - interval.y_size, i});
816 interval_points_sorted_by_y_.push_back({interval.bounding_area.y_max, i});
817 }
818
819 full_energy_ = minimum_energy_;
820 // Add four bogus points in the extremities so we can delegate setting up all
821 // internal state to Shrink().
822 interval_points_sorted_by_x_.push_back({bounding_box.x_min - 1, -1});
823 interval_points_sorted_by_x_.push_back({bounding_box.x_max + 1, -1});
824 interval_points_sorted_by_y_.push_back({bounding_box.y_min - 1, -1});
825 interval_points_sorted_by_y_.push_back({bounding_box.y_max + 1, -1});
826
827 auto comparator = [](const IntervalPoint& a, const IntervalPoint& b) {
828 return std::tie(a.value, a.index) < std::tie(b.value, b.index);
829 };
830 gtl::STLSortAndRemoveDuplicates(&interval_points_sorted_by_x_, comparator);
831 gtl::STLSortAndRemoveDuplicates(&interval_points_sorted_by_y_, comparator);
832
833 grouped_intervals_sorted_by_x_.reserve(interval_points_sorted_by_x_.size());
834 grouped_intervals_sorted_by_y_.reserve(interval_points_sorted_by_y_.size());
835 int i = 0;
836 while (i < interval_points_sorted_by_x_.size()) {
837 int idx_begin = i;
838 while (i < interval_points_sorted_by_x_.size() &&
839 interval_points_sorted_by_x_[i].value ==
840 interval_points_sorted_by_x_[idx_begin].value) {
841 i++;
842 }
843 grouped_intervals_sorted_by_x_.push_back(
844 {interval_points_sorted_by_x_[idx_begin].value,
845 absl::Span<IntervalPoint>(interval_points_sorted_by_x_)
846 .subspan(idx_begin, i - idx_begin)});
847 }
848
849 i = 0;
850 while (i < interval_points_sorted_by_y_.size()) {
851 int idx_begin = i;
852 while (i < interval_points_sorted_by_y_.size() &&
853 interval_points_sorted_by_y_[i].value ==
854 interval_points_sorted_by_y_[idx_begin].value) {
855 i++;
856 }
857 grouped_intervals_sorted_by_y_.push_back(
858 {interval_points_sorted_by_y_[idx_begin].value,
859 absl::Span<IntervalPoint>(interval_points_sorted_by_y_)
860 .subspan(idx_begin, i - idx_begin)});
861 }
862
863 Reset();
864}
865
867 indexes_[Edge::LEFT] = 0;
868 indexes_[Edge::RIGHT] = grouped_intervals_sorted_by_x_.size() - 1;
869 indexes_[Edge::BOTTOM] = 0;
870 indexes_[Edge::TOP] = grouped_intervals_sorted_by_y_.size() - 1;
871
872 next_indexes_[Edge::LEFT] = 1;
873 next_indexes_[Edge::RIGHT] = grouped_intervals_sorted_by_x_.size() - 2;
874 next_indexes_[Edge::BOTTOM] = 1;
875 next_indexes_[Edge::TOP] = grouped_intervals_sorted_by_y_.size() - 2;
876
877 minimum_energy_ = full_energy_;
878 ranges_touching_both_boundaries_[0].clear();
879 ranges_touching_both_boundaries_[1].clear();
880
881 for (int i = 0; i < 4; ++i) {
882 corner_count_[i] = 0;
883 intersect_length_[i] = 0;
884 cached_delta_energy_[i] = 0;
885 }
886
887 // Remove the four bogus points we added.
892}
893
895 return {
896 .x_min = grouped_intervals_sorted_by_x_[indexes_[Edge::LEFT]].coordinate,
897 .x_max = grouped_intervals_sorted_by_x_[indexes_[Edge::RIGHT]].coordinate,
898 .y_min =
899 grouped_intervals_sorted_by_y_[indexes_[Edge::BOTTOM]].coordinate,
900 .y_max = grouped_intervals_sorted_by_y_[indexes_[Edge::TOP]].coordinate};
901}
902
903namespace {
904// Intersects `rectangle` with the largest rectangle that must intersect with
905// the range in some way. To visualize this largest rectangle, imagine the four
906// possible extreme positions for the item in range (the four corners). This
907// rectangle is the one defined by the interior points of each position. This
908// don't use IsDisjoint() because it also works when the rectangle would be
909// malformed (it's bounding box less than twice the size).
910bool CanConsumeEnergy(const Rectangle& rectangle,
911 const RectangleInRange& item) {
912 return (rectangle.x_max > item.bounding_area.x_max - item.x_size) &&
913 (rectangle.y_max > item.bounding_area.y_max - item.y_size) &&
914 (rectangle.x_min < item.bounding_area.x_min + item.x_size) &&
915 (rectangle.y_min < item.bounding_area.y_min + item.y_size);
916}
917
918std::array<bool, 4> GetPossibleEdgeIntersection(const Rectangle& rectangle,
919 const RectangleInRange& range) {
920 std::array<bool, 4> result;
921 using Edge = ProbingRectangle::Edge;
922 result[Edge::LEFT] = rectangle.x_min >= range.bounding_area.x_min;
923 result[Edge::BOTTOM] = rectangle.y_min >= range.bounding_area.y_min;
924 result[Edge::RIGHT] = rectangle.x_max <= range.bounding_area.x_max;
925 result[Edge::TOP] = rectangle.y_max <= range.bounding_area.y_max;
926 return result;
927}
928
929} // namespace
930
931// NOMUTANTS -- This is a sanity check, it is hard to corrupt the data in an
932// unit test to check it will fail.
934 const Rectangle current_rectangle = GetCurrentRectangle();
936 IntegerValue intersect_length[4] = {0, 0, 0, 0};
937 IntegerValue corner_count[4] = {0, 0, 0, 0};
938 IntegerValue energy = 0;
939 CHECK_LE(next_indexes_[Edge::LEFT], indexes_[Edge::RIGHT]);
940 CHECK_LE(next_indexes_[Edge::BOTTOM], indexes_[Edge::TOP]);
941 CHECK_GE(next_indexes_[Edge::TOP], indexes_[Edge::BOTTOM]);
942 CHECK_GE(next_indexes_[Edge::RIGHT], indexes_[Edge::LEFT]);
943
944 for (int interval_idx = 0; interval_idx < intervals_.size(); interval_idx++) {
945 const RectangleInRange& range = intervals_[interval_idx];
946
947 const Rectangle min_intersect =
948 range.GetMinimumIntersection(current_rectangle);
949 CHECK_LE(min_intersect.SizeX(), range.x_size);
950 CHECK_LE(min_intersect.SizeY(), range.y_size);
951 energy += min_intersect.Area();
952
953 std::array<bool, 4> touching_boundary = {false, false, false, false};
954 CHECK_EQ(CanConsumeEnergy(current_rectangle, range) &&
955 current_rectangle.Area() != 0,
956 range.GetMinimumIntersectionArea(current_rectangle) != 0);
957 if (CanConsumeEnergy(current_rectangle, range)) {
958 touching_boundary = GetPossibleEdgeIntersection(current_rectangle, range);
959 }
960
961 CHECK_EQ(
962 touching_boundary[Edge::LEFT] && touching_boundary[Edge::RIGHT],
963 ranges_touching_both_boundaries_[Direction::LEFT_AND_RIGHT].contains(
964 interval_idx));
965 CHECK_EQ(
966 touching_boundary[Edge::TOP] && touching_boundary[Edge::BOTTOM],
967 ranges_touching_both_boundaries_[Direction::TOP_AND_BOTTOM].contains(
968 interval_idx));
969
970 if (touching_boundary[Edge::LEFT] && !touching_boundary[Edge::RIGHT]) {
971 intersect_length[Edge::LEFT] += Smallest1DIntersection(
972 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
973 current_rectangle.y_min, current_rectangle.y_max);
974 }
975
976 if (touching_boundary[Edge::RIGHT] && !touching_boundary[Edge::LEFT]) {
977 intersect_length[Edge::RIGHT] += Smallest1DIntersection(
978 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
979 current_rectangle.y_min, current_rectangle.y_max);
980 }
981
982 if (touching_boundary[Edge::TOP] && !touching_boundary[Edge::BOTTOM]) {
983 intersect_length[Edge::TOP] += Smallest1DIntersection(
984 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
985 current_rectangle.x_min, current_rectangle.x_max);
986 }
987
988 if (touching_boundary[Edge::BOTTOM] && !touching_boundary[Edge::TOP]) {
989 intersect_length[Edge::BOTTOM] += Smallest1DIntersection(
990 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
991 current_rectangle.x_min, current_rectangle.x_max);
992 }
993
994 if ((touching_boundary[Edge::LEFT] && touching_boundary[Edge::RIGHT]) ||
995 (touching_boundary[Edge::TOP] && touching_boundary[Edge::BOTTOM])) {
996 // We account separately for the problematic items that touches both
997 // sides.
998 continue;
999 }
1000 if (touching_boundary[Edge::BOTTOM] && touching_boundary[Edge::LEFT]) {
1002 }
1003 if (touching_boundary[Edge::BOTTOM] && touching_boundary[Edge::RIGHT]) {
1005 }
1006 if (touching_boundary[Edge::TOP] && touching_boundary[Edge::LEFT]) {
1007 corner_count[RectangleInRange::Corner::TOP_LEFT]++;
1008 }
1009 if (touching_boundary[Edge::TOP] && touching_boundary[Edge::RIGHT]) {
1011 }
1012 }
1013
1014 CHECK_EQ(energy, minimum_energy_);
1015 for (int i = 0; i < 4; i++) {
1016 CHECK_EQ(intersect_length[i], intersect_length_[i]);
1017 CHECK_EQ(corner_count[i], corner_count_[i]);
1018 }
1019}
1020
1021namespace {
1022
1023struct EdgeInfo {
1024 using Edge = ProbingRectangle::Edge;
1025 using Direction = ProbingRectangle::Direction;
1026 using Corner = RectangleInRange::Corner;
1027
1028 Edge opposite_edge;
1029
1030 struct OrthogonalInfo {
1031 Edge edge;
1032 Corner adjacent_corner;
1035 Direction shrink_direction;
1037 // Lower coordinate one first (ie., BOTTOM before TOP, LEFT before RIGHT).
1038 OrthogonalInfo orthogonal_edges[2];
1039};
1041struct EdgeInfoHolder {
1042 using Edge = ProbingRectangle::Edge;
1043 using Direction = ProbingRectangle::Direction;
1044 using Corner = RectangleInRange::Corner;
1045
1046 static constexpr EdgeInfo kLeft = {
1047 .opposite_edge = Edge::RIGHT,
1048 .shrink_direction = Direction::LEFT_AND_RIGHT,
1049 .orthogonal_shrink_direction = Direction::TOP_AND_BOTTOM,
1050 .orthogonal_edges = {
1051 {.edge = Edge::BOTTOM, .adjacent_corner = Corner::BOTTOM_LEFT},
1052 {.edge = Edge::TOP, .adjacent_corner = Corner::TOP_LEFT}}};
1053
1054 static constexpr EdgeInfo kRight = {
1055 .opposite_edge = Edge::LEFT,
1056 .shrink_direction = Direction::LEFT_AND_RIGHT,
1057 .orthogonal_shrink_direction = Direction::TOP_AND_BOTTOM,
1058 .orthogonal_edges = {
1059 {.edge = Edge::BOTTOM, .adjacent_corner = Corner::BOTTOM_RIGHT},
1060 {.edge = Edge::TOP, .adjacent_corner = Corner::TOP_RIGHT}}};
1061 static constexpr EdgeInfo kBottom = {
1062 .opposite_edge = Edge::TOP,
1063 .shrink_direction = Direction::TOP_AND_BOTTOM,
1064 .orthogonal_shrink_direction = Direction::LEFT_AND_RIGHT,
1065 .orthogonal_edges = {
1066 {.edge = Edge::LEFT, .adjacent_corner = Corner::BOTTOM_LEFT},
1067 {.edge = Edge::RIGHT, .adjacent_corner = Corner::BOTTOM_RIGHT}}};
1068 static constexpr EdgeInfo kTop = {
1069 .opposite_edge = Edge::BOTTOM,
1070 .shrink_direction = Direction::TOP_AND_BOTTOM,
1071 .orthogonal_shrink_direction = Direction::LEFT_AND_RIGHT,
1072 .orthogonal_edges = {
1073 {.edge = Edge::LEFT, .adjacent_corner = Corner::TOP_LEFT},
1074 {.edge = Edge::RIGHT, .adjacent_corner = Corner::TOP_RIGHT}}};
1075};
1076
1077constexpr const EdgeInfo& GetEdgeInfo(ProbingRectangle::Edge edge) {
1078 using Edge = ProbingRectangle::Edge;
1079 switch (edge) {
1080 case Edge::LEFT:
1081 return EdgeInfoHolder::kLeft;
1082 case Edge::RIGHT:
1083 return EdgeInfoHolder::kRight;
1084 case Edge::BOTTOM:
1085 return EdgeInfoHolder::kBottom;
1086 case Edge::TOP:
1087 return EdgeInfoHolder::kTop;
1088 }
1089}
1090
1091IntegerValue GetSmallest1DIntersection(ProbingRectangle::Direction direction,
1092 const RectangleInRange& range,
1093 const Rectangle& rectangle) {
1094 switch (direction) {
1096 return Smallest1DIntersection(range.bounding_area.x_min,
1097 range.bounding_area.x_max, range.x_size,
1098 rectangle.x_min, rectangle.x_max);
1100 return Smallest1DIntersection(range.bounding_area.y_min,
1101 range.bounding_area.y_max, range.y_size,
1102 rectangle.y_min, rectangle.y_max);
1103 }
1104}
1105
1106} // namespace
1107
1108template <ProbingRectangle::Edge edge>
1109void ProbingRectangle::ShrinkImpl() {
1110 constexpr EdgeInfo e = GetEdgeInfo(edge);
1111
1112 bool update_next_index[4] = {false, false, false, false};
1113 update_next_index[edge] = true;
1114
1115 IntegerValue step_1d_size;
1116 minimum_energy_ -= GetShrinkDeltaEnergy(edge);
1117 const std::vector<PointsForCoordinate>& sorted_intervals =
1118 e.shrink_direction == Direction::LEFT_AND_RIGHT
1119 ? grouped_intervals_sorted_by_x_
1120 : grouped_intervals_sorted_by_y_;
1121
1122 const Rectangle prev_rectangle = GetCurrentRectangle();
1123 indexes_[edge] = next_indexes_[edge];
1124 const Rectangle current_rectangle = GetCurrentRectangle();
1125
1126 switch (edge) {
1127 case Edge::LEFT:
1128 step_1d_size = current_rectangle.x_min - prev_rectangle.x_min;
1129 next_indexes_[edge] =
1130 std::min(indexes_[edge] + 1, indexes_[e.opposite_edge]);
1131 next_indexes_[e.opposite_edge] =
1132 std::max(indexes_[edge], next_indexes_[e.opposite_edge]);
1133 break;
1134 case Edge::BOTTOM:
1135 step_1d_size = current_rectangle.y_min - prev_rectangle.y_min;
1136 next_indexes_[edge] =
1137 std::min(indexes_[edge] + 1, indexes_[e.opposite_edge]);
1138 next_indexes_[e.opposite_edge] =
1139 std::max(indexes_[edge], next_indexes_[e.opposite_edge]);
1140 break;
1141 case Edge::RIGHT:
1142 step_1d_size = prev_rectangle.x_max - current_rectangle.x_max;
1143 next_indexes_[edge] =
1144 std::max(indexes_[edge] - 1, indexes_[e.opposite_edge]);
1145 next_indexes_[e.opposite_edge] =
1146 std::min(indexes_[edge], next_indexes_[e.opposite_edge]);
1147 break;
1148 case Edge::TOP:
1149 step_1d_size = prev_rectangle.y_max - current_rectangle.y_max;
1150 next_indexes_[edge] =
1151 std::max(indexes_[edge] - 1, indexes_[e.opposite_edge]);
1152 next_indexes_[e.opposite_edge] =
1153 std::min(indexes_[edge], next_indexes_[e.opposite_edge]);
1154 break;
1155 }
1156
1157 absl::Span<ProbingRectangle::IntervalPoint> items_touching_coordinate =
1158 sorted_intervals[indexes_[edge]].items_touching_coordinate;
1159
1160 IntegerValue delta_corner_count[4] = {0, 0, 0, 0};
1161 for (const auto& item : items_touching_coordinate) {
1162 const RectangleInRange& range = intervals_[item.index];
1163 if (!CanConsumeEnergy(prev_rectangle, range)) {
1164 // This item is out of our area of interest, skip.
1165 continue;
1166 }
1167
1168 const std::array<bool, 4> touching_boundary_before =
1169 GetPossibleEdgeIntersection(prev_rectangle, range);
1170 const std::array<bool, 4> touching_boundary_after =
1171 CanConsumeEnergy(current_rectangle, range)
1172 ? GetPossibleEdgeIntersection(current_rectangle, range)
1173 : std::array<bool, 4>({false, false, false, false});
1174
1175 bool remove_corner[4] = {false, false, false, false};
1176 auto erase_item = [this, &prev_rectangle, &range, &touching_boundary_before,
1177 &remove_corner](Edge edge_to_erase) {
1178 const EdgeInfo& erase_info = GetEdgeInfo(edge_to_erase);
1179 intersect_length_[edge_to_erase] -= GetSmallest1DIntersection(
1180 erase_info.orthogonal_shrink_direction, range, prev_rectangle);
1181
1182 if (touching_boundary_before[erase_info.orthogonal_edges[0].edge] &&
1183 touching_boundary_before[erase_info.orthogonal_edges[1].edge]) {
1184 // Ignore touching both corners
1185 return;
1186 }
1187 for (const auto [orthogonal_edge, corner] : erase_info.orthogonal_edges) {
1188 if (touching_boundary_before[orthogonal_edge]) {
1189 remove_corner[corner] = true;
1190 }
1191 }
1192 };
1193
1194 if (touching_boundary_after[edge] && !touching_boundary_before[edge]) {
1195 if (touching_boundary_before[e.opposite_edge]) {
1196 ranges_touching_both_boundaries_[e.shrink_direction].insert(item.index);
1197 erase_item(e.opposite_edge);
1198 } else {
1199 // Do the opposite of remove_item().
1200 intersect_length_[edge] += GetSmallest1DIntersection(
1201 e.orthogonal_shrink_direction, range, prev_rectangle);
1202 // Update the corner count unless it is touching both.
1203 if (!touching_boundary_before[e.orthogonal_edges[0].edge] ||
1204 !touching_boundary_before[e.orthogonal_edges[1].edge]) {
1205 for (const auto [orthogonal_edge, corner] : e.orthogonal_edges) {
1206 if (touching_boundary_before[orthogonal_edge]) {
1207 delta_corner_count[corner]++;
1208 }
1209 }
1210 }
1211 }
1212 }
1213
1214 for (int i = 0; i < 4; i++) {
1215 const Edge edge_to_update = (Edge)i;
1216 const EdgeInfo& info = GetEdgeInfo(edge_to_update);
1217 const bool remove_edge = touching_boundary_before[edge_to_update] &&
1218 !touching_boundary_after[edge_to_update];
1219 if (!remove_edge) {
1220 continue;
1221 }
1222
1223 update_next_index[edge_to_update] = true;
1224
1225 if (touching_boundary_before[info.opposite_edge]) {
1226 ranges_touching_both_boundaries_[info.shrink_direction].erase(
1227 item.index);
1228 } else {
1229 erase_item(edge_to_update);
1230 }
1231 }
1232
1233 for (int i = 0; i < 4; i++) {
1234 corner_count_[i] -= remove_corner[i];
1235 }
1236 }
1237
1238 // Update the intersection length for items touching both sides.
1239 for (const int idx : ranges_touching_both_boundaries_[e.shrink_direction]) {
1240 const RectangleInRange& range = intervals_[idx];
1241 const std::array<bool, 2> touching_corner =
1242 (e.shrink_direction == Direction::LEFT_AND_RIGHT)
1243 ? std::array<bool, 2>(
1244 {current_rectangle.y_min >= range.bounding_area.y_min,
1245 current_rectangle.y_max <= range.bounding_area.y_max})
1246 : std::array<bool, 2>(
1247 {current_rectangle.x_min >= range.bounding_area.x_min,
1248 current_rectangle.x_max <= range.bounding_area.x_max});
1249 if (touching_corner[0] == touching_corner[1]) {
1250 // Either it is not touching neither corners (so no length to update) or
1251 // it is touching both corners, which will be handled by the "both
1252 // sides" code and should not contribute to intersect_length_.
1253 continue;
1254 }
1255
1256 const IntegerValue incr =
1257 GetSmallest1DIntersection(e.shrink_direction, range, prev_rectangle) -
1258 GetSmallest1DIntersection(e.shrink_direction, range, current_rectangle);
1259 for (int i = 0; i < 2; i++) {
1260 if (touching_corner[i]) {
1261 intersect_length_[e.orthogonal_edges[i].edge] -= incr;
1262 }
1263 }
1264 }
1265
1266 for (const auto [orthogonal_edge, corner] : e.orthogonal_edges) {
1267 intersect_length_[orthogonal_edge] -= corner_count_[corner] * step_1d_size;
1268 }
1269
1270 for (int i = 0; i < 4; i++) {
1271 corner_count_[i] += delta_corner_count[i];
1272 }
1273
1274 auto points_consume_energy =
1275 [this,
1276 &current_rectangle](absl::Span<ProbingRectangle::IntervalPoint> points) {
1277 for (const auto& item : points) {
1278 const RectangleInRange& range = intervals_[item.index];
1279 if (CanConsumeEnergy(current_rectangle, range)) {
1280 return true;
1281 }
1282 }
1283 return false;
1284 };
1285 if (update_next_index[Edge::LEFT]) {
1286 for (; next_indexes_[Edge::LEFT] < indexes_[Edge::RIGHT];
1287 ++next_indexes_[Edge::LEFT]) {
1288 if (points_consume_energy(
1289 grouped_intervals_sorted_by_x_[next_indexes_[Edge::LEFT]]
1290 .items_touching_coordinate)) {
1291 break;
1292 }
1293 }
1294 }
1295 if (update_next_index[Edge::BOTTOM]) {
1296 for (; next_indexes_[Edge::BOTTOM] < indexes_[Edge::TOP];
1297 ++next_indexes_[Edge::BOTTOM]) {
1298 if (points_consume_energy(
1299 grouped_intervals_sorted_by_y_[next_indexes_[Edge::BOTTOM]]
1300 .items_touching_coordinate)) {
1301 break;
1302 }
1303 }
1304 }
1305 if (update_next_index[Edge::RIGHT]) {
1306 for (; next_indexes_[Edge::RIGHT] > indexes_[Edge::LEFT];
1307 --next_indexes_[Edge::RIGHT]) {
1308 if (points_consume_energy(
1309 grouped_intervals_sorted_by_x_[next_indexes_[Edge::RIGHT]]
1310 .items_touching_coordinate)) {
1311 break;
1312 }
1313 }
1314 }
1315 if (update_next_index[Edge::TOP]) {
1316 for (; next_indexes_[Edge::TOP] > indexes_[Edge::BOTTOM];
1317 --next_indexes_[Edge::TOP]) {
1318 if (points_consume_energy(
1319 grouped_intervals_sorted_by_y_[next_indexes_[Edge::TOP]]
1320 .items_touching_coordinate)) {
1321 break;
1322 }
1323 }
1324 }
1325
1326 probe_area_ = current_rectangle.Area();
1327 CacheShrinkDeltaEnergy(0);
1328 CacheShrinkDeltaEnergy(1);
1329}
1330
1331void ProbingRectangle::Shrink(Edge edge) {
1332 switch (edge) {
1334 ShrinkImpl<Edge::LEFT>();
1335 return;
1336 case Edge::BOTTOM:
1337 ShrinkImpl<Edge::BOTTOM>();
1338 return;
1339 case Edge::RIGHT:
1340 ShrinkImpl<Edge::RIGHT>();
1341 return;
1342 case Edge::TOP:
1343 ShrinkImpl<Edge::TOP>();
1344 return;
1345 }
1346}
1347
1349 const Rectangle current_rectangle = GetCurrentRectangle();
1350 const std::vector<PointsForCoordinate>& sorted_intervals =
1351 (edge == Edge::LEFT || edge == Edge::RIGHT)
1352 ? grouped_intervals_sorted_by_x_
1353 : grouped_intervals_sorted_by_y_;
1354 const IntegerValue coordinate =
1355 sorted_intervals[next_indexes_[edge]].coordinate;
1356 switch (edge) {
1357 case Edge::LEFT:
1358 return (coordinate - current_rectangle.x_min) * current_rectangle.SizeY();
1359 case Edge::BOTTOM:
1360 return (coordinate - current_rectangle.y_min) * current_rectangle.SizeX();
1361 case Edge::RIGHT:
1362 return (current_rectangle.x_max - coordinate) * current_rectangle.SizeY();
1363 case Edge::TOP:
1364 return (current_rectangle.y_max - coordinate) * current_rectangle.SizeX();
1365 }
1366}
1367
1368void ProbingRectangle::CacheShrinkDeltaEnergy(int dimension) {
1369 const Rectangle current_rectangle = GetCurrentRectangle();
1370 Rectangle next_rectangle_up = current_rectangle;
1371 Rectangle next_rectangle_down = current_rectangle;
1372 IntegerValue step_1d_size_up, step_1d_size_down;
1373 IntegerValue units_crossed_up, units_crossed_down;
1374 IntegerValue* delta_energy_up_ptr;
1375 IntegerValue* delta_energy_down_ptr;
1376
1377 if (dimension == 0) {
1378 // CanShrink(Edge::RIGHT) and CanShrink(Edge::LEFT) are equivalent
1379 if (!CanShrink(Edge::LEFT)) {
1380 cached_delta_energy_[Edge::LEFT] = 0;
1381 cached_delta_energy_[Edge::RIGHT] = 0;
1382 return;
1383 }
1384
1385 next_rectangle_up.x_min =
1386 grouped_intervals_sorted_by_x_[next_indexes_[Edge::LEFT]].coordinate;
1387 next_rectangle_down.x_max =
1388 grouped_intervals_sorted_by_x_[next_indexes_[Edge::RIGHT]].coordinate;
1389
1390 step_1d_size_up = next_rectangle_up.x_min - current_rectangle.x_min;
1391 step_1d_size_down = current_rectangle.x_max - next_rectangle_down.x_max;
1392 units_crossed_up = intersect_length_[Edge::LEFT];
1393 units_crossed_down = intersect_length_[Edge::RIGHT];
1394 delta_energy_up_ptr = &cached_delta_energy_[Edge::LEFT];
1395 delta_energy_down_ptr = &cached_delta_energy_[Edge::RIGHT];
1396 } else {
1397 if (!CanShrink(Edge::TOP)) {
1398 cached_delta_energy_[Edge::BOTTOM] = 0;
1399 cached_delta_energy_[Edge::TOP] = 0;
1400 return;
1401 }
1402
1403 next_rectangle_up.y_min =
1404 grouped_intervals_sorted_by_y_[next_indexes_[Edge::BOTTOM]].coordinate;
1405 next_rectangle_down.y_max =
1406 grouped_intervals_sorted_by_y_[next_indexes_[Edge::TOP]].coordinate;
1407
1408 step_1d_size_up = next_rectangle_up.y_min - current_rectangle.y_min;
1409 step_1d_size_down = current_rectangle.y_max - next_rectangle_down.y_max;
1410 units_crossed_up = intersect_length_[Edge::BOTTOM];
1411 units_crossed_down = intersect_length_[Edge::TOP];
1412 delta_energy_up_ptr = &cached_delta_energy_[Edge::BOTTOM];
1413 delta_energy_down_ptr = &cached_delta_energy_[Edge::TOP];
1414 }
1415 IntegerValue delta_energy_up = 0;
1416 IntegerValue delta_energy_down = 0;
1417
1418 // Note that the non-deterministic iteration order is fine here.
1419 for (const int idx : ranges_touching_both_boundaries_[dimension]) {
1420 const RectangleInRange& range = intervals_[idx];
1421 const IntegerValue curr_x = Smallest1DIntersection(
1422 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1423 current_rectangle.x_min, current_rectangle.x_max);
1424 const IntegerValue curr_y = Smallest1DIntersection(
1425 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1426 current_rectangle.y_min, current_rectangle.y_max);
1427 const IntegerValue curr = curr_x * curr_y;
1428 delta_energy_up += curr;
1429 delta_energy_down += curr;
1430
1431 if (dimension == 0) {
1432 const IntegerValue up_x = Smallest1DIntersection(
1433 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1434 next_rectangle_up.x_min, next_rectangle_up.x_max);
1435 const IntegerValue down_x = Smallest1DIntersection(
1436 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1437 next_rectangle_down.x_min, next_rectangle_down.x_max);
1438
1439 delta_energy_up -= curr_y * up_x;
1440 delta_energy_down -= curr_y * down_x;
1441 } else {
1442 const IntegerValue up_y = Smallest1DIntersection(
1443 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1444 next_rectangle_up.y_min, next_rectangle_up.y_max);
1445 const IntegerValue down_y = Smallest1DIntersection(
1446 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1447 next_rectangle_down.y_min, next_rectangle_down.y_max);
1448
1449 delta_energy_up -= curr_x * up_y;
1450 delta_energy_down -= curr_x * down_y;
1451 }
1452 }
1453 delta_energy_up += units_crossed_up * step_1d_size_up;
1454 delta_energy_down += units_crossed_down * step_1d_size_down;
1455 *delta_energy_up_ptr = delta_energy_up;
1456 *delta_energy_down_ptr = delta_energy_down;
1457}
1458
1459bool ProbingRectangle::CanShrink(Edge edge) const {
1460 switch (edge) {
1462 case Edge::RIGHT:
1463 return (next_indexes_[Edge::RIGHT] > indexes_[Edge::LEFT]);
1464 case Edge::BOTTOM:
1465 case Edge::TOP:
1466 return (indexes_[Edge::TOP] > next_indexes_[Edge::BOTTOM]);
1467 }
1468}
1469
1470namespace {
1471std::vector<double> GetExpTable() {
1472 std::vector<double> table(101);
1473 for (int i = 0; i <= 100; ++i) {
1474 table[i] = std::exp(-(i - 50) / 5.0);
1475 }
1476 return table;
1477}
1478
1479} // namespace
1480
1481FindRectanglesResult FindRectanglesWithEnergyConflictMC(
1482 const std::vector<RectangleInRange>& intervals, absl::BitGenRef random,
1483 double temperature, double candidate_energy_usage_factor) {
1484 FindRectanglesResult result;
1485 ProbingRectangle ranges(intervals);
1486
1487 static const std::vector<double>* cached_probabilities =
1488 new std::vector<double>(GetExpTable());
1489
1490 const double inv_temp = 1.0 / temperature;
1491 absl::InlinedVector<ProbingRectangle::Edge, 4> candidates;
1492 absl::InlinedVector<IntegerValue, 4> energy_deltas;
1493 absl::InlinedVector<double, 4> weights;
1494 while (!ranges.IsMinimal()) {
1495 const IntegerValue rect_area = ranges.GetCurrentRectangleArea();
1496 const IntegerValue min_energy = ranges.GetMinimumEnergy();
1497 if (min_energy > rect_area) {
1498 result.conflicts.push_back(ranges.GetCurrentRectangle());
1499 } else if (min_energy.value() >
1500 candidate_energy_usage_factor * rect_area.value()) {
1501 result.candidates.push_back(ranges.GetCurrentRectangle());
1502 }
1503 if (min_energy == 0) {
1504 break;
1505 }
1506 candidates.clear();
1507 energy_deltas.clear();
1508
1509 for (int border_idx = 0; border_idx < 4; ++border_idx) {
1510 const ProbingRectangle::Edge border =
1511 static_cast<ProbingRectangle::Edge>(border_idx);
1512 if (!ranges.CanShrink(border)) {
1513 continue;
1514 }
1515 candidates.push_back(border);
1516 const IntegerValue delta_area = ranges.GetShrinkDeltaArea(border);
1517 const IntegerValue delta_energy = ranges.GetShrinkDeltaEnergy(border);
1518 energy_deltas.push_back(delta_energy - delta_area);
1519 }
1520 const IntegerValue min_energy_delta =
1521 *std::min_element(energy_deltas.begin(), energy_deltas.end());
1522 weights.clear();
1523 for (const IntegerValue delta_slack : energy_deltas) {
1524 const int64_t table_lookup =
1525 std::max((int64_t)0,
1526 std::min((int64_t)((delta_slack - min_energy_delta).value() *
1527 5 * inv_temp +
1528 50),
1529 (int64_t)100));
1530 weights.push_back((*cached_probabilities)[table_lookup]);
1531 }
1532 // Pick a change with a probability proportional to exp(- delta_E / Temp)
1533 ranges.Shrink(candidates[WeightedPick(weights, random)]);
1534 }
1535 if (ranges.GetMinimumEnergy() > ranges.GetCurrentRectangleArea()) {
1536 result.conflicts.push_back(ranges.GetCurrentRectangle());
1537 }
1538 return result;
1539}
1540
1541std::string RenderDot(std::optional<Rectangle> bb,
1542 absl::Span<const Rectangle> solution) {
1543 const std::vector<std::string> colors = {"red", "green", "blue",
1544 "cyan", "yellow", "purple"};
1545 std::stringstream ss;
1546 ss << "digraph {\n";
1547 ss << " graph [ bgcolor=lightgray ]\n";
1548 ss << " node [style=filled]\n";
1549 if (bb.has_value()) {
1550 ss << " bb [fillcolor=\"grey\" pos=\"" << 2 * bb->x_min + bb->SizeX()
1551 << "," << 2 * bb->y_min + bb->SizeY()
1552 << "!\" shape=box width=" << 2 * bb->SizeX()
1553 << " height=" << 2 * bb->SizeY() << "]\n";
1554 }
1555 for (int i = 0; i < solution.size(); ++i) {
1556 ss << " " << i << " [fillcolor=\"" << colors[i % colors.size()]
1557 << "\" pos=\"" << 2 * solution[i].x_min + solution[i].SizeX() << ","
1558 << 2 * solution[i].y_min + solution[i].SizeY()
1559 << "!\" shape=box width=" << 2 * solution[i].SizeX()
1560 << " height=" << 2 * solution[i].SizeY() << "]\n";
1561 }
1562 ss << "}\n";
1563 return ss.str();
1564}
1565
1566std::vector<Rectangle> FindEmptySpaces(
1567 const Rectangle& bounding_box, std::vector<Rectangle> ocupied_rectangles) {
1568 std::vector<Rectangle> empty_spaces = {bounding_box};
1569 std::vector<Rectangle> new_empty_spaces;
1570 // Sorting is not necessary for correctness but makes it faster.
1571 std::sort(ocupied_rectangles.begin(), ocupied_rectangles.end(),
1572 [](const Rectangle& a, const Rectangle& b) {
1573 return std::tuple(a.x_min, -a.x_max, a.y_min) <
1574 std::tuple(b.x_min, -b.x_max, b.y_min);
1575 });
1576 for (const Rectangle& ocupied_rectangle : ocupied_rectangles) {
1577 new_empty_spaces.clear();
1578 for (const auto& empty_space : empty_spaces) {
1579 for (Rectangle& r : empty_space.SetDifference(ocupied_rectangle)) {
1580 new_empty_spaces.push_back(std::move(r));
1581 }
1582 }
1583 empty_spaces.swap(new_empty_spaces);
1584 if (empty_spaces.empty()) {
1585 break;
1586 }
1587 }
1588 return empty_spaces;
1589}
1590
1591} // namespace sat
1592} // namespace operations_research
IntegerValue y
IntegerValue size
void AddMandatoryConsumption(IntegerValue x_min, IntegerValue x_max, IntegerValue y_height)
void AddRectangle(IntegerValue x_min, IntegerValue x_max, IntegerValue y_min, IntegerValue y_max)
Adds a rectangle to the current shape.
void BuildResidualCapacityProfile(std::vector< Rectangle > *result)
IntegerValue GetShrinkDeltaEnergy(Edge edge) const
How much of GetMinimumEnergy() will change if Shrink() is called.
Definition diffn_util.h:518
void Reset()
Reset to the bounding box of the whole set.
IntegerValue GetShrinkDeltaArea(Edge edge) const
How much of GetCurrentRectangleArea() will change if Shrink() is called.
ProbingRectangle(const std::vector< RectangleInRange > &intervals)
It will initialize with the bounding box of the whole set.
int64_t a
Definition table.cc:44
int64_t value
static constexpr EdgeInfo kBottom
static constexpr EdgeInfo kTop
static constexpr EdgeInfo kLeft
Direction orthogonal_shrink_direction
OrthogonalInfo orthogonal_edges[2]
Lower coordinate one first (ie., BOTTOM before TOP, LEFT before RIGHT).
Edge opposite_edge
Corner adjacent_corner
Direction shrink_direction
static constexpr EdgeInfo kRight
Edge edge
int index
const bool DEBUG_MODE
Definition macros.h:24
double solution
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition integer.h:85
void AppendPairwiseRestrictions(absl::Span< const ItemForPairwiseRestriction > items, std::vector< PairwiseRestriction > *result)
bool BoxesAreInEnergyConflict(const std::vector< Rectangle > &rectangles, const std::vector< IntegerValue > &energies, absl::Span< const int > boxes, Rectangle *conflict)
absl::Span< int > FilterBoxesAndRandomize(absl::Span< const Rectangle > cached_rectangles, absl::Span< int > boxes, IntegerValue threshold_x, IntegerValue threshold_y, absl::BitGenRef random)
bool AnalyzeIntervals(bool transpose, absl::Span< const int > local_boxes, absl::Span< const Rectangle > rectangles, absl::Span< const IntegerValue > rectangle_energies, IntegerValue *x_threshold, IntegerValue *y_threshold, Rectangle *conflict)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
absl::Span< int > FilterBoxesThatAreTooLarge(absl::Span< const Rectangle > cached_rectangles, absl::Span< const IntegerValue > energies, absl::Span< int > boxes)
std::vector< int > GetIntervalArticulationPoints(std::vector< IndexedInterval > *intervals)
int WeightedPick(absl::Span< const double > input, absl::BitGenRef random)
Definition util.cc:382
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
std::string RenderDot(std::optional< Rectangle > bb, absl::Span< const Rectangle > solution)
bool ReportEnergyConflict(Rectangle bounding_box, absl::Span< const int > boxes, SchedulingConstraintHelper *x, SchedulingConstraintHelper *y)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles, absl::Span< int > active_rectangles)
FindRectanglesResult FindRectanglesWithEnergyConflictMC(const std::vector< RectangleInRange > &intervals, absl::BitGenRef random, double temperature, double candidate_energy_usage_factor)
IntegerValue Smallest1DIntersection(IntegerValue range_min, IntegerValue range_max, IntegerValue size, IntegerValue interval_min, IntegerValue interval_max)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
const Variable x
Definition qp_tests.cc:127
int64_t energy
Definition resource.cc:355
int64_t time
Definition resource.cc:1708
IntervalVar * interval
Definition resource.cc:101
std::optional< int64_t > end
int64_t start
const std::optional< Range > & range
Definition statistics.cc:37
const int width
Definition statistics.cc:38