Google OR-Tools v9.12
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-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 <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 <string_view>
29#include <tuple>
30#include <type_traits>
31#include <utility>
32#include <vector>
33
34#include "absl/algorithm/container.h"
35#include "absl/container/btree_set.h"
36#include "absl/container/flat_hash_map.h"
37#include "absl/container/flat_hash_set.h"
38#include "absl/container/inlined_vector.h"
39#include "absl/log/check.h"
40#include "absl/random/bit_gen_ref.h"
41#include "absl/types/optional.h"
42#include "absl/types/span.h"
49#include "ortools/sat/util.h"
53
54namespace operations_research {
55namespace sat {
56
57bool Rectangle::IsDisjoint(const Rectangle& other) const {
58 return x_min >= other.x_max || other.x_min >= x_max || y_min >= other.y_max ||
59 other.y_min >= y_max;
60}
61
62absl::InlinedVector<Rectangle, 4> Rectangle::RegionDifference(
63 const Rectangle& other) const {
64 const Rectangle intersect = Intersect(other);
65 if (intersect.SizeX() == 0) {
66 return {*this};
67 }
68
69 //-------------------
70 //| | 4 | |
71 //| |---------| |
72 //| 1 | other | 2 |
73 //| |---------| |
74 //| | 3 | |
75 //-------------------
76 absl::InlinedVector<Rectangle, 4> result;
77 if (x_min < intersect.x_min) {
78 // Piece 1
79 result.push_back({.x_min = x_min,
80 .x_max = intersect.x_min,
81 .y_min = y_min,
82 .y_max = y_max});
83 }
84 if (x_max > intersect.x_max) {
85 // Piece 2
86 result.push_back({.x_min = intersect.x_max,
87 .x_max = x_max,
88 .y_min = y_min,
89 .y_max = y_max});
90 }
91 if (y_min < intersect.y_min) {
92 // Piece 3
93 result.push_back({.x_min = intersect.x_min,
94 .x_max = intersect.x_max,
95 .y_min = y_min,
96 .y_max = intersect.y_min});
97 }
98 if (y_max > intersect.y_max) {
99 // Piece 4
100 result.push_back({.x_min = intersect.x_min,
101 .x_max = intersect.x_max,
102 .y_min = intersect.y_max,
103 .y_max = y_max});
104 }
105
106 return result;
107}
108
110 absl::Span<const Rectangle> rectangles) {
111 if (rectangles.empty()) return {};
112
113 std::vector<std::pair<int, int>> intersections =
115 const int num_intersections = intersections.size();
116 intersections.reserve(num_intersections * 2 + 1);
117 for (int i = 0; i < num_intersections; ++i) {
118 intersections.push_back({intersections[i].second, intersections[i].first});
119 }
120
122 view.ResetFromPairs(intersections, /*minimum_num_nodes=*/rectangles.size());
123 CompactVectorVector<int> components;
124 FindStronglyConnectedComponents(static_cast<int>(rectangles.size()), view,
125 &components);
127 for (int i = 0; i < components.size(); ++i) {
128 absl::Span<const int> component = components[i];
129 result.Add({});
130 for (const int r : component) {
131 result.AppendToLastVector(r);
132 }
133 }
134 return result;
135}
136
137bool ReportEnergyConflict(Rectangle bounding_box, absl::Span<const int> boxes,
140 x->ClearReason();
141 y->ClearReason();
142 IntegerValue total_energy(0);
143 for (const int b : boxes) {
144 const IntegerValue x_min = x->ShiftedStartMin(b);
145 const IntegerValue x_max = x->ShiftedEndMax(b);
146 if (x_min < bounding_box.x_min || x_max > bounding_box.x_max) continue;
147 const IntegerValue y_min = y->ShiftedStartMin(b);
148 const IntegerValue y_max = y->ShiftedEndMax(b);
149 if (y_min < bounding_box.y_min || y_max > bounding_box.y_max) continue;
150
151 x->AddEnergyMinInIntervalReason(b, bounding_box.x_min, bounding_box.x_max);
152 y->AddEnergyMinInIntervalReason(b, bounding_box.y_min, bounding_box.y_max);
153
154 x->AddPresenceReason(b);
156
157 total_energy += x->SizeMin(b) * y->SizeMin(b);
158
159 // We abort early if a subset of boxes is enough.
160 // TODO(user): Also relax the box if possible.
161 if (total_energy > bounding_box.Area()) break;
162 }
163
164 CHECK_GT(total_energy, bounding_box.Area());
165 x->ImportOtherReasons(*y);
166 return x->ReportConflict();
167}
168
169bool BoxesAreInEnergyConflict(absl::Span<const Rectangle> rectangles,
170 absl::Span<const IntegerValue> energies,
171 absl::Span<const int> boxes,
172 Rectangle* conflict) {
173 // First consider all relevant intervals along the x axis.
174 std::vector<IntegerValue> x_starts;
175 std::vector<TaskTime> boxes_by_increasing_x_max;
176 for (const int b : boxes) {
177 x_starts.push_back(rectangles[b].x_min);
178 boxes_by_increasing_x_max.push_back({b, rectangles[b].x_max});
179 }
181 std::sort(boxes_by_increasing_x_max.begin(), boxes_by_increasing_x_max.end());
182
183 std::vector<IntegerValue> y_starts;
184 std::vector<IntegerValue> energy_sum;
185 std::vector<TaskTime> boxes_by_increasing_y_max;
186
187 std::vector<std::vector<int>> stripes(x_starts.size());
188 for (int i = 0; i < boxes_by_increasing_x_max.size(); ++i) {
189 const int b = boxes_by_increasing_x_max[i].task_index;
190 const IntegerValue x_min = rectangles[b].x_min;
191 const IntegerValue x_max = rectangles[b].x_max;
192 for (int j = 0; j < x_starts.size(); ++j) {
193 if (x_starts[j] > x_min) break;
194 stripes[j].push_back(b);
195
196 // Redo the same on the y coordinate for the current x interval
197 // which is [starts[j], x_max].
198 y_starts.clear();
199 boxes_by_increasing_y_max.clear();
200 for (const int b : stripes[j]) {
201 y_starts.push_back(rectangles[b].y_min);
202 boxes_by_increasing_y_max.push_back({b, rectangles[b].y_max});
203 }
205 std::sort(boxes_by_increasing_y_max.begin(),
206 boxes_by_increasing_y_max.end());
207
208 const IntegerValue x_size = x_max - x_starts[j];
209 energy_sum.assign(y_starts.size(), IntegerValue(0));
210 for (int i = 0; i < boxes_by_increasing_y_max.size(); ++i) {
211 const int b = boxes_by_increasing_y_max[i].task_index;
212 const IntegerValue y_min = rectangles[b].y_min;
213 const IntegerValue y_max = rectangles[b].y_max;
214 for (int j = 0; j < y_starts.size(); ++j) {
215 if (y_starts[j] > y_min) break;
216 energy_sum[j] += energies[b];
217 if (energy_sum[j] > x_size * (y_max - y_starts[j])) {
218 if (conflict != nullptr) {
219 *conflict = rectangles[b];
220 for (int k = 0; k < i; ++k) {
221 const int task_index = boxes_by_increasing_y_max[k].task_index;
222 if (rectangles[task_index].y_min >= y_starts[j]) {
223 conflict->GrowToInclude(rectangles[task_index]);
224 }
225 }
226 }
227 return true;
228 }
229 }
230 }
231 }
232 }
233 return false;
234}
235
236bool AnalyzeIntervals(bool transpose, absl::Span<const int> local_boxes,
237 absl::Span<const Rectangle> rectangles,
238 absl::Span<const IntegerValue> rectangle_energies,
239 IntegerValue* x_threshold, IntegerValue* y_threshold,
240 Rectangle* conflict) {
241 // First, we compute the possible x_min values (removing duplicates).
242 // We also sort the relevant tasks by their x_max.
243 //
244 // TODO(user): If the number of unique x_max is smaller than the number of
245 // unique x_min, it is better to do it the other way around.
246 std::vector<IntegerValue> starts;
247 std::vector<TaskTime> task_by_increasing_x_max;
248 for (const int t : local_boxes) {
249 const IntegerValue x_min =
250 transpose ? rectangles[t].y_min : rectangles[t].x_min;
251 const IntegerValue x_max =
252 transpose ? rectangles[t].y_max : rectangles[t].x_max;
253 starts.push_back(x_min);
254 task_by_increasing_x_max.push_back({t, x_max});
255 }
257
258 // Note that for the same end_max, the order change our heuristic to
259 // evaluate the max_conflict_height.
260 std::sort(task_by_increasing_x_max.begin(), task_by_increasing_x_max.end());
261
262 // The maximum y dimension of a bounding area for which there is a potential
263 // conflict.
264 IntegerValue max_conflict_height(0);
265
266 // This is currently only used for logging.
267 absl::flat_hash_set<std::pair<IntegerValue, IntegerValue>> stripes;
268
269 // All quantities at index j correspond to the interval [starts[j], x_max].
270 std::vector<IntegerValue> energies(starts.size(), IntegerValue(0));
271 std::vector<IntegerValue> y_mins(starts.size(), kMaxIntegerValue);
272 std::vector<IntegerValue> y_maxs(starts.size(), -kMaxIntegerValue);
273 std::vector<IntegerValue> energy_at_max_y(starts.size(), IntegerValue(0));
274 std::vector<IntegerValue> energy_at_min_y(starts.size(), IntegerValue(0));
275
276 // Sentinel.
277 starts.push_back(kMaxIntegerValue);
278
279 // Iterate over all boxes by increasing x_max values.
280 int first_j = 0;
281 const IntegerValue threshold = transpose ? *y_threshold : *x_threshold;
282 for (int i = 0; i < task_by_increasing_x_max.size(); ++i) {
283 const int t = task_by_increasing_x_max[i].task_index;
284
285 const IntegerValue energy = rectangle_energies[t];
286 IntegerValue x_min = rectangles[t].x_min;
287 IntegerValue x_max = rectangles[t].x_max;
288 IntegerValue y_min = rectangles[t].y_min;
289 IntegerValue y_max = rectangles[t].y_max;
290 if (transpose) {
291 std::swap(x_min, y_min);
292 std::swap(x_max, y_max);
293 }
294
295 // Add this box contribution to all the [starts[j], x_max] intervals.
296 while (first_j + 1 < starts.size() && x_max - starts[first_j] > threshold) {
297 ++first_j;
298 }
299 for (int j = first_j; starts[j] <= x_min; ++j) {
300 const IntegerValue old_energy_at_max = energy_at_max_y[j];
301 const IntegerValue old_energy_at_min = energy_at_min_y[j];
302
303 energies[j] += energy;
304
305 const bool is_disjoint = y_min >= y_maxs[j] || y_max <= y_mins[j];
306
307 if (y_min <= y_mins[j]) {
308 if (y_min < y_mins[j]) {
309 y_mins[j] = y_min;
310 energy_at_min_y[j] = energy;
311 } else {
312 energy_at_min_y[j] += energy;
313 }
314 }
315
316 if (y_max >= y_maxs[j]) {
317 if (y_max > y_maxs[j]) {
318 y_maxs[j] = y_max;
319 energy_at_max_y[j] = energy;
320 } else {
321 energy_at_max_y[j] += energy;
322 }
323 }
324
325 // If the new box is disjoint in y from the ones added so far, there
326 // cannot be a new conflict involving this box, so we skip until we add
327 // new boxes.
328 if (is_disjoint) continue;
329
330 const IntegerValue width = x_max - starts[j];
331 IntegerValue conflict_height = CeilRatio(energies[j], width) - 1;
332 if (y_max - y_min > conflict_height) continue;
333 if (conflict_height >= y_maxs[j] - y_mins[j]) {
334 // We have a conflict.
335 if (conflict != nullptr) {
336 *conflict = rectangles[t];
337 for (int k = 0; k < i; ++k) {
338 const int task_index = task_by_increasing_x_max[k].task_index;
339 const IntegerValue task_x_min = transpose
340 ? rectangles[task_index].y_min
341 : rectangles[task_index].x_min;
342 if (task_x_min < starts[j]) continue;
343 conflict->GrowToInclude(rectangles[task_index]);
344 }
345 }
346 return false;
347 }
348
349 // Because we currently do not have a conflict involving the new box, the
350 // only way to have one is to remove enough energy to reduce the y domain.
351 IntegerValue can_remove = std::min(old_energy_at_min, old_energy_at_max);
352 if (old_energy_at_min < old_energy_at_max) {
353 if (y_maxs[j] - y_min >=
354 CeilRatio(energies[j] - old_energy_at_min, width)) {
355 // In this case, we need to remove at least old_energy_at_max to have
356 // a conflict.
357 can_remove = old_energy_at_max;
358 }
359 } else if (old_energy_at_max < old_energy_at_min) {
360 if (y_max - y_mins[j] >=
361 CeilRatio(energies[j] - old_energy_at_max, width)) {
362 can_remove = old_energy_at_min;
363 }
364 }
365 conflict_height = CeilRatio(energies[j] - can_remove, width) - 1;
366
367 // If the new box height is above the conflict_height, do not count
368 // it now. We only need to consider conflict involving the new box.
369 if (y_max - y_min > conflict_height) continue;
370
371 if (VLOG_IS_ON(2)) stripes.insert({starts[j], x_max});
372 max_conflict_height = std::max(max_conflict_height, conflict_height);
373 }
374 }
375
376 VLOG(2) << " num_starts: " << starts.size() - 1 << "/" << local_boxes.size()
377 << " conflict_height: " << max_conflict_height
378 << " num_stripes:" << stripes.size() << " (<= " << threshold << ")";
379
380 if (transpose) {
381 *x_threshold = std::min(*x_threshold, max_conflict_height);
382 } else {
383 *y_threshold = std::min(*y_threshold, max_conflict_height);
384 }
385 return true;
386}
387
388absl::Span<int> FilterBoxesAndRandomize(
389 absl::Span<const Rectangle> cached_rectangles, absl::Span<int> boxes,
390 IntegerValue threshold_x, IntegerValue threshold_y,
391 absl::BitGenRef random) {
392 size_t new_size = 0;
393 for (const int b : boxes) {
394 const Rectangle& dim = cached_rectangles[b];
395 if (dim.x_max - dim.x_min > threshold_x) continue;
396 if (dim.y_max - dim.y_min > threshold_y) continue;
397 boxes[new_size++] = b;
398 }
399 if (new_size == 0) return {};
400 std::shuffle(&boxes[0], &boxes[0] + new_size, random);
401 return {&boxes[0], new_size};
402}
403
404absl::Span<int> FilterBoxesThatAreTooLarge(
405 absl::Span<const Rectangle> cached_rectangles,
406 absl::Span<const IntegerValue> energies, absl::Span<int> boxes) {
407 // Sort the boxes by increasing area.
408 std::sort(boxes.begin(), boxes.end(), [cached_rectangles](int a, int b) {
409 return cached_rectangles[a].Area() < cached_rectangles[b].Area();
410 });
411
412 IntegerValue total_energy(0);
413 for (const int box : boxes) total_energy += energies[box];
414
415 // Remove all the large boxes until we have one with area smaller than the
416 // energy of the boxes below.
417 int new_size = boxes.size();
418 while (new_size > 0 &&
419 cached_rectangles[boxes[new_size - 1]].Area() >= total_energy) {
420 --new_size;
421 total_energy -= energies[boxes[new_size]];
422 }
423 return boxes.subspan(0, new_size);
424}
425
426void ConstructOverlappingSets(absl::Span<IndexedInterval> intervals,
427 CompactVectorVector<int>* result,
428 absl::Span<const int> order) {
429 result->clear();
430 DCHECK(std::is_sorted(intervals.begin(), intervals.end(),
432 IntegerValue min_end_in_set = kMaxIntegerValue;
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 const int size = intervals.size();
439 for (int end_index = 0; end_index < size;) {
440 const IntegerValue time = intervals[end_index].start;
441
442 // First, if there is some deletion, we will push the "old" set to the
443 // result before updating it. Otherwise, we will have a superset later, so
444 // we just continue for now.
445 if (min_end_in_set <= time) {
446 // Push the current set to result first if its size is > 1.
447 if (start_index + 1 < end_index) {
448 result->Add({});
449 for (int i = start_index; i < end_index; ++i) {
450 result->AppendToLastVector(intervals[i].index);
451 }
452 }
453
454 // Update the set. Note that we keep the order.
455 min_end_in_set = kMaxIntegerValue;
456 int new_start = end_index;
457 for (int i = end_index; --i >= start_index;) {
458 if (intervals[i].end > time) {
459 min_end_in_set = std::min(min_end_in_set, intervals[i].end);
460 intervals[--new_start] = intervals[i];
461 }
462 }
463 start_index = new_start;
464 }
465
466 // Add all the new intervals starting exactly at "time".
467 // Note that we always add at least one here.
468 const int old_end = end_index;
469 while (end_index < size && intervals[end_index].start == time) {
470 min_end_in_set = std::min(min_end_in_set, intervals[end_index].end);
471 ++end_index;
472 }
473
474 // If order is not empty, make sure we maintain the order.
475 // TODO(user): we could only do that when we push a new set.
476 if (!order.empty() && end_index > old_end) {
477 std::sort(intervals.data() + old_end, intervals.data() + end_index,
478 [order](const IndexedInterval& a, const IndexedInterval& b) {
479 return order[a.index] < order[b.index];
480 });
481 std::inplace_merge(
482 intervals.data() + start_index, intervals.data() + old_end,
483 intervals.data() + end_index,
484 [order](const IndexedInterval& a, const IndexedInterval& b) {
485 return order[a.index] < order[b.index];
486 });
487 }
488 }
489
490 // Push final set.
491 if (start_index + 1 < size) {
492 result->Add({});
493 for (int i = start_index; i < size; ++i) {
494 result->AppendToLastVector(intervals[i].index);
495 }
496 }
497}
498
500 std::vector<IndexedInterval>* intervals,
501 std::vector<std::vector<int>>* components) {
502 components->clear();
503 if (intervals->empty()) return;
504 if (intervals->size() == 1) {
505 components->push_back({intervals->front().index});
506 return;
507 }
508
509 // For correctness, ComparatorByStart is enough, but in unit tests we want to
510 // verify this function against another implementation, and fully defined
511 // sorting with tie-breaking makes that much easier.
512 // If that becomes a performance bottleneck:
513 // - One may want to sort the list outside of this function, and simply
514 // have this function DCHECK that it's sorted by start.
515 // - One may use stable_sort() with ComparatorByStart().
516 std::sort(intervals->begin(), intervals->end(),
518
519 IntegerValue end_max_so_far = (*intervals)[0].end;
520 components->push_back({(*intervals)[0].index});
521 for (int i = 1; i < intervals->size(); ++i) {
522 const IndexedInterval& interval = (*intervals)[i];
523 if (interval.start >= end_max_so_far) {
524 components->push_back({interval.index});
525 } else {
526 components->back().push_back(interval.index);
527 }
528 end_max_so_far = std::max(end_max_so_far, interval.end);
529 }
530}
531
532std::vector<int> GetIntervalArticulationPoints(
533 std::vector<IndexedInterval>* intervals) {
534 std::vector<int> articulation_points;
535 if (intervals->size() < 3) return articulation_points; // Empty.
536 if (DEBUG_MODE) {
537 for (const IndexedInterval& interval : *intervals) {
538 DCHECK_LT(interval.start, interval.end);
539 }
540 }
541
542 std::sort(intervals->begin(), intervals->end(),
543 IndexedInterval::ComparatorByStart());
544
545 IntegerValue end_max_so_far = (*intervals)[0].end;
546 int index_of_max = 0;
547 IntegerValue prev_end_max = kMinIntegerValue; // Initialized as a sentinel.
548 for (int i = 1; i < intervals->size(); ++i) {
549 const IndexedInterval& interval = (*intervals)[i];
550 if (interval.start >= end_max_so_far) {
551 // New connected component.
552 end_max_so_far = interval.end;
553 index_of_max = i;
554 prev_end_max = kMinIntegerValue;
555 continue;
556 }
557 // Still the same connected component. Was the previous "max" an
558 // articulation point ?
559 if (prev_end_max != kMinIntegerValue && interval.start >= prev_end_max) {
560 // We might be re-inserting the same articulation point: guard against it.
561 if (articulation_points.empty() ||
562 articulation_points.back() != index_of_max) {
563 articulation_points.push_back(index_of_max);
564 }
565 }
566 // Update the max end.
567 if (interval.end > end_max_so_far) {
568 prev_end_max = end_max_so_far;
569 end_max_so_far = interval.end;
570 index_of_max = i;
571 } else if (interval.end > prev_end_max) {
572 prev_end_max = interval.end;
573 }
574 }
575 // Convert articulation point indices to IndexedInterval.index.
576 for (int& index : articulation_points) index = (*intervals)[index].index;
577 return articulation_points;
578}
579
580namespace {
581bool IsZeroOrPowerOfTwo(int value) { return (value & (value - 1)) == 0; }
582
583void AppendPairwiseRestriction(const ItemWithVariableSize& item1,
584 const ItemWithVariableSize& item2,
585 std::vector<PairwiseRestriction>* result) {
586 const int state =
587 // box1 can be left of box2.
588 (item1.x.end_min <= item2.x.start_max) +
589 // box1 can be right of box2.
590 2 * (item2.x.end_min <= item1.x.start_max) +
591 // box1 can be below box2.
592 4 * (item1.y.end_min <= item2.y.start_max) +
593 // box1 can be up of box2.
594 8 * (item2.y.end_min <= item1.y.start_max);
595
596 if (!IsZeroOrPowerOfTwo(state)) {
597 return;
598 }
599
600 switch (state) {
601 case 0:
602 // Conflict. The two boxes must overlap in both dimensions.
603 result->push_back(
604 {.first_index = item1.index,
605 .second_index = item2.index,
607 break;
608 case 1:
609 // box2 can only be after box1 on x.
610 if (item1.x.end_min > item2.x.start_min ||
611 item2.x.start_max < item1.x.end_max) {
612 result->push_back({.first_index = item1.index,
613 .second_index = item2.index,
614 .type = PairwiseRestriction::
615 PairwiseRestrictionType::FIRST_LEFT_OF_SECOND});
616 }
617 break;
618 case 2: // box1 an only be after box2 on x.
619 if (item2.x.end_min > item1.x.start_min ||
620 item1.x.start_max < item2.x.end_max) {
621 result->push_back({.first_index = item1.index,
622 .second_index = item2.index,
623 .type = PairwiseRestriction::
624 PairwiseRestrictionType::FIRST_RIGHT_OF_SECOND});
625 }
626 break;
627 case 4:
628 // box2 an only be after box1 on y.
629 if (item1.y.end_min > item2.y.start_min ||
630 item2.y.start_max < item1.y.end_max) {
631 result->push_back({.first_index = item1.index,
632 .second_index = item2.index,
633 .type = PairwiseRestriction::
634 PairwiseRestrictionType::FIRST_BELOW_SECOND});
635 }
636 break;
637 case 8: // box1 an only be after box2 on y.
638 if (item2.y.end_min > item1.y.start_min ||
639 item1.y.start_max < item2.y.end_max) {
640 result->push_back({.first_index = item1.index,
641 .second_index = item2.index,
642 .type = PairwiseRestriction::
643 PairwiseRestrictionType::FIRST_ABOVE_SECOND});
644 }
645 break;
646 default:
647 break;
648 }
649}
650} // namespace
651
652void AppendPairwiseRestrictions(absl::Span<const ItemWithVariableSize> items,
653 std::vector<PairwiseRestriction>* result) {
654 for (int i1 = 0; i1 + 1 < items.size(); ++i1) {
655 for (int i2 = i1 + 1; i2 < items.size(); ++i2) {
656 AppendPairwiseRestriction(items[i1], items[i2], result);
657 }
658 }
659}
660
662 absl::Span<const ItemWithVariableSize> items,
663 absl::Span<const ItemWithVariableSize> other_items,
664 std::vector<PairwiseRestriction>* result) {
665 for (int i1 = 0; i1 < items.size(); ++i1) {
666 for (int i2 = 0; i2 < other_items.size(); ++i2) {
667 AppendPairwiseRestriction(items[i1], other_items[i2], result);
668 }
669 }
670}
671
673 events_.clear();
674 num_rectangles_added_ = 0;
675}
676
677void CapacityProfile::AddRectangle(IntegerValue x_min, IntegerValue x_max,
678 IntegerValue y_min, IntegerValue y_max) {
679 DCHECK_LE(x_min, x_max);
680 if (x_min == x_max) return;
681
682 events_.push_back(
683 StartRectangleEvent(num_rectangles_added_, x_min, y_min, y_max));
684 events_.push_back(EndRectangleEvent(num_rectangles_added_, x_max));
685 ++num_rectangles_added_;
686}
687
688void CapacityProfile::AddMandatoryConsumption(IntegerValue x_min,
689 IntegerValue x_max,
690 IntegerValue y_height) {
691 DCHECK_LE(x_min, x_max);
692 if (x_min == x_max) return;
693
694 events_.push_back(ChangeMandatoryProfileEvent(x_min, y_height));
695 events_.push_back(ChangeMandatoryProfileEvent(x_max, -y_height));
696}
697
699 std::vector<CapacityProfile::Rectangle>* result) {
700 std::sort(events_.begin(), events_.end());
701 IntegerPriorityQueue<QueueElement> min_pq(num_rectangles_added_);
702 IntegerPriorityQueue<QueueElement> max_pq(num_rectangles_added_);
703 IntegerValue mandatory_capacity(0);
704
705 result->clear();
706
707 result->push_back({kMinIntegerValue, IntegerValue(0)});
708
709 for (int i = 0; i < events_.size();) {
710 const IntegerValue current_time = events_[i].time;
711 for (; i < events_.size(); ++i) {
712 const Event& event = events_[i];
713 if (event.time != current_time) break;
714
715 switch (events_[i].type) {
716 case START_RECTANGLE: {
717 min_pq.Add({event.index, -event.y_min});
718 max_pq.Add({event.index, event.y_max});
719 break;
720 }
721 case END_RECTANGLE: {
722 min_pq.Remove(event.index);
723 max_pq.Remove(event.index);
724 break;
725 }
726 case CHANGE_MANDATORY_PROFILE: {
727 mandatory_capacity += event.y_min;
728 break;
729 }
730 }
731 }
732
733 DCHECK(!max_pq.IsEmpty() || mandatory_capacity == 0);
734 const IntegerValue new_height =
735 max_pq.IsEmpty()
736 ? IntegerValue(0)
737 : max_pq.Top().value + min_pq.Top().value - mandatory_capacity;
738 if (new_height != result->back().height) {
739 result->push_back({current_time, new_height});
740 }
741 }
742}
743
744IntegerValue CapacityProfile::GetBoundingArea() {
745 std::sort(events_.begin(), events_.end());
746 IntegerPriorityQueue<QueueElement> min_pq(num_rectangles_added_);
747 IntegerPriorityQueue<QueueElement> max_pq(num_rectangles_added_);
748
749 IntegerValue area(0);
750 IntegerValue previous_time = kMinIntegerValue;
751 IntegerValue previous_height(0);
752
753 for (int i = 0; i < events_.size();) {
754 const IntegerValue current_time = events_[i].time;
755 for (; i < events_.size(); ++i) {
756 const Event& event = events_[i];
757 if (event.time != current_time) break;
758
759 switch (event.type) {
760 case START_RECTANGLE: {
761 min_pq.Add({event.index, -event.y_min});
762 max_pq.Add({event.index, event.y_max});
763 break;
764 }
765 case END_RECTANGLE: {
766 min_pq.Remove(event.index);
767 max_pq.Remove(event.index);
768 break;
769 }
770 case CHANGE_MANDATORY_PROFILE: {
771 break;
772 }
773 }
774 }
775 const IntegerValue new_height =
776 max_pq.IsEmpty() ? IntegerValue(0)
777 : max_pq.Top().value + min_pq.Top().value;
778 if (previous_height != 0) {
779 area += previous_height * (current_time - previous_time);
780 }
781 previous_time = current_time;
782 previous_height = new_height;
783 }
784 return area;
785}
786
787IntegerValue Smallest1DIntersection(IntegerValue range_min,
788 IntegerValue range_max, IntegerValue size,
789 IntegerValue interval_min,
790 IntegerValue interval_max) {
791 // If the item is on the left of the range, we get the intersection between
792 // [range_min, range_min + size] and [interval_min, interval_max].
793 const IntegerValue overlap_on_left =
794 std::min(range_min + size, interval_max) -
795 std::max(range_min, interval_min);
796
797 // If the item is on the right of the range, we get the intersection between
798 // [range_max - size, range_max] and [interval_min, interval_max].
799 const IntegerValue overlap_on_right =
800 std::min(range_max, interval_max) -
801 std::max(range_max - size, interval_min);
802
803 return std::max(IntegerValue(0), std::min(overlap_on_left, overlap_on_right));
804}
805
806ProbingRectangle::ProbingRectangle(
807 const std::vector<RectangleInRange>& intervals)
808 : intervals_(intervals) {
809 minimum_energy_ = 0;
810 if (intervals_.empty()) {
811 return;
812 }
813 interval_points_sorted_by_x_.reserve(intervals_.size() * 4 + 2);
814 interval_points_sorted_by_y_.reserve(intervals_.size() * 4 + 2);
815
816 Rectangle bounding_box = {.x_min = std::numeric_limits<IntegerValue>::max(),
817 .x_max = std::numeric_limits<IntegerValue>::min(),
818 .y_min = std::numeric_limits<IntegerValue>::max(),
819 .y_max = std::numeric_limits<IntegerValue>::min()};
820
821 for (int i = 0; i < intervals_.size(); ++i) {
822 const RectangleInRange& interval = intervals_[i];
823 minimum_energy_ += interval.x_size * interval.y_size;
824
825 bounding_box.x_min =
826 std::min(bounding_box.x_min, interval.bounding_area.x_min);
827 bounding_box.x_max =
828 std::max(bounding_box.x_max, interval.bounding_area.x_max);
829 bounding_box.y_min =
830 std::min(bounding_box.y_min, interval.bounding_area.y_min);
831 bounding_box.y_max =
832 std::max(bounding_box.y_max, interval.bounding_area.y_max);
833
834 interval_points_sorted_by_x_.push_back({interval.bounding_area.x_min, i});
835 interval_points_sorted_by_x_.push_back(
836 {interval.bounding_area.x_min + interval.x_size, i});
837 interval_points_sorted_by_x_.push_back(
838 {interval.bounding_area.x_max - interval.x_size, i});
839 interval_points_sorted_by_x_.push_back({interval.bounding_area.x_max, i});
840
841 interval_points_sorted_by_y_.push_back({interval.bounding_area.y_min, i});
842 interval_points_sorted_by_y_.push_back(
843 {interval.bounding_area.y_min + interval.y_size, i});
844 interval_points_sorted_by_y_.push_back(
845 {interval.bounding_area.y_max - interval.y_size, i});
846 interval_points_sorted_by_y_.push_back({interval.bounding_area.y_max, i});
847 }
848
849 full_energy_ = minimum_energy_;
850 // Add four bogus points in the extremities so we can delegate setting up all
851 // internal state to Shrink().
852 interval_points_sorted_by_x_.push_back({bounding_box.x_min - 1, -1});
853 interval_points_sorted_by_x_.push_back({bounding_box.x_max + 1, -1});
854 interval_points_sorted_by_y_.push_back({bounding_box.y_min - 1, -1});
855 interval_points_sorted_by_y_.push_back({bounding_box.y_max + 1, -1});
856
857 auto comparator = [](const IntervalPoint& a, const IntervalPoint& b) {
858 return std::tie(a.value, a.index) < std::tie(b.value, b.index);
859 };
860 gtl::STLSortAndRemoveDuplicates(&interval_points_sorted_by_x_, comparator);
861 gtl::STLSortAndRemoveDuplicates(&interval_points_sorted_by_y_, comparator);
862
863 grouped_intervals_sorted_by_x_.reserve(interval_points_sorted_by_x_.size());
864 grouped_intervals_sorted_by_y_.reserve(interval_points_sorted_by_y_.size());
865 int i = 0;
866 while (i < interval_points_sorted_by_x_.size()) {
867 int idx_begin = i;
868 while (i < interval_points_sorted_by_x_.size() &&
869 interval_points_sorted_by_x_[i].value ==
870 interval_points_sorted_by_x_[idx_begin].value) {
871 i++;
872 }
873 grouped_intervals_sorted_by_x_.push_back(
874 {interval_points_sorted_by_x_[idx_begin].value,
875 absl::Span<IntervalPoint>(interval_points_sorted_by_x_)
876 .subspan(idx_begin, i - idx_begin)});
877 }
878
879 i = 0;
880 while (i < interval_points_sorted_by_y_.size()) {
881 int idx_begin = i;
882 while (i < interval_points_sorted_by_y_.size() &&
883 interval_points_sorted_by_y_[i].value ==
884 interval_points_sorted_by_y_[idx_begin].value) {
885 i++;
886 }
887 grouped_intervals_sorted_by_y_.push_back(
888 {interval_points_sorted_by_y_[idx_begin].value,
889 absl::Span<IntervalPoint>(interval_points_sorted_by_y_)
890 .subspan(idx_begin, i - idx_begin)});
891 }
892
893 Reset();
894}
895
897 indexes_[Edge::LEFT] = 0;
898 indexes_[Edge::RIGHT] = grouped_intervals_sorted_by_x_.size() - 1;
899 indexes_[Edge::BOTTOM] = 0;
900 indexes_[Edge::TOP] = grouped_intervals_sorted_by_y_.size() - 1;
901
902 next_indexes_[Edge::LEFT] = 1;
903 next_indexes_[Edge::RIGHT] = grouped_intervals_sorted_by_x_.size() - 2;
904 next_indexes_[Edge::BOTTOM] = 1;
905 next_indexes_[Edge::TOP] = grouped_intervals_sorted_by_y_.size() - 2;
906
907 minimum_energy_ = full_energy_;
908 ranges_touching_both_boundaries_[0].clear();
909 ranges_touching_both_boundaries_[1].clear();
910
911 for (int i = 0; i < 4; ++i) {
912 corner_count_[i] = 0;
913 intersect_length_[i] = 0;
914 cached_delta_energy_[i] = 0;
915 }
916
917 // Remove the four bogus points we added.
918 Shrink(Edge::LEFT);
919 Shrink(Edge::BOTTOM);
920 Shrink(Edge::RIGHT);
921 Shrink(Edge::TOP);
922}
923
924Rectangle ProbingRectangle::GetCurrentRectangle() const {
925 return {
926 .x_min = grouped_intervals_sorted_by_x_[indexes_[Edge::LEFT]].coordinate,
927 .x_max = grouped_intervals_sorted_by_x_[indexes_[Edge::RIGHT]].coordinate,
928 .y_min =
929 grouped_intervals_sorted_by_y_[indexes_[Edge::BOTTOM]].coordinate,
930 .y_max = grouped_intervals_sorted_by_y_[indexes_[Edge::TOP]].coordinate};
931}
932
933namespace {
934// Intersects `rectangle` with the largest rectangle that must intersect with
935// the range in some way. To visualize this largest rectangle, imagine the four
936// possible extreme positions for the item in range (the four corners). This
937// rectangle is the one defined by the interior points of each position. This
938// don't use IsDisjoint() because it also works when the rectangle would be
939// malformed (it's bounding box less than twice the size).
940bool CanConsumeEnergy(const Rectangle& rectangle,
941 const RectangleInRange& item) {
942 return (rectangle.x_max > item.bounding_area.x_max - item.x_size) &&
943 (rectangle.y_max > item.bounding_area.y_max - item.y_size) &&
944 (rectangle.x_min < item.bounding_area.x_min + item.x_size) &&
945 (rectangle.y_min < item.bounding_area.y_min + item.y_size);
946}
947
948std::array<bool, 4> GetPossibleEdgeIntersection(const Rectangle& rectangle,
949 const RectangleInRange& range) {
950 std::array<bool, 4> result;
951 using Edge = ProbingRectangle::Edge;
952 result[Edge::LEFT] = rectangle.x_min >= range.bounding_area.x_min;
953 result[Edge::BOTTOM] = rectangle.y_min >= range.bounding_area.y_min;
954 result[Edge::RIGHT] = rectangle.x_max <= range.bounding_area.x_max;
955 result[Edge::TOP] = rectangle.y_max <= range.bounding_area.y_max;
956 return result;
957}
958
959} // namespace
960
961// NOMUTANTS -- This is a sanity check, it is hard to corrupt the data in an
962// unit test to check it will fail.
963void ProbingRectangle::ValidateInvariants() const {
964 const Rectangle current_rectangle = GetCurrentRectangle();
966 IntegerValue intersect_length[4] = {0, 0, 0, 0};
967 IntegerValue corner_count[4] = {0, 0, 0, 0};
968 IntegerValue energy = 0;
969 CHECK_LE(next_indexes_[Edge::LEFT], indexes_[Edge::RIGHT]);
970 CHECK_LE(next_indexes_[Edge::BOTTOM], indexes_[Edge::TOP]);
971 CHECK_GE(next_indexes_[Edge::TOP], indexes_[Edge::BOTTOM]);
972 CHECK_GE(next_indexes_[Edge::RIGHT], indexes_[Edge::LEFT]);
973
974 for (int interval_idx = 0; interval_idx < intervals_.size(); interval_idx++) {
975 const RectangleInRange& range = intervals_[interval_idx];
976
977 const Rectangle min_intersect =
978 range.GetMinimumIntersection(current_rectangle);
979 CHECK_LE(min_intersect.SizeX(), range.x_size);
980 CHECK_LE(min_intersect.SizeY(), range.y_size);
981 energy += min_intersect.Area();
982
983 std::array<bool, 4> touching_boundary = {false, false, false, false};
984 CHECK_EQ(CanConsumeEnergy(current_rectangle, range) &&
985 current_rectangle.Area() != 0,
986 range.GetMinimumIntersectionArea(current_rectangle) != 0);
987 if (CanConsumeEnergy(current_rectangle, range)) {
988 touching_boundary = GetPossibleEdgeIntersection(current_rectangle, range);
989 }
990
991 CHECK_EQ(
992 touching_boundary[Edge::LEFT] && touching_boundary[Edge::RIGHT],
993 ranges_touching_both_boundaries_[Direction::LEFT_AND_RIGHT].contains(
994 interval_idx));
995 CHECK_EQ(
996 touching_boundary[Edge::TOP] && touching_boundary[Edge::BOTTOM],
997 ranges_touching_both_boundaries_[Direction::TOP_AND_BOTTOM].contains(
998 interval_idx));
999
1000 if (touching_boundary[Edge::LEFT] && !touching_boundary[Edge::RIGHT]) {
1001 intersect_length[Edge::LEFT] += Smallest1DIntersection(
1002 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1003 current_rectangle.y_min, current_rectangle.y_max);
1004 }
1005
1006 if (touching_boundary[Edge::RIGHT] && !touching_boundary[Edge::LEFT]) {
1007 intersect_length[Edge::RIGHT] += Smallest1DIntersection(
1008 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1009 current_rectangle.y_min, current_rectangle.y_max);
1010 }
1011
1012 if (touching_boundary[Edge::TOP] && !touching_boundary[Edge::BOTTOM]) {
1013 intersect_length[Edge::TOP] += Smallest1DIntersection(
1014 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1015 current_rectangle.x_min, current_rectangle.x_max);
1016 }
1017
1018 if (touching_boundary[Edge::BOTTOM] && !touching_boundary[Edge::TOP]) {
1019 intersect_length[Edge::BOTTOM] += Smallest1DIntersection(
1020 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1021 current_rectangle.x_min, current_rectangle.x_max);
1022 }
1023
1024 if ((touching_boundary[Edge::LEFT] && touching_boundary[Edge::RIGHT]) ||
1025 (touching_boundary[Edge::TOP] && touching_boundary[Edge::BOTTOM])) {
1026 // We account separately for the problematic items that touches both
1027 // sides.
1028 continue;
1029 }
1030 if (touching_boundary[Edge::BOTTOM] && touching_boundary[Edge::LEFT]) {
1031 corner_count[RectangleInRange::Corner::BOTTOM_LEFT]++;
1032 }
1033 if (touching_boundary[Edge::BOTTOM] && touching_boundary[Edge::RIGHT]) {
1034 corner_count[RectangleInRange::Corner::BOTTOM_RIGHT]++;
1035 }
1036 if (touching_boundary[Edge::TOP] && touching_boundary[Edge::LEFT]) {
1037 corner_count[RectangleInRange::Corner::TOP_LEFT]++;
1038 }
1039 if (touching_boundary[Edge::TOP] && touching_boundary[Edge::RIGHT]) {
1040 corner_count[RectangleInRange::Corner::TOP_RIGHT]++;
1041 }
1042 }
1043
1044 CHECK_EQ(energy, minimum_energy_);
1045 for (int i = 0; i < 4; i++) {
1046 CHECK_EQ(intersect_length[i], intersect_length_[i]);
1047 CHECK_EQ(corner_count[i], corner_count_[i]);
1048 }
1049}
1050
1051namespace {
1052
1053struct EdgeInfo {
1054 using Edge = ProbingRectangle::Edge;
1055 using Direction = ProbingRectangle::Direction;
1056 using Corner = RectangleInRange::Corner;
1057
1058 Edge opposite_edge;
1059
1060 struct OrthogonalInfo {
1061 Edge edge;
1062 Corner adjacent_corner;
1063 };
1064
1065 Direction shrink_direction;
1066 Direction orthogonal_shrink_direction;
1067 // Lower coordinate one first (ie., BOTTOM before TOP, LEFT before RIGHT).
1068 OrthogonalInfo orthogonal_edges[2];
1069};
1070
1071struct EdgeInfoHolder {
1072 using Edge = ProbingRectangle::Edge;
1073 using Direction = ProbingRectangle::Direction;
1074 using Corner = RectangleInRange::Corner;
1075
1076 static constexpr EdgeInfo kLeft = {
1077 .opposite_edge = Edge::RIGHT,
1078 .shrink_direction = Direction::LEFT_AND_RIGHT,
1079 .orthogonal_shrink_direction = Direction::TOP_AND_BOTTOM,
1080 .orthogonal_edges = {
1081 {.edge = Edge::BOTTOM, .adjacent_corner = Corner::BOTTOM_LEFT},
1082 {.edge = Edge::TOP, .adjacent_corner = Corner::TOP_LEFT}}};
1083
1084 static constexpr EdgeInfo kRight = {
1085 .opposite_edge = Edge::LEFT,
1086 .shrink_direction = Direction::LEFT_AND_RIGHT,
1087 .orthogonal_shrink_direction = Direction::TOP_AND_BOTTOM,
1088 .orthogonal_edges = {
1089 {.edge = Edge::BOTTOM, .adjacent_corner = Corner::BOTTOM_RIGHT},
1090 {.edge = Edge::TOP, .adjacent_corner = Corner::TOP_RIGHT}}};
1091 static constexpr EdgeInfo kBottom = {
1092 .opposite_edge = Edge::TOP,
1093 .shrink_direction = Direction::TOP_AND_BOTTOM,
1094 .orthogonal_shrink_direction = Direction::LEFT_AND_RIGHT,
1095 .orthogonal_edges = {
1096 {.edge = Edge::LEFT, .adjacent_corner = Corner::BOTTOM_LEFT},
1097 {.edge = Edge::RIGHT, .adjacent_corner = Corner::BOTTOM_RIGHT}}};
1098 static constexpr EdgeInfo kTop = {
1099 .opposite_edge = Edge::BOTTOM,
1100 .shrink_direction = Direction::TOP_AND_BOTTOM,
1101 .orthogonal_shrink_direction = Direction::LEFT_AND_RIGHT,
1102 .orthogonal_edges = {
1103 {.edge = Edge::LEFT, .adjacent_corner = Corner::TOP_LEFT},
1104 {.edge = Edge::RIGHT, .adjacent_corner = Corner::TOP_RIGHT}}};
1105};
1106
1107constexpr const EdgeInfo& GetEdgeInfo(ProbingRectangle::Edge edge) {
1108 using Edge = ProbingRectangle::Edge;
1109 switch (edge) {
1110 case Edge::LEFT:
1111 return EdgeInfoHolder::kLeft;
1112 case Edge::RIGHT:
1113 return EdgeInfoHolder::kRight;
1114 case Edge::BOTTOM:
1115 return EdgeInfoHolder::kBottom;
1116 case Edge::TOP:
1117 return EdgeInfoHolder::kTop;
1118 }
1119}
1120
1121IntegerValue GetSmallest1DIntersection(ProbingRectangle::Direction direction,
1122 const RectangleInRange& range,
1123 const Rectangle& rectangle) {
1124 switch (direction) {
1125 case ProbingRectangle::Direction::LEFT_AND_RIGHT:
1126 return Smallest1DIntersection(range.bounding_area.x_min,
1127 range.bounding_area.x_max, range.x_size,
1128 rectangle.x_min, rectangle.x_max);
1129 case ProbingRectangle::Direction::TOP_AND_BOTTOM:
1130 return Smallest1DIntersection(range.bounding_area.y_min,
1131 range.bounding_area.y_max, range.y_size,
1132 rectangle.y_min, rectangle.y_max);
1133 }
1134}
1135
1136} // namespace
1137
1138template <ProbingRectangle::Edge edge>
1139void ProbingRectangle::ShrinkImpl() {
1140 constexpr EdgeInfo e = GetEdgeInfo(edge);
1141
1142 bool update_next_index[4] = {false, false, false, false};
1143 update_next_index[edge] = true;
1144
1145 IntegerValue step_1d_size;
1146 minimum_energy_ -= GetShrinkDeltaEnergy(edge);
1147 const std::vector<PointsForCoordinate>& sorted_intervals =
1148 e.shrink_direction == Direction::LEFT_AND_RIGHT
1149 ? grouped_intervals_sorted_by_x_
1150 : grouped_intervals_sorted_by_y_;
1151
1152 const Rectangle prev_rectangle = GetCurrentRectangle();
1153 indexes_[edge] = next_indexes_[edge];
1154 const Rectangle current_rectangle = GetCurrentRectangle();
1155
1156 switch (edge) {
1157 case Edge::LEFT:
1158 step_1d_size = current_rectangle.x_min - prev_rectangle.x_min;
1159 next_indexes_[edge] =
1160 std::min(indexes_[edge] + 1, indexes_[e.opposite_edge]);
1161 next_indexes_[e.opposite_edge] =
1162 std::max(indexes_[edge], next_indexes_[e.opposite_edge]);
1163 break;
1164 case Edge::BOTTOM:
1165 step_1d_size = current_rectangle.y_min - prev_rectangle.y_min;
1166 next_indexes_[edge] =
1167 std::min(indexes_[edge] + 1, indexes_[e.opposite_edge]);
1168 next_indexes_[e.opposite_edge] =
1169 std::max(indexes_[edge], next_indexes_[e.opposite_edge]);
1170 break;
1171 case Edge::RIGHT:
1172 step_1d_size = prev_rectangle.x_max - current_rectangle.x_max;
1173 next_indexes_[edge] =
1174 std::max(indexes_[edge] - 1, indexes_[e.opposite_edge]);
1175 next_indexes_[e.opposite_edge] =
1176 std::min(indexes_[edge], next_indexes_[e.opposite_edge]);
1177 break;
1178 case Edge::TOP:
1179 step_1d_size = prev_rectangle.y_max - current_rectangle.y_max;
1180 next_indexes_[edge] =
1181 std::max(indexes_[edge] - 1, indexes_[e.opposite_edge]);
1182 next_indexes_[e.opposite_edge] =
1183 std::min(indexes_[edge], next_indexes_[e.opposite_edge]);
1184 break;
1185 }
1186
1187 absl::Span<ProbingRectangle::IntervalPoint> items_touching_coordinate =
1188 sorted_intervals[indexes_[edge]].items_touching_coordinate;
1189
1190 IntegerValue delta_corner_count[4] = {0, 0, 0, 0};
1191 for (const auto& item : items_touching_coordinate) {
1192 const RectangleInRange& range = intervals_[item.index];
1193 if (!CanConsumeEnergy(prev_rectangle, range)) {
1194 // This item is out of our area of interest, skip.
1195 continue;
1196 }
1197
1198 const std::array<bool, 4> touching_boundary_before =
1199 GetPossibleEdgeIntersection(prev_rectangle, range);
1200 const std::array<bool, 4> touching_boundary_after =
1201 CanConsumeEnergy(current_rectangle, range)
1202 ? GetPossibleEdgeIntersection(current_rectangle, range)
1203 : std::array<bool, 4>({false, false, false, false});
1204
1205 bool remove_corner[4] = {false, false, false, false};
1206 auto erase_item = [this, &prev_rectangle, &range, &touching_boundary_before,
1207 &remove_corner](Edge edge_to_erase) {
1208 const EdgeInfo& erase_info = GetEdgeInfo(edge_to_erase);
1209 intersect_length_[edge_to_erase] -= GetSmallest1DIntersection(
1210 erase_info.orthogonal_shrink_direction, range, prev_rectangle);
1211
1212 if (touching_boundary_before[erase_info.orthogonal_edges[0].edge] &&
1213 touching_boundary_before[erase_info.orthogonal_edges[1].edge]) {
1214 // Ignore touching both corners
1215 return;
1216 }
1217 for (const auto [orthogonal_edge, corner] : erase_info.orthogonal_edges) {
1218 if (touching_boundary_before[orthogonal_edge]) {
1219 remove_corner[corner] = true;
1220 }
1221 }
1222 };
1223
1224 if (touching_boundary_after[edge] && !touching_boundary_before[edge]) {
1225 if (touching_boundary_before[e.opposite_edge]) {
1226 ranges_touching_both_boundaries_[e.shrink_direction].insert(item.index);
1227 erase_item(e.opposite_edge);
1228 } else {
1229 // Do the opposite of remove_item().
1230 intersect_length_[edge] += GetSmallest1DIntersection(
1231 e.orthogonal_shrink_direction, range, prev_rectangle);
1232 // Update the corner count unless it is touching both.
1233 if (!touching_boundary_before[e.orthogonal_edges[0].edge] ||
1234 !touching_boundary_before[e.orthogonal_edges[1].edge]) {
1235 for (const auto [orthogonal_edge, corner] : e.orthogonal_edges) {
1236 if (touching_boundary_before[orthogonal_edge]) {
1237 delta_corner_count[corner]++;
1238 }
1239 }
1240 }
1241 }
1242 }
1243
1244 for (int i = 0; i < 4; i++) {
1245 const Edge edge_to_update = (Edge)i;
1246 const EdgeInfo& info = GetEdgeInfo(edge_to_update);
1247 const bool remove_edge = touching_boundary_before[edge_to_update] &&
1248 !touching_boundary_after[edge_to_update];
1249 if (!remove_edge) {
1250 continue;
1251 }
1252
1253 update_next_index[edge_to_update] = true;
1254
1255 if (touching_boundary_before[info.opposite_edge]) {
1256 ranges_touching_both_boundaries_[info.shrink_direction].erase(
1257 item.index);
1258 } else {
1259 erase_item(edge_to_update);
1260 }
1261 }
1262
1263 for (int i = 0; i < 4; i++) {
1264 corner_count_[i] -= remove_corner[i];
1265 }
1266 }
1267
1268 // Update the intersection length for items touching both sides.
1269 for (const int idx : ranges_touching_both_boundaries_[e.shrink_direction]) {
1270 const RectangleInRange& range = intervals_[idx];
1271 const std::array<bool, 2> touching_corner =
1272 (e.shrink_direction == Direction::LEFT_AND_RIGHT)
1273 ? std::array<bool, 2>(
1274 {current_rectangle.y_min >= range.bounding_area.y_min,
1275 current_rectangle.y_max <= range.bounding_area.y_max})
1276 : std::array<bool, 2>(
1277 {current_rectangle.x_min >= range.bounding_area.x_min,
1278 current_rectangle.x_max <= range.bounding_area.x_max});
1279 if (touching_corner[0] == touching_corner[1]) {
1280 // Either it is not touching neither corners (so no length to update) or
1281 // it is touching both corners, which will be handled by the "both
1282 // sides" code and should not contribute to intersect_length_.
1283 continue;
1284 }
1285
1286 const IntegerValue incr =
1287 GetSmallest1DIntersection(e.shrink_direction, range, prev_rectangle) -
1288 GetSmallest1DIntersection(e.shrink_direction, range, current_rectangle);
1289 for (int i = 0; i < 2; i++) {
1290 if (touching_corner[i]) {
1291 intersect_length_[e.orthogonal_edges[i].edge] -= incr;
1292 }
1293 }
1294 }
1295
1296 for (const auto [orthogonal_edge, corner] : e.orthogonal_edges) {
1297 intersect_length_[orthogonal_edge] -= corner_count_[corner] * step_1d_size;
1298 }
1299
1300 for (int i = 0; i < 4; i++) {
1301 corner_count_[i] += delta_corner_count[i];
1302 }
1303
1304 auto points_consume_energy =
1305 [this,
1306 &current_rectangle](absl::Span<ProbingRectangle::IntervalPoint> points) {
1307 for (const auto& item : points) {
1308 const RectangleInRange& range = intervals_[item.index];
1309 if (CanConsumeEnergy(current_rectangle, range)) {
1310 return true;
1311 }
1312 }
1313 return false;
1314 };
1315 if (update_next_index[Edge::LEFT]) {
1316 for (; next_indexes_[Edge::LEFT] < indexes_[Edge::RIGHT];
1317 ++next_indexes_[Edge::LEFT]) {
1318 if (points_consume_energy(
1319 grouped_intervals_sorted_by_x_[next_indexes_[Edge::LEFT]]
1320 .items_touching_coordinate)) {
1321 break;
1322 }
1323 }
1324 }
1325 if (update_next_index[Edge::BOTTOM]) {
1326 for (; next_indexes_[Edge::BOTTOM] < indexes_[Edge::TOP];
1327 ++next_indexes_[Edge::BOTTOM]) {
1328 if (points_consume_energy(
1329 grouped_intervals_sorted_by_y_[next_indexes_[Edge::BOTTOM]]
1330 .items_touching_coordinate)) {
1331 break;
1332 }
1333 }
1334 }
1335 if (update_next_index[Edge::RIGHT]) {
1336 for (; next_indexes_[Edge::RIGHT] > indexes_[Edge::LEFT];
1337 --next_indexes_[Edge::RIGHT]) {
1338 if (points_consume_energy(
1339 grouped_intervals_sorted_by_x_[next_indexes_[Edge::RIGHT]]
1340 .items_touching_coordinate)) {
1341 break;
1342 }
1343 }
1344 }
1345 if (update_next_index[Edge::TOP]) {
1346 for (; next_indexes_[Edge::TOP] > indexes_[Edge::BOTTOM];
1347 --next_indexes_[Edge::TOP]) {
1348 if (points_consume_energy(
1349 grouped_intervals_sorted_by_y_[next_indexes_[Edge::TOP]]
1350 .items_touching_coordinate)) {
1351 break;
1352 }
1353 }
1354 }
1355
1356 probe_area_ = current_rectangle.Area();
1357 CacheShrinkDeltaEnergy(0);
1358 CacheShrinkDeltaEnergy(1);
1359}
1360
1361void ProbingRectangle::Shrink(Edge edge) {
1362 switch (edge) {
1364 ShrinkImpl<Edge::LEFT>();
1365 return;
1366 case Edge::BOTTOM:
1367 ShrinkImpl<Edge::BOTTOM>();
1368 return;
1369 case Edge::RIGHT:
1370 ShrinkImpl<Edge::RIGHT>();
1371 return;
1372 case Edge::TOP:
1373 ShrinkImpl<Edge::TOP>();
1374 return;
1375 }
1376}
1377
1378IntegerValue ProbingRectangle::GetShrinkDeltaArea(Edge edge) const {
1379 const Rectangle current_rectangle = GetCurrentRectangle();
1380 const std::vector<PointsForCoordinate>& sorted_intervals =
1381 (edge == Edge::LEFT || edge == Edge::RIGHT)
1382 ? grouped_intervals_sorted_by_x_
1383 : grouped_intervals_sorted_by_y_;
1384 const IntegerValue coordinate =
1385 sorted_intervals[next_indexes_[edge]].coordinate;
1386 switch (edge) {
1387 case Edge::LEFT:
1388 return (coordinate - current_rectangle.x_min) * current_rectangle.SizeY();
1389 case Edge::BOTTOM:
1390 return (coordinate - current_rectangle.y_min) * current_rectangle.SizeX();
1391 case Edge::RIGHT:
1392 return (current_rectangle.x_max - coordinate) * current_rectangle.SizeY();
1393 case Edge::TOP:
1394 return (current_rectangle.y_max - coordinate) * current_rectangle.SizeX();
1395 }
1396}
1397
1398void ProbingRectangle::CacheShrinkDeltaEnergy(int dimension) {
1399 const Rectangle current_rectangle = GetCurrentRectangle();
1400 Rectangle next_rectangle_up = current_rectangle;
1401 Rectangle next_rectangle_down = current_rectangle;
1402 IntegerValue step_1d_size_up, step_1d_size_down;
1403 IntegerValue units_crossed_up, units_crossed_down;
1404 IntegerValue* delta_energy_up_ptr;
1405 IntegerValue* delta_energy_down_ptr;
1406
1407 if (dimension == 0) {
1408 // CanShrink(Edge::RIGHT) and CanShrink(Edge::LEFT) are equivalent
1409 if (!CanShrink(Edge::LEFT)) {
1410 cached_delta_energy_[Edge::LEFT] = 0;
1411 cached_delta_energy_[Edge::RIGHT] = 0;
1412 return;
1413 }
1414
1415 next_rectangle_up.x_min =
1416 grouped_intervals_sorted_by_x_[next_indexes_[Edge::LEFT]].coordinate;
1417 next_rectangle_down.x_max =
1418 grouped_intervals_sorted_by_x_[next_indexes_[Edge::RIGHT]].coordinate;
1419
1420 step_1d_size_up = next_rectangle_up.x_min - current_rectangle.x_min;
1421 step_1d_size_down = current_rectangle.x_max - next_rectangle_down.x_max;
1422 units_crossed_up = intersect_length_[Edge::LEFT];
1423 units_crossed_down = intersect_length_[Edge::RIGHT];
1424 delta_energy_up_ptr = &cached_delta_energy_[Edge::LEFT];
1425 delta_energy_down_ptr = &cached_delta_energy_[Edge::RIGHT];
1426 } else {
1427 if (!CanShrink(Edge::TOP)) {
1428 cached_delta_energy_[Edge::BOTTOM] = 0;
1429 cached_delta_energy_[Edge::TOP] = 0;
1430 return;
1431 }
1432
1433 next_rectangle_up.y_min =
1434 grouped_intervals_sorted_by_y_[next_indexes_[Edge::BOTTOM]].coordinate;
1435 next_rectangle_down.y_max =
1436 grouped_intervals_sorted_by_y_[next_indexes_[Edge::TOP]].coordinate;
1437
1438 step_1d_size_up = next_rectangle_up.y_min - current_rectangle.y_min;
1439 step_1d_size_down = current_rectangle.y_max - next_rectangle_down.y_max;
1440 units_crossed_up = intersect_length_[Edge::BOTTOM];
1441 units_crossed_down = intersect_length_[Edge::TOP];
1442 delta_energy_up_ptr = &cached_delta_energy_[Edge::BOTTOM];
1443 delta_energy_down_ptr = &cached_delta_energy_[Edge::TOP];
1444 }
1445 IntegerValue delta_energy_up = 0;
1446 IntegerValue delta_energy_down = 0;
1447
1448 // Note that the non-deterministic iteration order is fine here.
1449 for (const int idx : ranges_touching_both_boundaries_[dimension]) {
1450 const RectangleInRange& range = intervals_[idx];
1451 const IntegerValue curr_x = Smallest1DIntersection(
1452 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1453 current_rectangle.x_min, current_rectangle.x_max);
1454 const IntegerValue curr_y = Smallest1DIntersection(
1455 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1456 current_rectangle.y_min, current_rectangle.y_max);
1457 const IntegerValue curr = curr_x * curr_y;
1458 delta_energy_up += curr;
1459 delta_energy_down += curr;
1460
1461 if (dimension == 0) {
1462 const IntegerValue up_x = Smallest1DIntersection(
1463 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1464 next_rectangle_up.x_min, next_rectangle_up.x_max);
1465 const IntegerValue down_x = Smallest1DIntersection(
1466 range.bounding_area.x_min, range.bounding_area.x_max, range.x_size,
1467 next_rectangle_down.x_min, next_rectangle_down.x_max);
1468
1469 delta_energy_up -= curr_y * up_x;
1470 delta_energy_down -= curr_y * down_x;
1471 } else {
1472 const IntegerValue up_y = Smallest1DIntersection(
1473 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1474 next_rectangle_up.y_min, next_rectangle_up.y_max);
1475 const IntegerValue down_y = Smallest1DIntersection(
1476 range.bounding_area.y_min, range.bounding_area.y_max, range.y_size,
1477 next_rectangle_down.y_min, next_rectangle_down.y_max);
1478
1479 delta_energy_up -= curr_x * up_y;
1480 delta_energy_down -= curr_x * down_y;
1481 }
1482 }
1483 delta_energy_up += units_crossed_up * step_1d_size_up;
1484 delta_energy_down += units_crossed_down * step_1d_size_down;
1485 *delta_energy_up_ptr = delta_energy_up;
1486 *delta_energy_down_ptr = delta_energy_down;
1487}
1488
1489bool ProbingRectangle::CanShrink(Edge edge) const {
1490 switch (edge) {
1492 case Edge::RIGHT:
1493 return (next_indexes_[Edge::RIGHT] > indexes_[Edge::LEFT]);
1494 case Edge::BOTTOM:
1495 case Edge::TOP:
1496 return (indexes_[Edge::TOP] > next_indexes_[Edge::BOTTOM]);
1497 }
1498}
1499
1500namespace {
1501std::vector<double> GetExpTable() {
1502 std::vector<double> table(101);
1503 for (int i = 0; i <= 100; ++i) {
1504 table[i] = std::exp(-(i - 50) / 5.0);
1505 }
1506 return table;
1507}
1508
1509} // namespace
1510
1511FindRectanglesResult FindRectanglesWithEnergyConflictMC(
1512 const std::vector<RectangleInRange>& intervals, absl::BitGenRef random,
1513 double temperature, double candidate_energy_usage_factor) {
1514 FindRectanglesResult result;
1515 ProbingRectangle ranges(intervals);
1516
1517 static const std::vector<double>* cached_probabilities =
1518 new std::vector<double>(GetExpTable());
1519
1520 const double inv_temp = 1.0 / temperature;
1521 absl::InlinedVector<ProbingRectangle::Edge, 4> candidates;
1522 absl::InlinedVector<IntegerValue, 4> energy_deltas;
1523 absl::InlinedVector<double, 4> weights;
1524 while (!ranges.IsMinimal()) {
1525 const IntegerValue rect_area = ranges.GetCurrentRectangleArea();
1526 const IntegerValue min_energy = ranges.GetMinimumEnergy();
1527 if (min_energy > rect_area) {
1528 result.conflicts.push_back(ranges.GetCurrentRectangle());
1529 } else if (min_energy.value() >
1530 candidate_energy_usage_factor * rect_area.value()) {
1531 result.candidates.push_back(ranges.GetCurrentRectangle());
1532 }
1533 if (min_energy == 0) {
1534 break;
1535 }
1536 candidates.clear();
1537 energy_deltas.clear();
1538
1539 for (int border_idx = 0; border_idx < 4; ++border_idx) {
1540 const ProbingRectangle::Edge border =
1541 static_cast<ProbingRectangle::Edge>(border_idx);
1542 if (!ranges.CanShrink(border)) {
1543 continue;
1544 }
1545 candidates.push_back(border);
1546 const IntegerValue delta_area = ranges.GetShrinkDeltaArea(border);
1547 const IntegerValue delta_energy = ranges.GetShrinkDeltaEnergy(border);
1548 energy_deltas.push_back(delta_energy - delta_area);
1549 }
1550 const IntegerValue min_energy_delta =
1551 *std::min_element(energy_deltas.begin(), energy_deltas.end());
1552 weights.clear();
1553 for (const IntegerValue delta_slack : energy_deltas) {
1554 const int64_t table_lookup =
1555 std::max((int64_t)0,
1556 std::min((int64_t)((delta_slack - min_energy_delta).value() *
1557 5 * inv_temp +
1558 50),
1559 (int64_t)100));
1560 weights.push_back((*cached_probabilities)[table_lookup]);
1561 }
1562 // Pick a change with a probability proportional to exp(- delta_E / Temp)
1563 ranges.Shrink(candidates[WeightedPick(weights, random)]);
1564 }
1565 if (ranges.GetMinimumEnergy() > ranges.GetCurrentRectangleArea()) {
1566 result.conflicts.push_back(ranges.GetCurrentRectangle());
1567 }
1568 return result;
1569}
1570
1571std::string RenderDot(std::optional<Rectangle> bb,
1572 absl::Span<const Rectangle> solution,
1573 std::string_view extra_dot_payload) {
1574 const std::vector<std::string> colors = {"#0000ff80", "#ee00ee80",
1575 "#ff000080", "#eeee0080",
1576 "#00ff0080", "#00eeee80"};
1577 std::stringstream ss;
1578 ss << "digraph {\n";
1579 ss << " graph [ bgcolor=lightgray ]\n";
1580 ss << " node [style=filled shape=box]\n";
1581 if (bb.has_value()) {
1582 ss << " bb [fillcolor=\"grey\" pos=\"" << 2 * bb->x_min + bb->SizeX()
1583 << "," << 2 * bb->y_min + bb->SizeY() << "!\" width=" << 2 * bb->SizeX()
1584 << " height=" << 2 * bb->SizeY() << "]\n";
1585 }
1586 for (int i = 0; i < solution.size(); ++i) {
1587 ss << " " << i << " [fillcolor=\"" << colors[i % colors.size()]
1588 << "\" pos=\"" << 2 * solution[i].x_min + solution[i].SizeX() << ","
1589 << 2 * solution[i].y_min + solution[i].SizeY()
1590 << "!\" width=" << 2 * solution[i].SizeX()
1591 << " height=" << 2 * solution[i].SizeY() << "]\n";
1592 }
1593 ss << extra_dot_payload;
1594 ss << "}\n";
1595 return ss.str();
1596}
1597
1598std::vector<Rectangle> FindEmptySpaces(
1599 const Rectangle& bounding_box, std::vector<Rectangle> ocupied_rectangles) {
1600 // Sorting is not necessary for correctness but makes it faster.
1601 std::sort(ocupied_rectangles.begin(), ocupied_rectangles.end(),
1602 [](const Rectangle& a, const Rectangle& b) {
1603 return std::tuple(a.x_min, -a.x_max, a.y_min) <
1604 std::tuple(b.x_min, -b.x_max, b.y_min);
1605 });
1606 return PavedRegionDifference({bounding_box}, ocupied_rectangles);
1607}
1608
1609std::vector<Rectangle> PavedRegionDifference(
1610 std::vector<Rectangle> original_region,
1611 absl::Span<const Rectangle> area_to_remove) {
1612 std::vector<Rectangle> new_area_to_cover;
1613 new_area_to_cover.reserve(original_region.size());
1614 for (const Rectangle& rectangle : area_to_remove) {
1615 new_area_to_cover.clear();
1616 for (const Rectangle& r : original_region) {
1617 const auto& new_rectangles = r.RegionDifference(rectangle);
1618 new_area_to_cover.insert(new_area_to_cover.end(), new_rectangles.begin(),
1619 new_rectangles.end());
1620 }
1621 original_region.swap(new_area_to_cover);
1622 if (original_region.empty()) break;
1623 }
1624 return original_region;
1625}
1626
1627// Each node in the tree will hold either a single box that is covering the
1628// whole interval represented by the node, or, if no such box exists, a superset
1629// of all the connected components of boxes that are overlapping the interval.
1630// It is a superset and not the exact set of connected components because we
1631// don't delete nodes that became stale, as explained in the class comment
1632// below.
1633struct BinaryTreeNode {
1634 // Contains exactly one element if occupying_box_index != -1.
1635 absl::flat_hash_set<int> connected_components_descendants;
1636 // Hold the x_max of the box that is currently occupying this node (if any) to
1637 // know when it is stale.
1639 // -1 if not occupied.
1641};
1643// A data structure to store which boxes are overlapping the current sweep line.
1644// This uses a binary tree in a slight non-standard way: in a typical use of a
1645// binary tree the actual values are stored in the leaves and the intermediate
1646// nodes are there just to make finding the right leaf efficient. Here we do the
1647// opposite: the values are stored as high up in the tree as possible.
1648// For example, for a tree of size 8 a box that occupies the y interval [0, 7]
1649// will be stored as a single node at the root. In the same tree, a box that
1650// occupies [3, 7] will be stored with the nodes representing the [3, 4), [4, 6)
1651// and [6, 8) intervals. There is no difference on what is stored in the
1652// intermediate nodes or on the leaves. When the sweep line moves, we don't
1653// update the existing nodes on the tree. Thus, some nodes will become stale and
1654// will represent boxes that no longer overlap the sweep line. Those stale nodes
1655// get removed lazily.
1656struct SweepLineIntervalTree {
1657 explicit SweepLineIntervalTree(int max_y, int num_boxes)
1658 : tree(LeafIndex(max_y + 1)), tree_nodes(tree.StorageSize()) {
1659 union_find.SetNumberOfNodes(num_boxes);
1660 }
1661
1662 // Recompute the connected components of a given node, by simply setting it to
1663 // {self} + left.connected_components + right.connected_components.
1664 void RecomputeConnectedComponents(TreeNodeIndex idx) {
1665 BinaryTreeNode& node = tree_nodes[idx];
1666 if (node.occupying_box_index != -1) {
1667 node.connected_components_descendants = {
1668 union_find.FindRoot(node.occupying_box_index)};
1669 return;
1670 }
1671 node.connected_components_descendants.clear();
1672 if (tree.IsLeaf(idx)) return;
1673 for (const TreeNodeIndex child_idx :
1674 {tree.LeftChild(idx), tree.RightChild(idx)}) {
1675 // The order is non-deterministic, but since this is doing the union of
1676 // hash sets the result is deterministic.
1677 for (const int c :
1678 tree_nodes[child_idx].connected_components_descendants) {
1679 node.connected_components_descendants.insert(union_find.FindRoot(c));
1680 }
1681 }
1682 }
1683
1684 // We don't have global deletion method in this class, but this method
1685 // checks if a single interval is fully to the left of the sweep line and
1686 // removes it if so, also updating its connected components.
1687 void RemoveNodeIfXMaxLowerOrEqual(TreeNodeIndex idx, int x_threshold) {
1688 BinaryTreeNode& node = tree_nodes[idx];
1689 if (node.occupying_box_index == -1) {
1690 // Node is already empty.
1691 return;
1692 }
1693 if (node.occupying_box_x_max > x_threshold) {
1694 // Node is still overlapping the sweep line.
1695 return;
1696 }
1697 node.occupying_box_index = -1;
1698 RecomputeConnectedComponents(idx);
1699 }
1700
1701 void UpdateChildrenIntersecting(TreeNodeIndex idx, int sweep_line_x_pos,
1702 int component_index,
1703 std::vector<int>* new_connections) {
1704 if (tree.IsLeaf(idx)) return;
1705 for (const TreeNodeIndex child_idx :
1706 {tree.LeftChild(idx), tree.RightChild(idx)}) {
1707 RemoveNodeIfXMaxLowerOrEqual(child_idx, sweep_line_x_pos);
1708 BinaryTreeNode& child_node = tree_nodes[child_idx];
1709 if (child_node.occupying_box_index != -1) {
1710 if (union_find.AddEdge(child_node.occupying_box_index,
1711 component_index)) {
1712 new_connections->push_back(child_node.occupying_box_index);
1713 }
1714 // No need to recurse here: we already connected everything on this
1715 // branch to the new box.
1716 continue;
1717 }
1718 const bool had_different_component =
1719 absl::c_any_of(child_node.connected_components_descendants,
1720 [this, component_index](const int c) {
1721 return !union_find.Connected(c, component_index);
1722 });
1723 // Since everything is intersecting the current box, all descendants
1724 // must be in one single component.
1725 child_node.connected_components_descendants = {component_index};
1726
1727 // Only go down on the tree if we have below either:
1728 // - a different component to connect.
1729 // - a box to remove that is in a different component.
1730 // In any case, we will visit O(c + d) terminals, where c is the number of
1731 // components we are connecting and d is the number of boxes that we will
1732 // delete. Since a box can only be deleted log N times (one per interval
1733 // it was cut into) and we can only connect O(N) components in total, the
1734 // amortized cost of a call to UpdateChildrenIntersecting is O((log N)^2).
1735 if (had_different_component) {
1736 UpdateChildrenIntersecting(child_idx, sweep_line_x_pos, component_index,
1737 new_connections);
1738 }
1739 }
1740 }
1741
1742 bool UpdateParents(TreeNodeIndex node, int sweep_line_x_pos,
1743 int component_index, std::vector<int>* new_connections) {
1744 if (node == tree.Root()) return false;
1745 for (TreeNodeIndex parent = tree.Parent(node); parent != tree.Root();
1746 parent = tree.Parent(parent)) {
1747 RemoveNodeIfXMaxLowerOrEqual(parent, sweep_line_x_pos);
1748 BinaryTreeNode& parent_value = tree_nodes[parent];
1749 if (parent_value.occupying_box_index != -1) {
1750 if (union_find.AddEdge(parent_value.occupying_box_index,
1751 component_index)) {
1752 new_connections->push_back(parent_value.occupying_box_index);
1753 return true;
1754 }
1755 }
1756 parent_value.connected_components_descendants.insert(component_index);
1757 }
1758 return false;
1759 }
1760
1761 // Add a new box to the sweep line. This will store it in the tree (split in
1762 // log N intervals) check if it connects to one or more existing connected
1763 // components, and for each case it does, add the box that it is overlapping
1764 // to new_connections.
1765 void AddInterval(TreeNodeIndex idx, int sweep_line_x_pos, int box_index,
1766 int x_max, std::vector<int>* new_connections) {
1767 RemoveNodeIfXMaxLowerOrEqual(idx, sweep_line_x_pos);
1768 int cur_box_component = union_find.FindRoot(box_index);
1769 BinaryTreeNode& node = tree_nodes[idx];
1770 if (node.occupying_box_index == -1) {
1771 node.connected_components_descendants = {box_index};
1772 node.occupying_box_index = box_index;
1773 node.occupying_box_x_max = x_max;
1774 const bool had_occupied_parent = UpdateParents(
1775 idx, sweep_line_x_pos, cur_box_component, new_connections);
1776 // We can only be connecting children if it is not already connect via
1777 // something above on the tree.
1778 if (!had_occupied_parent) {
1779 UpdateChildrenIntersecting(idx, sweep_line_x_pos, cur_box_component,
1780 new_connections);
1781 }
1782 } else {
1783 // We have already something fully occupying this interval.
1784 if (union_find.AddEdge(node.occupying_box_index, cur_box_component)) {
1785 new_connections->push_back(node.occupying_box_index);
1786 cur_box_component = union_find.FindRoot(cur_box_component);
1787 }
1788 node.connected_components_descendants = {cur_box_component};
1789 if (node.occupying_box_x_max < x_max) {
1790 // Replace the existing box by the new one.
1791 node.occupying_box_index = box_index;
1792 node.occupying_box_x_max = x_max;
1793 }
1794 }
1795 }
1796
1797 FixedShapeBinaryTree tree;
1798 util_intops::StrongVector<TreeNodeIndex, BinaryTreeNode> tree_nodes;
1802struct Rectangle32 {
1803 int x_min;
1810// Requires that rectangles are sorted by x_min and that sizes on both
1811// dimensions are > 0.
1812std::vector<std::pair<int, int>> FindPartialRectangleIntersectionsImpl(
1813 absl::Span<const Rectangle32> rectangles, int32_t y_max) {
1814 // We are going to use a sweep line algorithm to find the intersections.
1815 // First, we sort the rectangles by their x coordinates, then consider a sweep
1816 // line that goes from the left to the right. See the comment on the
1817 // SweepLineIntervalTree class for more details about what we store for each
1818 // line.
1819 DCHECK(std::is_sorted(rectangles.begin(), rectangles.end(),
1820 [](const Rectangle32& a, const Rectangle32& b) {
1821 return a.x_min < b.x_min;
1822 }));
1823
1824 SweepLineIntervalTree interval_tree(y_max, rectangles.size());
1825
1826 std::vector<TreeNodeIndex> interval_pieces;
1827 std::vector<int> new_connections;
1828 std::vector<std::pair<int, int>> arcs;
1829 for (int rectangle_index = 0; rectangle_index < rectangles.size();
1830 ++rectangle_index) {
1831 DCHECK_LT(rectangles[rectangle_index].x_min,
1832 rectangles[rectangle_index].x_max);
1833 DCHECK_LT(rectangles[rectangle_index].y_min,
1834 rectangles[rectangle_index].y_max);
1835 const int sweep_line_x_pos = rectangles[rectangle_index].x_min;
1836 const Rectangle32& r = rectangles[rectangle_index];
1837 interval_pieces.clear();
1838 interval_tree.tree.PartitionIntervalIntoNodes(
1839 LeafIndex(r.y_min), LeafIndex(r.y_max - 1), &interval_pieces);
1840 new_connections.clear();
1841 for (const TreeNodeIndex& node : interval_pieces) {
1842 interval_tree.AddInterval(node, sweep_line_x_pos, rectangle_index,
1843 r.x_max, &new_connections);
1844 }
1845 for (const int new_connection : new_connections) {
1846 arcs.push_back({rectangles[new_connection].index, r.index});
1847 }
1848 }
1849 return arcs;
1850}
1851
1852namespace {
1853struct PostProcessedResult {
1854 std::vector<Rectangle32> rectangles_sorted_by_x_min;
1855 std::pair<int32_t, int32_t> bounding_box; // Always starting at (0,0).
1856};
1857
1858PostProcessedResult ConvertToRectangle32WithNonZeroSizes(
1859 absl::Span<const Rectangle> rectangles) {
1860 // This function is a preprocessing function for algorithms that find overlap
1861 // between rectangles. It does the following:
1862 // - It converts the arbitrary int64_t coordinates into a small integer by
1863 // sorting the possible values and assigning them consecutive integers.
1864 // - It grows zero size intervals to make them size one. This simplifies
1865 // things considerably, since it is hard to reason about degenerated
1866 // rectangles in the general algorithm.
1867 //
1868 // Note that the last point need to be done with care. Imagine the following
1869 // example:
1870 // +----------+
1871 // | |
1872 // | +--------------+
1873 // | | |
1874 // | | p,q r |
1875 // | +----*-----*-+-+
1876 // | | |
1877 // | | |
1878 // | | |
1879 // | +------------+
1880 // | |
1881 // | |
1882 // +----------+
1883 // Where p,q and r are points (ie, boxes of size 0x0) and p and q have the
1884 // same coordinates. We replace them by the following:
1885 // +----------+
1886 // | |
1887 // | +----------------------+
1888 // | | |
1889 // | | |
1890 // | +----+-+---------------+
1891 // | | |p|
1892 // | | +-+-+
1893 // | | |q|
1894 // | | +-+ +-+
1895 // | | |r|
1896 // | +--------------+-+---+
1897 // | | |
1898 // | | |
1899 // | | |
1900 // | +--------------------+
1901 // | |
1902 // | |
1903 // +----------+
1904 //
1905 // That is a pretty radical deformation of the original shape, but it retains
1906 // the property of whether a pair of rectangles intersect or not.
1907
1908 if (rectangles.empty()) return {};
1909
1910 enum class Event {
1911 kEnd = 0,
1912 kPoint = 1,
1913 kBegin = 2,
1914 };
1915 std::vector<std::tuple<IntegerValue, Event, int>> x_events;
1916 std::vector<std::tuple<IntegerValue, Event, int>> y_events;
1917 x_events.reserve(rectangles.size() * 2);
1918 y_events.reserve(rectangles.size() * 2);
1919 for (int i = 0; i < rectangles.size(); ++i) {
1920 const Rectangle& r = rectangles[i];
1921 DCHECK_GE(r.SizeX(), 0);
1922 DCHECK_GE(r.SizeY(), 0);
1923 if (r.SizeX() == 0) {
1924 x_events.push_back({r.x_min, Event::kPoint, i});
1925 } else {
1926 x_events.push_back({r.x_min, Event::kBegin, i});
1927 x_events.push_back({r.x_max, Event::kEnd, i});
1928 }
1929 if (r.SizeY() == 0) {
1930 y_events.push_back({r.y_min, Event::kPoint, i});
1931 } else {
1932 y_events.push_back({r.y_min, Event::kBegin, i});
1933 y_events.push_back({r.y_max, Event::kEnd, i});
1934 }
1935 }
1936 std::sort(y_events.begin(), y_events.end());
1937
1938 std::vector<Rectangle32> rectangles32;
1939 rectangles32.resize(rectangles.size());
1940 IntegerValue prev_y = 0;
1941 Event prev_event = Event::kEnd;
1942 int cur_index = -1;
1943 for (int i = 0; i < y_events.size(); ++i) {
1944 const auto [y, event, index] = y_events[i];
1945 if ((prev_event != event && prev_event != Event::kEnd) || prev_y != y ||
1946 event == Event::kPoint || cur_index == -1) {
1947 ++cur_index;
1948 }
1949
1950 switch (event) {
1951 case Event::kBegin:
1952 rectangles32[index].y_min = cur_index;
1953 rectangles32[index].index = index;
1954 break;
1955 case Event::kEnd:
1956 rectangles32[index].y_max = cur_index;
1957 break;
1958 case Event::kPoint:
1959 rectangles32[index].y_min = cur_index;
1960 rectangles32[index].y_max = cur_index + 1;
1961 rectangles32[index].index = index;
1962 break;
1963 }
1964 prev_event = event;
1965 prev_y = y;
1966 }
1967 const int max_y_index = cur_index + 1;
1968
1969 gtl::STLClearObject(&y_events);
1970
1971 std::sort(x_events.begin(), x_events.end());
1972 IntegerValue prev_x = 0;
1973 prev_event = Event::kEnd;
1974 cur_index = -1;
1975 for (int i = 0; i < x_events.size(); ++i) {
1976 const auto [x, event, index] = x_events[i];
1977 if ((prev_event != event && prev_event != Event::kEnd) || prev_x != x ||
1978 event == Event::kPoint || cur_index == -1) {
1979 ++cur_index;
1980 }
1981
1982 switch (event) {
1983 case Event::kBegin:
1984 rectangles32[index].x_min = cur_index;
1985 break;
1986 case Event::kEnd:
1987 rectangles32[index].x_max = cur_index;
1988 break;
1989 case Event::kPoint:
1990 rectangles32[index].x_min = cur_index;
1991 rectangles32[index].x_max = cur_index + 1;
1992 break;
1993 }
1994 prev_event = event;
1995 prev_x = x;
1996 }
1997 const int max_x_index = cur_index + 1;
1998
1999 std::vector<Rectangle32> sorted_rectangles32;
2000 sorted_rectangles32.reserve(rectangles.size());
2001 for (int i = 0; i < x_events.size(); ++i) {
2002 const auto [x, event, index] = x_events[i];
2003 if (event == Event::kBegin || event == Event::kPoint) {
2004 sorted_rectangles32.push_back(rectangles32[index]);
2005 }
2006 }
2007
2008 return {sorted_rectangles32, {max_x_index, max_y_index}};
2009}
2010template <typename RectangleT>
2011std::optional<std::pair<int, int>> FindOneIntersectionIfPresentImpl(
2012 absl::Span<const RectangleT> rectangles) {
2013 using CoordinateType = std::decay_t<decltype(RectangleT::x_min)>;
2014 DCHECK(absl::c_is_sorted(rectangles,
2015 [](const RectangleT& a, const RectangleT& b) {
2016 return a.x_min < b.x_min;
2017 }));
2018
2019 // Set of box intersection the sweep line. We only store y_min, other
2020 // coordinates can be accessed via rectangles[index].coordinate.
2021 struct Element {
2022 mutable int index;
2023 CoordinateType y_min;
2024 bool operator<(const Element& other) const { return y_min < other.y_min; }
2025 };
2026
2027 // Note: To use btree_set that has no iterator stability, we have to be
2028 // a bit careful below.
2029 absl::btree_set<Element> interval_set;
2030
2031 for (int i = 0; i < rectangles.size(); ++i) {
2032 const CoordinateType x = rectangles[i].x_min;
2033 const CoordinateType y_min = rectangles[i].y_min;
2034 const CoordinateType y_max = rectangles[i].y_max;
2035
2036 // TODO(user): We can handle that, but it require some changes below.
2037 DCHECK_LE(y_min, y_max);
2038
2039 // Try to add this rectangle to the set, if there is an intersection, lazily
2040 // remove it if its x_max is already passed, otherwise report the
2041 // intersection.
2042 auto [it, inserted] = interval_set.insert({i, y_min});
2043 if (!inserted) {
2044 if (rectangles[it->index].x_max <= x) {
2045 // We just replace if the rectangle at position i is stale.
2046 it->index = i;
2047 } else {
2048 // Intersection.
2049 return {{it->index, i}};
2050 }
2051 } else {
2052 // If there was no element at position y_min, we need to test if the
2053 // interval before is stale or if it overlap with the new one.
2054 if (it != interval_set.begin()) {
2055 auto it_before = it;
2056 --it_before;
2057
2058 // Lazy erase stale entry.
2059 if (rectangles[it_before->index].x_max <= x) {
2060 // For absl::btree_set we don't have iterator stability, so we do need
2061 // to re-assign 'it' to the element just after the one we erased.
2062 it = interval_set.erase(it_before);
2063 } else {
2064 DCHECK_LE(it_before->y_min, y_min);
2065 const CoordinateType y_max_before =
2066 rectangles[it_before->index].y_max;
2067 if (y_max_before > y_min) {
2068 // Intersection.
2069 return {{it_before->index, i}};
2070 }
2071 }
2072 }
2073 }
2074
2075 // We handled the part before, now we need to deal with the interval that
2076 // starts after y_min.
2077 ++it;
2078 while (it != interval_set.end()) {
2079 // Lazy erase stale entry.
2080 if (rectangles[it->index].x_max <= x) {
2081 it = interval_set.erase(it);
2082 continue;
2083 }
2084
2085 DCHECK_LE(y_min, it->y_min);
2086 if (y_max > it->y_min) {
2087 // Intersection.
2088 return {{it->index, i}};
2089 }
2090 break;
2091 }
2092 }
2093
2094 return {};
2095}
2096
2097} // namespace
2098
2099std::vector<std::pair<int, int>> FindPartialRectangleIntersections(
2100 absl::Span<const Rectangle> rectangles) {
2101 auto postprocessed = ConvertToRectangle32WithNonZeroSizes(rectangles);
2103 postprocessed.rectangles_sorted_by_x_min,
2104 postprocessed.bounding_box.second);
2105}
2106
2107std::optional<std::pair<int, int>> FindOneIntersectionIfPresent(
2108 absl::Span<const Rectangle> rectangles) {
2109 return FindOneIntersectionIfPresentImpl(rectangles);
2110}
2111
2112std::optional<std::pair<int, int>> FindOneIntersectionIfPresentWithZeroArea(
2113 absl::Span<const Rectangle> rectangles) {
2114 auto postprocessed = ConvertToRectangle32WithNonZeroSizes(rectangles);
2115 std::optional<std::pair<int, int>> result = FindOneIntersectionIfPresentImpl(
2116 absl::MakeConstSpan(postprocessed.rectangles_sorted_by_x_min));
2117 if (!result.has_value()) return {};
2118 return {{postprocessed.rectangles_sorted_by_x_min[result->first].index,
2119 postprocessed.rectangles_sorted_by_x_min[result->second].index}};
2120}
2121
2122} // namespace sat
2123} // namespace operations_research
A connected components finder that only works on dense ints.
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)
void ResetFromPairs(const Collection &pairs, int minimum_num_nodes=0)
Similar to ResetFromFlatMapping().
Definition util.h:881
IntegerValue GetShrinkDeltaEnergy(Edge edge) const
How much of GetMinimumEnergy() will change if Shrink() is called.
Definition diffn_util.h:592
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.
void ClearReason()
Functions to clear and then set the current reason.
void AddEnergyMinInIntervalReason(int t, IntegerValue min, IntegerValue max)
const bool DEBUG_MODE
Definition macros.h:26
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
void STLClearObject(T *obj)
Definition stl_util.h:123
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
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)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
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)
void AppendPairwiseRestrictions(absl::Span< const ItemWithVariableSize > items, std::vector< PairwiseRestriction > *result)
void ConstructOverlappingSets(absl::Span< IndexedInterval > intervals, CompactVectorVector< int > *result, absl::Span< const int > order)
int WeightedPick(absl::Span< const double > input, absl::BitGenRef random)
Definition util.cc:426
bool ReportEnergyConflict(Rectangle bounding_box, absl::Span< const int > boxes, SchedulingConstraintHelper *x, SchedulingConstraintHelper *y)
std::vector< std::pair< int, int > > FindPartialRectangleIntersectionsImpl(absl::Span< const Rectangle32 > rectangles, int32_t y_max)
FindRectanglesResult FindRectanglesWithEnergyConflictMC(const std::vector< RectangleInRange > &intervals, absl::BitGenRef random, double temperature, double candidate_energy_usage_factor)
std::vector< Rectangle > PavedRegionDifference(std::vector< Rectangle > original_region, absl::Span< const Rectangle > area_to_remove)
bool BoxesAreInEnergyConflict(absl::Span< const Rectangle > rectangles, absl::Span< const IntegerValue > energies, absl::Span< const int > boxes, Rectangle *conflict)
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(absl::Span< const Rectangle > rectangles)
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.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
int occupying_box_index
-1 if not occupied.
absl::flat_hash_set< int > connected_components_descendants
Contains exactly one element if occupying_box_index != -1.
void GrowToInclude(const Rectangle &other)
Definition diffn_util.h:50
absl::InlinedVector< Rectangle, 4 > RegionDifference(const Rectangle &other) const
Definition diffn_util.cc:62
bool IsDisjoint(const Rectangle &other) const
Definition diffn_util.cc:57
Rectangle Intersect(const Rectangle &other) const
The methods below are not meant to be used with zero-area rectangles.
Definition diffn_util.h:105
DenseConnectedComponentsFinder union_find
void RemoveNodeIfXMaxLowerOrEqual(TreeNodeIndex idx, int x_threshold)
void UpdateChildrenIntersecting(TreeNodeIndex idx, int sweep_line_x_pos, int component_index, std::vector< int > *new_connections)
util_intops::StrongVector< TreeNodeIndex, BinaryTreeNode > tree_nodes