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