OR-Tools  9.1
diffn_util.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
17
18namespace operations_research {
19namespace sat {
20
21bool Rectangle::IsDisjoint(const Rectangle& other) const {
22 return x_min >= other.x_max || other.x_min >= x_max || y_min >= other.y_max ||
23 other.y_min >= y_max;
24}
25
26std::vector<absl::Span<int>> GetOverlappingRectangleComponents(
27 const std::vector<Rectangle>& rectangles,
28 absl::Span<int> active_rectangles) {
29 if (active_rectangles.empty()) return {};
30
31 std::vector<absl::Span<int>> result;
32 const int size = active_rectangles.size();
33 for (int start = 0; start < size;) {
34 // Find the component of active_rectangles[start].
35 int end = start + 1;
36 for (int i = start; i < end; i++) {
37 for (int j = end; j < size; ++j) {
38 if (!rectangles[active_rectangles[i]].IsDisjoint(
39 rectangles[active_rectangles[j]])) {
40 std::swap(active_rectangles[end++], active_rectangles[j]);
41 }
42 }
43 }
44 if (end > start + 1) {
45 result.push_back(active_rectangles.subspan(start, end - start));
46 }
47 start = end;
48 }
49 return result;
50}
51
52bool ReportEnergyConflict(Rectangle bounding_box, absl::Span<const int> boxes,
55 x->ClearReason();
56 y->ClearReason();
57 IntegerValue total_energy(0);
58 for (const int b : boxes) {
59 const IntegerValue x_min = x->ShiftedStartMin(b);
60 const IntegerValue x_max = x->ShiftedEndMax(b);
61 if (x_min < bounding_box.x_min || x_max > bounding_box.x_max) continue;
62 const IntegerValue y_min = y->ShiftedStartMin(b);
63 const IntegerValue y_max = y->ShiftedEndMax(b);
64 if (y_min < bounding_box.y_min || y_max > bounding_box.y_max) continue;
65
66 x->AddEnergyMinInIntervalReason(b, bounding_box.x_min, bounding_box.x_max);
67 y->AddEnergyMinInIntervalReason(b, bounding_box.y_min, bounding_box.y_max);
68
69 total_energy += x->SizeMin(b) * y->SizeMin(b);
70
71 // We abort early if a subset of boxes is enough.
72 // TODO(user): Also relax the box if possible.
73 if (total_energy > bounding_box.Area()) break;
74 }
75
76 CHECK_GT(total_energy, bounding_box.Area());
77 x->ImportOtherReasons(*y);
78 return x->ReportConflict();
79}
80
81bool BoxesAreInEnergyConflict(const std::vector<Rectangle>& rectangles,
82 const std::vector<IntegerValue>& energies,
83 absl::Span<const int> boxes,
84 Rectangle* conflict) {
85 // First consider all relevant intervals along the x axis.
86 std::vector<IntegerValue> x_starts;
87 std::vector<TaskTime> boxes_by_increasing_x_max;
88 for (const int b : boxes) {
89 x_starts.push_back(rectangles[b].x_min);
90 boxes_by_increasing_x_max.push_back({b, rectangles[b].x_max});
91 }
93 std::sort(boxes_by_increasing_x_max.begin(), boxes_by_increasing_x_max.end());
94
95 std::vector<IntegerValue> y_starts;
96 std::vector<IntegerValue> energy_sum;
97 std::vector<TaskTime> boxes_by_increasing_y_max;
98
99 std::vector<std::vector<int>> stripes(x_starts.size());
100 for (int i = 0; i < boxes_by_increasing_x_max.size(); ++i) {
101 const int b = boxes_by_increasing_x_max[i].task_index;
102 const IntegerValue x_min = rectangles[b].x_min;
103 const IntegerValue x_max = rectangles[b].x_max;
104 for (int j = 0; j < x_starts.size(); ++j) {
105 if (x_starts[j] > x_min) break;
106 stripes[j].push_back(b);
107
108 // Redo the same on the y coordinate for the current x interval
109 // which is [starts[j], x_max].
110 y_starts.clear();
111 boxes_by_increasing_y_max.clear();
112 for (const int b : stripes[j]) {
113 y_starts.push_back(rectangles[b].y_min);
114 boxes_by_increasing_y_max.push_back({b, rectangles[b].y_max});
115 }
117 std::sort(boxes_by_increasing_y_max.begin(),
118 boxes_by_increasing_y_max.end());
119
120 const IntegerValue x_size = x_max - x_starts[j];
121 energy_sum.assign(y_starts.size(), IntegerValue(0));
122 for (int i = 0; i < boxes_by_increasing_y_max.size(); ++i) {
123 const int b = boxes_by_increasing_y_max[i].task_index;
124 const IntegerValue y_min = rectangles[b].y_min;
125 const IntegerValue y_max = rectangles[b].y_max;
126 for (int j = 0; j < y_starts.size(); ++j) {
127 if (y_starts[j] > y_min) break;
128 energy_sum[j] += energies[b];
129 if (energy_sum[j] > x_size * (y_max - y_starts[j])) {
130 if (conflict != nullptr) {
131 *conflict = rectangles[b];
132 for (int k = 0; k < i; ++k) {
133 const int task_index = boxes_by_increasing_y_max[k].task_index;
134 if (rectangles[task_index].y_min >= y_starts[j]) {
135 conflict->TakeUnionWith(rectangles[task_index]);
136 }
137 }
138 }
139 return true;
140 }
141 }
142 }
143 }
144 }
145 return false;
146}
147
148bool AnalyzeIntervals(bool transpose, absl::Span<const int> local_boxes,
149 const std::vector<Rectangle>& rectangles,
150 const std::vector<IntegerValue>& rectangle_energies,
151 IntegerValue* x_threshold, IntegerValue* y_threshold,
152 Rectangle* conflict) {
153 // First, we compute the possible x_min values (removing duplicates).
154 // We also sort the relevant tasks by their x_max.
155 //
156 // TODO(user): If the number of unique x_max is smaller than the number of
157 // unique x_min, it is better to do it the other way around.
158 std::vector<IntegerValue> starts;
159 std::vector<TaskTime> task_by_increasing_x_max;
160 for (const int t : local_boxes) {
161 const IntegerValue x_min =
162 transpose ? rectangles[t].y_min : rectangles[t].x_min;
163 const IntegerValue x_max =
164 transpose ? rectangles[t].y_max : rectangles[t].x_max;
165 starts.push_back(x_min);
166 task_by_increasing_x_max.push_back({t, x_max});
167 }
169
170 // Note that for the same end_max, the order change our heuristic to
171 // evaluate the max_conflict_height.
172 std::sort(task_by_increasing_x_max.begin(), task_by_increasing_x_max.end());
173
174 // The maximum y dimension of a bounding area for which there is a potential
175 // conflict.
176 IntegerValue max_conflict_height(0);
177
178 // This is currently only used for logging.
179 absl::flat_hash_set<std::pair<IntegerValue, IntegerValue>> stripes;
180
181 // All quantities at index j correspond to the interval [starts[j], x_max].
182 std::vector<IntegerValue> energies(starts.size(), IntegerValue(0));
183 std::vector<IntegerValue> y_mins(starts.size(), kMaxIntegerValue);
184 std::vector<IntegerValue> y_maxs(starts.size(), -kMaxIntegerValue);
185 std::vector<IntegerValue> energy_at_max_y(starts.size(), IntegerValue(0));
186 std::vector<IntegerValue> energy_at_min_y(starts.size(), IntegerValue(0));
187
188 // Sentinel.
189 starts.push_back(kMaxIntegerValue);
190
191 // Iterate over all boxes by increasing x_max values.
192 int first_j = 0;
193 const IntegerValue threshold = transpose ? *y_threshold : *x_threshold;
194 for (int i = 0; i < task_by_increasing_x_max.size(); ++i) {
195 const int t = task_by_increasing_x_max[i].task_index;
196
197 const IntegerValue energy = rectangle_energies[t];
198 IntegerValue x_min = rectangles[t].x_min;
199 IntegerValue x_max = rectangles[t].x_max;
200 IntegerValue y_min = rectangles[t].y_min;
201 IntegerValue y_max = rectangles[t].y_max;
202 if (transpose) {
203 std::swap(x_min, y_min);
204 std::swap(x_max, y_max);
205 }
206
207 // Add this box contribution to all the [starts[j], x_max] intervals.
208 while (first_j + 1 < starts.size() && x_max - starts[first_j] > threshold) {
209 ++first_j;
210 }
211 for (int j = first_j; starts[j] <= x_min; ++j) {
212 const IntegerValue old_energy_at_max = energy_at_max_y[j];
213 const IntegerValue old_energy_at_min = energy_at_min_y[j];
214
215 energies[j] += energy;
216
217 const bool is_disjoint = y_min >= y_maxs[j] || y_max <= y_mins[j];
218
219 if (y_min <= y_mins[j]) {
220 if (y_min < y_mins[j]) {
221 y_mins[j] = y_min;
222 energy_at_min_y[j] = energy;
223 } else {
224 energy_at_min_y[j] += energy;
225 }
226 }
227
228 if (y_max >= y_maxs[j]) {
229 if (y_max > y_maxs[j]) {
230 y_maxs[j] = y_max;
231 energy_at_max_y[j] = energy;
232 } else {
233 energy_at_max_y[j] += energy;
234 }
235 }
236
237 // If the new box is disjoint in y from the ones added so far, there
238 // cannot be a new conflict involving this box, so we skip until we add
239 // new boxes.
240 if (is_disjoint) continue;
241
242 const IntegerValue width = x_max - starts[j];
243 IntegerValue conflict_height = CeilRatio(energies[j], width) - 1;
244 if (y_max - y_min > conflict_height) continue;
245 if (conflict_height >= y_maxs[j] - y_mins[j]) {
246 // We have a conflict.
247 if (conflict != nullptr) {
248 *conflict = rectangles[t];
249 for (int k = 0; k < i; ++k) {
250 const int task_index = task_by_increasing_x_max[k].task_index;
251 const IntegerValue task_x_min = transpose
252 ? rectangles[task_index].y_min
253 : rectangles[task_index].x_min;
254 if (task_x_min < starts[j]) continue;
255 conflict->TakeUnionWith(rectangles[task_index]);
256 }
257 }
258 return false;
259 }
260
261 // Because we currently do not have a conflict involving the new box, the
262 // only way to have one is to remove enough energy to reduce the y domain.
263 IntegerValue can_remove = std::min(old_energy_at_min, old_energy_at_max);
264 if (old_energy_at_min < old_energy_at_max) {
265 if (y_maxs[j] - y_min >=
266 CeilRatio(energies[j] - old_energy_at_min, width)) {
267 // In this case, we need to remove at least old_energy_at_max to have
268 // a conflict.
269 can_remove = old_energy_at_max;
270 }
271 } else if (old_energy_at_max < old_energy_at_min) {
272 if (y_max - y_mins[j] >=
273 CeilRatio(energies[j] - old_energy_at_max, width)) {
274 can_remove = old_energy_at_min;
275 }
276 }
277 conflict_height = CeilRatio(energies[j] - can_remove, width) - 1;
278
279 // If the new box height is above the conflict_height, do not count
280 // it now. We only need to consider conflict involving the new box.
281 if (y_max - y_min > conflict_height) continue;
282
283 if (VLOG_IS_ON(2)) stripes.insert({starts[j], x_max});
284 max_conflict_height = std::max(max_conflict_height, conflict_height);
285 }
286 }
287
288 VLOG(2) << " num_starts: " << starts.size() - 1 << "/" << local_boxes.size()
289 << " conflict_height: " << max_conflict_height
290 << " num_stripes:" << stripes.size() << " (<= " << threshold << ")";
291
292 if (transpose) {
293 *x_threshold = std::min(*x_threshold, max_conflict_height);
294 } else {
295 *y_threshold = std::min(*y_threshold, max_conflict_height);
296 }
297 return true;
298}
299
301 const std::vector<Rectangle>& cached_rectangles, absl::Span<int> boxes,
302 IntegerValue threshold_x, IntegerValue threshold_y,
303 absl::BitGenRef random) {
304 size_t new_size = 0;
305 for (const int b : boxes) {
306 const Rectangle& dim = cached_rectangles[b];
307 if (dim.x_max - dim.x_min > threshold_x) continue;
308 if (dim.y_max - dim.y_min > threshold_y) continue;
309 boxes[new_size++] = b;
310 }
311 if (new_size == 0) return {};
312 std::shuffle(&boxes[0], &boxes[0] + new_size, random);
313 return {&boxes[0], new_size};
314}
315
317 const std::vector<Rectangle>& cached_rectangles,
318 const std::vector<IntegerValue>& energies, absl::Span<int> boxes) {
319 // Sort the boxes by increasing area.
320 std::sort(boxes.begin(), boxes.end(), [&cached_rectangles](int a, int b) {
321 return cached_rectangles[a].Area() < cached_rectangles[b].Area();
322 });
323
324 IntegerValue total_energy(0);
325 for (const int box : boxes) total_energy += energies[box];
326
327 // Remove all the large boxes until we have one with area smaller than the
328 // energy of the boxes below.
329 int new_size = boxes.size();
330 while (new_size > 0 &&
331 cached_rectangles[boxes[new_size - 1]].Area() >= total_energy) {
332 --new_size;
333 total_energy -= energies[boxes[new_size]];
334 }
335 return boxes.subspan(0, new_size);
336}
337
338std::ostream& operator<<(std::ostream& out, const IndexedInterval& interval) {
339 return out << "[" << interval.start << ".." << interval.end << " (#"
340 << interval.index << ")]";
341}
342
343void ConstructOverlappingSets(bool already_sorted,
344 std::vector<IndexedInterval>* intervals,
345 std::vector<std::vector<int>>* result) {
346 result->clear();
347 if (already_sorted) {
348 DCHECK(std::is_sorted(intervals->begin(), intervals->end(),
350 } else {
351 std::sort(intervals->begin(), intervals->end(),
353 }
354 IntegerValue min_end_in_set = kMaxIntegerValue;
355 intervals->push_back({-1, kMaxIntegerValue, kMaxIntegerValue}); // Sentinel.
356 const int size = intervals->size();
357
358 // We do a line sweep. The "current" subset crossing the "line" at
359 // (time, time + 1) will be in (*intervals)[start_index, end_index) at the end
360 // of the loop block.
361 int start_index = 0;
362 for (int end_index = 0; end_index < size;) {
363 const IntegerValue time = (*intervals)[end_index].start;
364
365 // First, if there is some deletion, we will push the "old" set to the
366 // result before updating it. Otherwise, we will have a superset later, so
367 // we just continue for now.
368 if (min_end_in_set <= time) {
369 result->push_back({});
370 min_end_in_set = kMaxIntegerValue;
371 for (int i = start_index; i < end_index; ++i) {
372 result->back().push_back((*intervals)[i].index);
373 if ((*intervals)[i].end <= time) {
374 std::swap((*intervals)[start_index++], (*intervals)[i]);
375 } else {
376 min_end_in_set = std::min(min_end_in_set, (*intervals)[i].end);
377 }
378 }
379
380 // Do not output subset of size one.
381 if (result->back().size() == 1) result->pop_back();
382 }
383
384 // Add all the new intervals starting exactly at "time".
385 do {
386 min_end_in_set = std::min(min_end_in_set, (*intervals)[end_index].end);
387 ++end_index;
388 } while (end_index < size && (*intervals)[end_index].start == time);
389 }
390}
391
393 std::vector<IndexedInterval>* intervals,
394 std::vector<std::vector<int>>* components) {
395 components->clear();
396 if (intervals->empty()) return;
397 if (intervals->size() == 1) {
398 components->push_back({intervals->front().index});
399 return;
400 }
401
402 // For correctness, ComparatorByStart is enough, but in unit tests we want to
403 // verify this function against another implementation, and fully defined
404 // sorting with tie-breaking makes that much easier.
405 // If that becomes a performance bottleneck:
406 // - One may want to sort the list outside of this function, and simply
407 // have this function DCHECK that it's sorted by start.
408 // - One may use std::stable_sort() with ComparatorByStart().
409 std::sort(intervals->begin(), intervals->end(),
411
412 IntegerValue end_max_so_far = (*intervals)[0].end;
413 components->push_back({(*intervals)[0].index});
414 for (int i = 1; i < intervals->size(); ++i) {
415 const IndexedInterval& interval = (*intervals)[i];
416 if (interval.start >= end_max_so_far) {
417 components->push_back({interval.index});
418 } else {
419 components->back().push_back(interval.index);
420 }
421 end_max_so_far = std::max(end_max_so_far, interval.end);
422 }
423}
424
426 std::vector<IndexedInterval>* intervals) {
427 std::vector<int> articulation_points;
428 if (intervals->size() < 3) return articulation_points; // Empty.
429 if (DEBUG_MODE) {
430 for (const IndexedInterval& interval : *intervals) {
431 DCHECK_LT(interval.start, interval.end);
432 }
433 }
434
435 std::sort(intervals->begin(), intervals->end(),
437
438 IntegerValue end_max_so_far = (*intervals)[0].end;
439 int index_of_max = 0;
440 IntegerValue prev_end_max = kMinIntegerValue; // Initialized as a sentinel.
441 for (int i = 1; i < intervals->size(); ++i) {
442 const IndexedInterval& interval = (*intervals)[i];
443 if (interval.start >= end_max_so_far) {
444 // New connected component.
445 end_max_so_far = interval.end;
446 index_of_max = i;
447 prev_end_max = kMinIntegerValue;
448 continue;
449 }
450 // Still the same connected component. Was the previous "max" an
451 // articulation point ?
452 if (prev_end_max != kMinIntegerValue && interval.start >= prev_end_max) {
453 // We might be re-inserting the same articulation point: guard against it.
454 if (articulation_points.empty() ||
455 articulation_points.back() != index_of_max) {
456 articulation_points.push_back(index_of_max);
457 }
458 }
459 // Update the max end.
460 if (interval.end > end_max_so_far) {
461 prev_end_max = end_max_so_far;
462 end_max_so_far = interval.end;
463 index_of_max = i;
464 } else if (interval.end > prev_end_max) {
465 prev_end_max = interval.end;
466 }
467 }
468 // Convert articulation point indices to IndexedInterval.index.
469 for (int& index : articulation_points) index = (*intervals)[index].index;
470 return articulation_points;
471}
472
473} // namespace sat
474} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
#define DCHECK(condition)
Definition: base/logging.h:885
#define VLOG(verboselevel)
Definition: base/logging.h:979
int index() const
Returns the index of the interval constraint in the model.
Definition: cp_model.h:395
void ImportOtherReasons(const SchedulingConstraintHelper &other_helper)
Definition: intervals.cc:541
void AddEnergyMinInIntervalReason(int t, IntegerValue min, IntegerValue max)
Definition: intervals.h:600
int64_t b
int64_t a
const bool DEBUG_MODE
Definition: macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition: cp_model.cc:68
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
Definition: diffn_util.cc:392
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:82
std::vector< int > GetIntervalArticulationPoints(std::vector< IndexedInterval > *intervals)
Definition: diffn_util.cc:425
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:26
absl::Span< int > FilterBoxesAndRandomize(const std::vector< Rectangle > &cached_rectangles, absl::Span< int > boxes, IntegerValue threshold_x, IntegerValue threshold_y, absl::BitGenRef random)
Definition: diffn_util.cc:300
bool AnalyzeIntervals(bool transpose, absl::Span< const int > local_boxes, const std::vector< Rectangle > &rectangles, const std::vector< IntegerValue > &rectangle_energies, IntegerValue *x_threshold, IntegerValue *y_threshold, Rectangle *conflict)
Definition: diffn_util.cc:148
bool ReportEnergyConflict(Rectangle bounding_box, absl::Span< const int > boxes, SchedulingConstraintHelper *x, SchedulingConstraintHelper *y)
Definition: diffn_util.cc:52
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
Definition: diffn_util.cc:343
bool BoxesAreInEnergyConflict(const std::vector< Rectangle > &rectangles, const std::vector< IntegerValue > &energies, absl::Span< const int > boxes, Rectangle *conflict)
Definition: diffn_util.cc:81
absl::Span< int > FilterBoxesThatAreTooLarge(const std::vector< Rectangle > &cached_rectangles, const std::vector< IntegerValue > &energies, absl::Span< int > boxes)
Definition: diffn_util.cc:316
Collection of objects used to extend the Constraint Solver library.
int index
Definition: pack.cc:509
int64_t energy
Definition: resource.cc:354
int64_t time
Definition: resource.cc:1691
IntervalVar * interval
Definition: resource.cc:100
void TakeUnionWith(const Rectangle &other)
Definition: diffn_util.h:35
bool IsDisjoint(const Rectangle &other) const
Definition: diffn_util.cc:21
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41