Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sorted_interval_list.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 <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <cstdlib>
20#include <limits>
21#include <ostream>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/container/inlined_vector.h"
27#include "absl/numeric/int128.h"
28#include "absl/strings/str_format.h"
29#include "absl/types/span.h"
31#include "ortools/base/types.h"
33
34namespace operations_research {
35
36std::string ClosedInterval::DebugString() const {
37 if (start == end) return absl::StrFormat("[%d]", start);
38 return absl::StrFormat("[%d,%d]", start, end);
39}
40
42 absl::Span<const ClosedInterval> intervals) {
43 for (int i = 1; i < intervals.size(); ++i) {
44 if (intervals[i - 1].start > intervals[i - 1].end) return false;
45 // First test make sure that intervals[i - 1].end + 1 will not overflow.
46 if (intervals[i - 1].end >= intervals[i].start ||
47 intervals[i - 1].end + 1 >= intervals[i].start) {
48 return false;
49 }
50 }
51 return intervals.empty() ? true
52 : intervals.back().start <= intervals.back().end;
53}
54
55namespace {
56
57template <class Intervals>
58std::string IntervalsAsString(const Intervals& intervals) {
59 std::string result;
60 for (ClosedInterval interval : intervals) {
61 result += interval.DebugString();
62 }
63 if (result.empty()) result = "[]";
64 return result;
65}
66
67void SortAndRemoveInvalidIntervals(
68 absl::InlinedVector<ClosedInterval, 1>* intervals) {
69 intervals->erase(std::remove_if(intervals->begin(), intervals->end(),
70 [](const ClosedInterval& interval) {
71 return interval.start > interval.end;
72 }),
73 intervals->end());
74 std::sort(intervals->begin(), intervals->end());
75}
76
77// Transforms a sorted list of intervals in a sorted DISJOINT list for which
78// IntervalsAreSortedAndNonAdjacent() would return true.
79void UnionOfSortedIntervals(absl::InlinedVector<ClosedInterval, 1>* intervals) {
80 DCHECK(std::is_sorted(intervals->begin(), intervals->end()));
81 const int size = intervals->size();
82 if (size == 0) return;
83
84 int new_size = 1;
85 for (int i = 1; i < size; ++i) {
86 const ClosedInterval& current = (*intervals)[i];
87 const int64_t end = (*intervals)[new_size - 1].end;
88 if (end == std::numeric_limits<int64_t>::max() ||
89 current.start <= end + 1) {
90 (*intervals)[new_size - 1].end = std::max(current.end, end);
91 continue;
92 }
93 (*intervals)[new_size++] = current;
94 }
95 intervals->resize(new_size);
96
97 // This is important for InlinedVector in the case the result is a single
98 // intervals.
99 intervals->shrink_to_fit();
100 DCHECK(IntervalsAreSortedAndNonAdjacent(*intervals));
101}
102
103} // namespace
104
105// TODO(user): Use MathUtil::CeilOfRatio / FloorOfRatio instead.
106int64_t CeilRatio(int64_t value, int64_t positive_coeff) {
107 DCHECK_GT(positive_coeff, 0);
108 const int64_t result = value / positive_coeff;
109 const int64_t adjust = static_cast<int64_t>(result * positive_coeff < value);
110 return result + adjust;
111}
112
113int64_t FloorRatio(int64_t value, int64_t positive_coeff) {
114 DCHECK_GT(positive_coeff, 0);
115 const int64_t result = value / positive_coeff;
116 const int64_t adjust = static_cast<int64_t>(result * positive_coeff > value);
117 return result - adjust;
118}
119
120std::ostream& operator<<(std::ostream& out, const ClosedInterval& interval) {
121 return out << interval.DebugString();
122}
123
124std::ostream& operator<<(std::ostream& out,
125 const std::vector<ClosedInterval>& intervals) {
126 return out << IntervalsAsString(intervals);
127}
128
129std::ostream& operator<<(std::ostream& out, const Domain& domain) {
130 return out << IntervalsAsString(domain);
131}
132
133Domain::Domain(int64_t value) : intervals_({{value, value}}) {}
134
135// HACK(user): We spare significant time if we use an initializer here, because
136// InlineVector<1> is able to recognize the fast path or "exactly one element".
137// I was unable to obtain the same performance with any other recipe, I always
138// had at least 1 more cycle. See BM_SingleIntervalDomainConstructor.
139// Since the constructor takes very few cycles (most likely less than 10),
140// that's quite significant.
141namespace {
142inline ClosedInterval UncheckedClosedInterval(int64_t s, int64_t e) {
143 ClosedInterval i;
144 i.start = s;
145 i.end = e;
146 return i;
147}
148} // namespace
149
150Domain::Domain(int64_t left, int64_t right)
151 : intervals_({UncheckedClosedInterval(left, right)}) {
152 if (left > right) intervals_.clear();
153}
154
156
157Domain Domain::FromValues(std::vector<int64_t> values) {
158 std::sort(values.begin(), values.end());
159 Domain result;
160 for (const int64_t v : values) {
161 if (result.intervals_.empty() || v > result.intervals_.back().end + 1) {
162 result.intervals_.push_back({v, v});
163 } else {
164 result.intervals_.back().end = v;
165 }
166 if (result.intervals_.back().end == std::numeric_limits<int64_t>::max()) {
167 break;
168 }
169 }
170 return result;
171}
172
173Domain Domain::FromIntervals(absl::Span<const ClosedInterval> intervals) {
174 Domain result;
175 result.intervals_.assign(intervals.begin(), intervals.end());
176 SortAndRemoveInvalidIntervals(&result.intervals_);
177 UnionOfSortedIntervals(&result.intervals_);
178 return result;
179}
180
182 absl::Span<const int64_t> flat_intervals) {
183 DCHECK(flat_intervals.size() % 2 == 0) << flat_intervals.size();
184 Domain result;
185 result.intervals_.reserve(flat_intervals.size() / 2);
186 for (int i = 0; i < flat_intervals.size(); i += 2) {
187 result.intervals_.push_back({flat_intervals[i], flat_intervals[i + 1]});
188 }
189 SortAndRemoveInvalidIntervals(&result.intervals_);
190 UnionOfSortedIntervals(&result.intervals_);
191 return result;
192}
193
194Domain Domain::FromFlatIntervals(const std::vector<int64_t>& flat_intervals) {
195 return FromFlatSpanOfIntervals(absl::MakeSpan(flat_intervals));
196}
197
199 const std::vector<std::vector<int64_t>>& intervals) {
200 Domain result;
201 for (const std::vector<int64_t>& interval : intervals) {
202 if (interval.size() == 1) {
203 result.intervals_.push_back({interval[0], interval[0]});
204 } else {
205 DCHECK_EQ(interval.size(), 2);
206 result.intervals_.push_back({interval[0], interval[1]});
207 }
208 }
209 SortAndRemoveInvalidIntervals(&result.intervals_);
210 UnionOfSortedIntervals(&result.intervals_);
211 return result;
212}
213
214bool Domain::IsEmpty() const { return intervals_.empty(); }
215
216bool Domain::IsFixed() const { return Min() == Max(); }
217
218int64_t Domain::Size() const {
219 int64_t size = 0;
220 for (const ClosedInterval interval : intervals_) {
222 size, operations_research::CapSub(interval.end, interval.start));
223 }
224 // Because the intervals are closed on both side above, with miss 1 per
225 // interval.
226 size = operations_research::CapAdd(size, intervals_.size());
227 return size;
228}
229
230int64_t Domain::Min() const {
231 DCHECK(!IsEmpty());
232 return intervals_.front().start;
233}
234
235int64_t Domain::Max() const {
236 DCHECK(!IsEmpty());
237 return intervals_.back().end;
238}
239
240int64_t Domain::SmallestValue() const {
241 DCHECK(!IsEmpty());
242 int64_t result = Min();
243 for (const ClosedInterval interval : intervals_) {
244 if (interval.start <= 0 && interval.end >= 0) return 0;
245 for (const int64_t b : {interval.start, interval.end}) {
246 if (b > 0 && b <= std::abs(result)) result = b;
247 if (b < 0 && -b < std::abs(result)) result = b;
248 }
249 }
250 return result;
251}
252
254 for (const ClosedInterval interval : intervals_) {
255 if (interval.start <= 0 && interval.end >= 0) {
256 return Domain(interval.start, interval.end);
257 }
258 }
259 return Domain();
260}
261
262// TODO(user): Use std::upper_bound() like in ValueAtOrBefore() ?
263int64_t Domain::ClosestValue(int64_t wanted) const {
264 DCHECK(!IsEmpty());
265 if (wanted <= intervals_[0].start) {
266 return intervals_[0].start;
267 }
268 int64_t best_point;
269 int64_t best_distance;
270 for (const ClosedInterval interval : intervals_) {
271 if (interval.start <= wanted && wanted <= interval.end) {
272 return wanted;
273 } else if (interval.start >= wanted) {
274 return CapSub(interval.start, wanted) <= best_distance ? interval.start
275 : best_point;
276 } else {
277 best_point = interval.end;
278 best_distance = CapSub(wanted, interval.end);
279 }
280 }
281 return best_point;
282}
283
284int64_t Domain::ValueAtOrBefore(int64_t input) const {
285 // Because we only compare by start and there is no duplicate starts, this
286 // should be the next interval after the one that has a chance to contains
287 // value.
288 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
290 if (it == intervals_.begin()) return input;
291 --it;
292 return input <= it->end ? input : it->end;
293}
294
295int64_t Domain::ValueAtOrAfter(int64_t input) const {
296 // Because we only compare by start and there is no duplicate starts, this
297 // should be the next interval after the one that has a chance to contains
298 // value.
299 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
301 if (it == intervals_.end()) return input;
302 const int64_t candidate = it->start;
303 if (it == intervals_.begin()) return candidate;
304 --it;
305 return input <= it->end ? input : candidate;
306}
307
308int64_t Domain::FixedValue() const {
309 DCHECK(IsFixed());
310 return intervals_.front().start;
311}
312
313bool Domain::Contains(int64_t value) const {
314 // Because we only compare by start and there is no duplicate starts, this
315 // should be the next interval after the one that has a chance to contains
316 // value.
317 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
318 ClosedInterval(value, value));
319 if (it == intervals_.begin()) return false;
320 --it;
321 return value <= it->end;
322}
323
324// TODO(user): Deal with overflow.
325int64_t Domain::Distance(int64_t value) const {
326 int64_t min_distance = std::numeric_limits<int64_t>::max();
327 for (const ClosedInterval interval : intervals_) {
328 if (value >= interval.start && value <= interval.end) return 0;
329 if (interval.start > value) {
330 min_distance = std::min(min_distance, interval.start - value);
331 break;
332 } else {
333 min_distance = value - interval.end;
334 }
335 }
336 return min_distance;
337}
338
339bool Domain::IsIncludedIn(const Domain& domain) const {
340 int i = 0;
341 const auto& others = domain.intervals_;
342 for (const ClosedInterval interval : intervals_) {
343 // Find the unique interval in others that contains interval if any.
344 for (; i < others.size() && interval.end > others[i].end; ++i) {
345 }
346 if (i == others.size()) return false;
347 if (interval.start < others[i].start) return false;
348 }
349 return true;
350}
351
353 Domain result;
354 int64_t next_start = kint64min;
355 result.intervals_.reserve(intervals_.size() + 1);
356 for (const ClosedInterval& interval : intervals_) {
357 if (interval.start != kint64min) {
358 result.intervals_.push_back({next_start, interval.start - 1});
359 }
360 if (interval.end == kint64max) return result;
361 next_start = interval.end + 1;
362 }
363 result.intervals_.push_back({next_start, kint64max});
364 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
365 return result;
366}
367
369 Domain result = *this;
370 result.NegateInPlace();
371 return result;
372}
373
374void Domain::NegateInPlace() {
375 if (intervals_.empty()) return;
376 std::reverse(intervals_.begin(), intervals_.end());
377 if (intervals_.back().end == kint64min) {
378 // corner-case
379 intervals_.pop_back();
380 }
381 for (ClosedInterval& ref : intervals_) {
382 std::swap(ref.start, ref.end);
383 ref.start = ref.start == kint64min ? kint64max : -ref.start;
384 ref.end = ref.end == kint64min ? kint64max : -ref.end;
385 }
386 DCHECK(IntervalsAreSortedAndNonAdjacent(intervals_));
387}
388
390 Domain result;
391 const auto& a = intervals_;
392 const auto& b = domain.intervals_;
393 for (int i = 0, j = 0; i < a.size() && j < b.size();) {
394 if (a[i].start <= b[j].start) {
395 if (a[i].end < b[j].start) {
396 // Empty intersection. We advance past the first interval.
397 ++i;
398 } else { // a[i].end >= b[j].start
399 // Non-empty intersection: push back the intersection of these two, and
400 // advance past the first interval to finish.
401 if (a[i].end <= b[j].end) {
402 result.intervals_.push_back({b[j].start, a[i].end});
403 ++i;
404 } else { // a[i].end > b[j].end.
405 result.intervals_.push_back({b[j].start, b[j].end});
406 ++j;
407 }
408 }
409 } else { // a[i].start > b[i].start.
410 // We do the exact same thing as above, but swapping a and b.
411 if (b[j].end < a[i].start) {
412 ++j;
413 } else { // b[j].end >= a[i].start
414 if (b[j].end <= a[i].end) {
415 result.intervals_.push_back({a[i].start, b[j].end});
416 ++j;
417 } else { // a[i].end > b[j].end.
418 result.intervals_.push_back({a[i].start, a[i].end});
419 ++i;
420 }
421 }
422 }
423 }
424 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
425 return result;
426}
427
428Domain Domain::UnionWith(const Domain& domain) const {
429 Domain result;
430 const auto& a = intervals_;
431 const auto& b = domain.intervals_;
432 result.intervals_.resize(a.size() + b.size());
433 std::merge(a.begin(), a.end(), b.begin(), b.end(), result.intervals_.begin());
434 UnionOfSortedIntervals(&result.intervals_);
435 return result;
436}
437
438// TODO(user): Use a better algorithm.
439Domain Domain::AdditionWith(const Domain& domain) const {
440 Domain result;
441
442 const auto& a = intervals_;
443 const auto& b = domain.intervals_;
444 result.intervals_.reserve(a.size() * b.size());
445 for (const ClosedInterval& i : a) {
446 for (const ClosedInterval& j : b) {
447 if (i.start > 0 && j.start > 0) {
448 if (AddOverflows(i.start, j.start)) continue; // empty.
449 result.intervals_.push_back({i.start + j.start, CapAdd(i.end, j.end)});
450 } else if (i.end < 0 && j.end < 0) {
451 if (AddOverflows(i.end, j.end)) continue; // empty.
452 result.intervals_.push_back({CapAdd(i.start, j.start), i.end + j.end});
453 } else {
454 result.intervals_.push_back(
455 {CapAdd(i.start, j.start), CapAdd(i.end, j.end)});
456 }
457 }
458 }
459
460 // The sort is not needed if one of the list is of size 1.
461 if (a.size() > 1 && b.size() > 1) {
462 std::sort(result.intervals_.begin(), result.intervals_.end());
463 }
464 UnionOfSortedIntervals(&result.intervals_);
465 return result;
466}
467
469 if (NumIntervals() > kDomainComplexityLimit) {
470 return Domain(Min(), Max());
471 } else {
472 return *this;
473 }
474}
475
476Domain Domain::MultiplicationBy(int64_t coeff, bool* exact) const {
477 if (exact != nullptr) *exact = true;
478 if (intervals_.empty()) return {};
479 if (coeff == 0) return Domain(0);
480
481 const int64_t abs_coeff = std::abs(coeff);
482 const int64_t size_if_non_trivial = abs_coeff > 1 ? Size() : 0;
483 if (size_if_non_trivial > kDomainComplexityLimit) {
484 if (exact != nullptr) *exact = false;
485 return ContinuousMultiplicationBy(coeff);
486 }
487
488 Domain result;
489 if (abs_coeff > 1) {
490 const int64_t max_value = kint64max / abs_coeff;
491 const int64_t min_value = kint64min / abs_coeff;
492 result.intervals_.reserve(size_if_non_trivial);
493 for (const ClosedInterval& i : intervals_) {
494 for (int64_t v = i.start;; ++v) {
495 // We ignore anything that overflow.
496 if (v >= min_value && v <= max_value) {
497 // Because abs_coeff > 1, all new values are disjoint.
498 const int64_t new_value = v * abs_coeff;
499 result.intervals_.push_back({new_value, new_value});
500 }
501
502 // This is to avoid doing ++v when v is kint64max!
503 if (v == i.end) break;
504 }
505 }
506 } else {
507 result = *this;
508 }
509 if (coeff < 0) result.NegateInPlace();
510 return result;
511}
512
514 Domain result = *this;
515 const int64_t abs_coeff = std::abs(coeff);
516 for (ClosedInterval& i : result.intervals_) {
517 i.start = CapProd(i.start, abs_coeff);
518 i.end = CapProd(i.end, abs_coeff);
519 }
520 UnionOfSortedIntervals(&result.intervals_);
521 if (coeff < 0) result.NegateInPlace();
522 return result;
523}
524
526 Domain result;
527 for (const ClosedInterval& i : this->intervals_) {
528 for (const ClosedInterval& j : domain.intervals_) {
529 ClosedInterval new_interval;
530 const int64_t a = CapProd(i.start, j.start);
531 const int64_t b = CapProd(i.end, j.end);
532 const int64_t c = CapProd(i.start, j.end);
533 const int64_t d = CapProd(i.end, j.start);
534 new_interval.start = std::min({a, b, c, d});
535 new_interval.end = std::max({a, b, c, d});
536 result.intervals_.push_back(new_interval);
537 }
538 }
539 std::sort(result.intervals_.begin(), result.intervals_.end());
540 UnionOfSortedIntervals(&result.intervals_);
541 return result;
542}
543
544Domain Domain::DivisionBy(int64_t coeff) const {
545 CHECK_NE(coeff, 0);
546 Domain result = *this;
547 const int64_t abs_coeff = std::abs(coeff);
548 for (ClosedInterval& i : result.intervals_) {
549 i.start = i.start / abs_coeff;
550 i.end = i.end / abs_coeff;
551 }
552 UnionOfSortedIntervals(&result.intervals_);
553 if (coeff < 0) result.NegateInPlace();
554 return result;
555}
556
557Domain Domain::InverseMultiplicationBy(const int64_t coeff) const {
558 if (coeff == 0) {
559 return Contains(0) ? Domain::AllValues() : Domain();
560 }
561 Domain result = *this;
562 int new_size = 0;
563 const int64_t abs_coeff = std::abs(coeff);
564 for (const ClosedInterval& i : result.intervals_) {
565 const int64_t start = CeilRatio(i.start, abs_coeff);
566 const int64_t end = FloorRatio(i.end, abs_coeff);
567 if (start > end) continue;
568 if (new_size > 0 && start == result.intervals_[new_size - 1].end + 1) {
569 result.intervals_[new_size - 1].end = end;
570 } else {
571 result.intervals_[new_size++] = {start, end};
572 }
573 }
574 result.intervals_.resize(new_size);
575 result.intervals_.shrink_to_fit();
576 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
577 if (coeff < 0) result.NegateInPlace();
578 return result;
579}
580
581namespace {
582Domain ModuloHelper(int64_t min, int64_t max, const Domain& modulo) {
583 DCHECK_GT(min, 0);
584 DCHECK_GT(modulo.Min(), 0);
585 const int64_t max_mod = modulo.Max() - 1;
586
587 // The min/max are exact if the modulo is fixed. Note that we could return the
588 // exact domain with a potential hole but we currently don't.
589 if (modulo.Min() == modulo.Max()) {
590 const int64_t size = max - min;
591 const int64_t v1 = min % modulo.Max();
592 if (v1 + size > max_mod) return Domain(0, max_mod);
593 return Domain(v1, v1 + size);
594 }
595
596 // TODO(user): This is a superset.
597 return Domain(0, std::min(max, max_mod));
598}
599} // namespace
600
602 if (IsEmpty()) return Domain();
603 CHECK_GT(modulo.Min(), 0);
604 const int64_t max_mod = modulo.Max() - 1;
605 if (Max() >= 0 && Min() <= 0) {
606 return Domain(std::max(Min(), -max_mod), std::min(Max(), max_mod));
607 }
608 if (Min() > 0) {
609 return ModuloHelper(Min(), Max(), modulo);
610 }
611 DCHECK_LT(Max(), 0);
612 return ModuloHelper(-Max(), -Min(), modulo).Negation();
613}
614
616 if (IsEmpty()) return Domain();
617 CHECK_GT(divisor.Min(), 0);
618 return Domain(std::min(Min() / divisor.Max(), Min() / divisor.Min()),
619 std::max(Max() / divisor.Min(), Max() / divisor.Max()));
620}
621
623 if (IsEmpty()) return Domain();
624 const Domain abs_domain =
625 IntersectionWith({0, std::numeric_limits<int64_t>::max()})
627 {0, std::numeric_limits<int64_t>::max()}));
628 if (abs_domain.Size() >= kDomainComplexityLimit) {
629 Domain result;
630 result.intervals_.reserve(abs_domain.NumIntervals());
631 for (const auto& interval : abs_domain) {
632 result.intervals_.push_back(
633 ClosedInterval(CapProd(interval.start, interval.start),
634 CapProd(interval.end, interval.end)));
635 }
636 UnionOfSortedIntervals(&result.intervals_);
637 return result;
638 } else {
639 std::vector<int64_t> values;
640 values.reserve(abs_domain.Size());
641 for (const int64_t value : abs_domain.Values()) {
642 values.push_back(CapProd(value, value));
643 }
644 return Domain::FromValues(std::move(values));
645 }
646}
647
648namespace {
649ClosedInterval EvaluateQuadraticProdInterval(int64_t a, int64_t b, int64_t c,
650 int64_t d, int64_t variable_min,
651 int64_t variable_max) {
652 // We have (a*x + b)(c*x + d) = a*c*x*x + (a*d + b*c)*x + b*d
653 // The minimum or maximum is at x = -(a*d + b*c)/(2*a*c)
654 //
655 // The minimum and maximum of the expression happens when x is one of the
656 // following:
657 // - variable_min;
658 // - variable_max;
659 // - the closest point to the parabola extreme, rounded down;
660 // - the closest point to the parabola extreme, rounded up.
661
662 const absl::int128 nominator =
663 -absl::int128{a} * absl::int128{d} - absl::int128{b} * absl::int128{c};
664 const absl::int128 denominator = absl::int128{a} * absl::int128{c};
665 const absl::int128 evaluated_minimum_point = (nominator / denominator) / 2;
666
667 const auto& evaluate = [&a, &b, &c, &d](const int64_t x) {
668 return CapProd(CapAdd(CapProd(a, x), b), CapAdd(CapProd(c, x), d));
669 };
670
671 const int64_t at_min_x = evaluate(variable_min);
672 const int64_t at_max_x = evaluate(variable_max);
673 int64_t min_var = std::min(at_min_x, at_max_x);
674 int64_t max_var = std::max(at_min_x, at_max_x);
675
676 if (evaluated_minimum_point > variable_min &&
677 evaluated_minimum_point < variable_max) {
678 const int64_t point_at_minimum_64 =
679 static_cast<int64_t>(evaluated_minimum_point);
680 const int rounder = ((nominator > 0) == (denominator > 0) ? 1 : -1);
681 const int64_t point1 = evaluate(point_at_minimum_64);
682 const int64_t point2 = evaluate(point_at_minimum_64 + rounder);
683 min_var = std::min(min_var, std::min(point1, point2));
684 max_var = std::max(max_var, std::max(point1, point2));
685 }
686
687 return ClosedInterval(min_var, max_var);
688}
689} // namespace
690
691Domain Domain::QuadraticSuperset(int64_t a, int64_t b, int64_t c,
692 int64_t d) const {
693 if (IsEmpty()) return Domain();
694
695 if (Size() < kDomainComplexityLimit) {
696 std::vector<int64_t> values;
697 values.reserve(Size());
698 for (const int64_t value : Values()) {
699 values.push_back( //
700 CapProd( //
701 CapAdd(CapProd(a, value), b), CapAdd(CapProd(c, value), d)));
702 }
703 return Domain::FromValues(std::move(values));
704 }
705
706 if (a == 0) {
707 return MultiplicationBy(CapProd(c, b)).AdditionWith(Domain(CapProd(d, b)));
708 }
709 if (c == 0) {
710 return MultiplicationBy(CapProd(a, d)).AdditionWith(Domain(CapProd(d, b)));
711 }
712
713 Domain result;
714 result.intervals_.reserve(NumIntervals());
715 for (const auto& interval : intervals_) {
716 result.intervals_.push_back(EvaluateQuadraticProdInterval(
717 a, b, c, d, interval.start, interval.end));
718 }
719 std::sort(result.intervals_.begin(), result.intervals_.end());
720 UnionOfSortedIntervals(&result.intervals_);
721 return result;
722}
723
724// It is a bit difficult to see, but this code is doing the same thing as
725// for all interval in this.UnionWith(implied_domain.Complement())):
726// - Take the two extreme points (min and max) in interval \inter implied.
727// - Append to result [min, max] if these points exists.
729 Domain result;
730 if (implied_domain.IsEmpty()) return result;
731
732 int i = 0;
733 int64_t min_point;
734 int64_t max_point;
735 bool started = false;
736 for (const ClosedInterval interval : intervals_) {
737 // We only "close" the new result interval if it cannot be extended by
738 // implied_domain.Complement(). The only extension possible look like:
739 // interval_: ...] [....
740 // implied : ...] [... i ...]
741 if (started && implied_domain.intervals_[i].start < interval.start) {
742 result.intervals_.push_back({min_point, max_point});
743 started = false;
744 }
745
746 // Find the two extreme points in interval \inter implied_domain.
747 // Always stop the loop at the first interval with and end strictly greater
748 // that interval.end.
749 for (; i < implied_domain.intervals_.size(); ++i) {
750 const ClosedInterval current = implied_domain.intervals_[i];
751 if (current.end >= interval.start && current.start <= interval.end) {
752 // Current and interval have a non-empty intersection.
753 const int64_t inter_max = std::min(interval.end, current.end);
754 if (!started) {
755 started = true;
756 min_point = std::max(interval.start, current.start);
757 max_point = inter_max;
758 } else {
759 // No need to update the min_point here, and the new inter_max must
760 // necessarily be > old one.
761 DCHECK_GE(inter_max, max_point);
762 max_point = inter_max;
763 }
764 }
765 if (current.end > interval.end) break;
766 }
767 if (i == implied_domain.intervals_.size()) break;
768 }
769 if (started) {
770 result.intervals_.push_back({min_point, max_point});
771 }
772 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
773 return result;
774}
775
776std::vector<int64_t> Domain::FlattenedIntervals() const {
777 std::vector<int64_t> result;
778 for (const ClosedInterval& interval : intervals_) {
779 result.push_back(interval.start);
780 result.push_back(interval.end);
781 }
782 return result;
783}
784
785bool Domain::operator<(const Domain& other) const {
786 const auto& d1 = intervals_;
787 const auto& d2 = other.intervals_;
788 const int common_size = std::min(d1.size(), d2.size());
789 for (int i = 0; i < common_size; ++i) {
790 const ClosedInterval& i1 = d1[i];
791 const ClosedInterval& i2 = d2[i];
792 if (i1.start < i2.start) return true;
793 if (i1.start > i2.start) return false;
794 if (i1.end < i2.end) return true;
795 if (i1.end > i2.end) return false;
796 }
797 return d1.size() < d2.size();
798}
799
800std::string Domain::ToString() const { return IntervalsAsString(intervals_); }
801
802int64_t SumOfKMinValueInDomain(const Domain& domain, int k) {
803 int64_t current_sum = 0.0;
804 int current_index = 0;
805 for (const ClosedInterval interval : domain) {
806 if (current_index >= k) break;
807 for (int v(interval.start); v <= interval.end; ++v) {
808 if (current_index >= k) break;
809 current_index++;
810 current_sum += v;
811 }
812 }
813 return current_sum;
814}
815
816int64_t SumOfKMaxValueInDomain(const Domain& domain, int k) {
817 return -SumOfKMinValueInDomain(domain.Negation(), k);
818}
819
821
823 const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
824 InsertIntervals(starts, ends);
825}
826
828 const std::vector<int>& starts, const std::vector<int>& ends) {
829 InsertIntervals(starts, ends);
830}
831
833 const std::vector<ClosedInterval>& intervals) {
834 for (ClosedInterval interval : intervals) {
835 InsertInterval(interval.start, interval.end);
836 }
837}
838
841 int64_t end) {
842 SortedDisjointIntervalList interval_list;
843 int64_t next_start = start;
844 for (auto it = FirstIntervalGreaterOrEqual(start); it != this->end(); ++it) {
845 const ClosedInterval& interval = *it;
846 const int64_t next_end = CapSub(interval.start, 1);
847 if (next_end > end) break;
848 if (next_start <= next_end) {
849 interval_list.InsertInterval(next_start, next_end);
850 }
851 next_start = CapAdd(interval.end, 1);
852 }
853 if (next_start <= end) {
854 interval_list.InsertInterval(next_start, end);
855 }
856 return interval_list;
857}
858
860 int64_t start, int64_t end) {
861 // start > end could mean an empty interval, but we prefer to LOG(DFATAL)
862 // anyway. Really, the user should never give us that.
863 if (start > end) {
864 LOG(DFATAL) << "Invalid interval: " << ClosedInterval({start, end});
865 return intervals_.end();
866 }
867
868 auto result = intervals_.insert({start, end});
869 if (!result.second) return result.first; // Duplicate: exit immediately.
870
871 // TODO(user): tune the algorithm below if it proves to be a bottleneck.
872 // For example, one could try to avoid an insertion if it's not needed
873 // (when the interval merges with a single existing interval or is fully
874 // contained by one).
875
876 // Iterate over the previous iterators whose end is after (or almost at) our
877 // start. After this, "it1" will point to the first interval that needs to be
878 // merged with the current interval (possibly pointing to the current interval
879 // itself, if no "earlier" interval should be merged).
880 auto it1 = result.first;
881 if (start == kint64min) { // Catch underflows
882 it1 = intervals_.begin();
883 } else {
884 const int64_t before_start = start - 1;
885 while (it1 != intervals_.begin()) {
886 auto prev_it = it1;
887 --prev_it;
888 if (prev_it->end < before_start) break;
889 it1 = prev_it;
890 }
891 }
892
893 // Ditto, on the other side: "it2" will point to the interval *after* the last
894 // one that should be merged with the current interval.
895 auto it2 = result.first;
896 if (end == kint64max) {
897 it2 = intervals_.end();
898 } else {
899 const int64_t after_end = end + 1;
900 do {
901 ++it2;
902 } while (it2 != intervals_.end() && it2->start <= after_end);
903 }
904
905 // [it1..it2) is the range (inclusive on it1, exclusive on it2) of intervals
906 // that should be merged together. We'll set it3 = it2-1 and erase [it1..it3)
907 // and set *it3 to the merged interval.
908 auto it3 = it2;
909 it3--;
910 if (it1 == it3) return it3; // Nothing was merged.
911 const int64_t new_start = std::min(it1->start, start);
912 const int64_t new_end = std::max(it3->end, end);
913 auto it = intervals_.erase(it1, it3);
914 // HACK(user): set iterators point to *const* values. Which is expected,
915 // because if one alters a set element's value, then it collapses the set
916 // property! But in this very special case, we know that we can just overwrite
917 // it->start, so we do it.
918 const_cast<ClosedInterval*>(&(*it))->start = new_start;
919 const_cast<ClosedInterval*>(&(*it))->end = new_end;
920 return it;
921}
922
924 int64_t value, int64_t* newly_covered) {
925 auto it = intervals_.upper_bound({value, kint64max});
926 auto it_prev = it;
927
928 // No interval containing or adjacent to "value" on the left (i.e. below).
929 if (it != begin()) {
930 --it_prev;
931 }
932 if (it == begin() || ((value != kint64min) && it_prev->end < value - 1)) {
933 *newly_covered = value;
934 if (it == end() || it->start != value + 1) {
935 // No interval adjacent to "value" on the right: insert a singleton.
936 return intervals_.insert(it, {value, value});
937 } else {
938 // There is an interval adjacent to "value" on the right. Extend it by
939 // one. Note that we already know that there won't be a merge with another
940 // interval on the left, since there were no interval adjacent to "value"
941 // on the left.
942 DCHECK_EQ(it->start, value + 1);
943 const_cast<ClosedInterval*>(&(*it))->start = value;
944 return it;
945 }
946 }
947
948 // At this point, "it_prev" points to an interval containing or adjacent to
949 // "value" on the left: grow it by one, and if it now touches the next
950 // interval, merge with it.
951 CHECK_NE(kint64max, it_prev->end) << "Cannot grow right by one: the interval "
952 "that would grow already ends at "
953 "kint64max";
954 *newly_covered = it_prev->end + 1;
955 if (it != end() && it_prev->end + 2 == it->start) {
956 // We need to merge it_prev with 'it'.
957 const_cast<ClosedInterval*>(&(*it_prev))->end = it->end;
958 intervals_.erase(it);
959 } else {
960 const_cast<ClosedInterval*>(&(*it_prev))->end = it_prev->end + 1;
961 }
962 return it_prev;
963}
964
965template <class T>
966void SortedDisjointIntervalList::InsertAll(const std::vector<T>& starts,
967 const std::vector<T>& ends) {
968 CHECK_EQ(starts.size(), ends.size());
969 for (int i = 0; i < starts.size(); ++i) InsertInterval(starts[i], ends[i]);
970}
971
973 const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
974 InsertAll(starts, ends);
975}
976
977void SortedDisjointIntervalList::InsertIntervals(const std::vector<int>& starts,
978 const std::vector<int>& ends) {
979 // TODO(user): treat kint32min and kint32max as their kint64 variants.
980 InsertAll(starts, ends);
981}
982
985 const auto it = intervals_.upper_bound({value, kint64max});
986 if (it == begin()) return it;
987 auto it_prev = it;
988 it_prev--;
989 DCHECK_LE(it_prev->start, value);
990 return it_prev->end >= value ? it_prev : it;
991}
992
995 const auto it = intervals_.upper_bound({value, kint64max});
996 if (it == begin()) return end();
997 auto it_prev = it;
998 it_prev--;
999 return it_prev;
1000}
1001
1003 std::string str;
1004 for (const ClosedInterval& interval : intervals_) {
1005 str += interval.DebugString();
1006 }
1007 return str;
1008}
1009
1010} // namespace operations_research
Domain SimplifyUsingImpliedDomain(const Domain &implied_domain) const
static Domain FromVectorIntervals(const std::vector< std::vector< int64_t > > &intervals)
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
static Domain FromValues(std::vector< int64_t > values)
bool operator<(const Domain &other) const
int64_t ValueAtOrAfter(int64_t input) const
std::vector< int64_t > FlattenedIntervals() const
Domain IntersectionWith(const Domain &domain) const
Domain QuadraticSuperset(int64_t a, int64_t b, int64_t c, int64_t d) const
static Domain FromIntervals(absl::Span< const ClosedInterval > intervals)
Domain ContinuousMultiplicationBy(int64_t coeff) const
bool Contains(int64_t value) const
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
static Domain FromFlatIntervals(const std::vector< int64_t > &flat_intervals)
Domain()
By default, Domain will be empty.
Domain DivisionBy(int64_t coeff) const
int64_t Distance(int64_t value) const
static Domain FromFlatSpanOfIntervals(absl::Span< const int64_t > flat_intervals)
absl::InlinedVector< ClosedInterval, 1 >::const_iterator end() const
Domain PositiveDivisionBySuperset(const Domain &divisor) const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
int64_t ValueAtOrBefore(int64_t input) const
int64_t ClosestValue(int64_t wanted) const
void InsertIntervals(const std::vector< int64_t > &starts, const std::vector< int64_t > &ends)
Iterator InsertInterval(int64_t start, int64_t end)
SortedDisjointIntervalList BuildComplementOnInterval(int64_t start, int64_t end)
Iterator GrowRightByOne(int64_t value, int64_t *newly_covered)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
int64_t FloorRatio(int64_t value, int64_t positive_coeff)
int64_t CapSub(int64_t x, int64_t y)
int64_t CeilRatio(int64_t value, int64_t positive_coeff)
int64_t SumOfKMinValueInDomain(const Domain &domain, int k)
Returns the sum of smallest k values in the domain.
std::ostream & operator<<(std::ostream &out, const Assignment &assignment)
bool AddOverflows(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
bool IntervalsAreSortedAndNonAdjacent(absl::Span< const ClosedInterval > intervals)
int64_t SumOfKMaxValueInDomain(const Domain &domain, int k)
Returns the sum of largest k values in the domain.
static int input(yyscan_t yyscanner)
Definition model.h:51
static const int64_t kint64max
Definition types.h:32
static const int64_t kint64min
Definition types.h:31