Google OR-Tools v9.15
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::LowerOrEqual(int64_t value) { return Domain(kint64min, value); }
158
160 return Domain(value, kint64max);
161}
162
163Domain Domain::FromValues(std::vector<int64_t> values) {
164 std::sort(values.begin(), values.end());
165 Domain result;
166 for (const int64_t v : values) {
167 if (result.intervals_.empty() || v > result.intervals_.back().end + 1) {
168 result.intervals_.push_back({v, v});
169 } else {
170 result.intervals_.back().end = v;
171 }
172 if (result.intervals_.back().end == std::numeric_limits<int64_t>::max()) {
173 break;
174 }
175 }
176 return result;
177}
178
179Domain Domain::FromIntervals(absl::Span<const ClosedInterval> intervals) {
180 Domain result;
181 result.intervals_.assign(intervals.begin(), intervals.end());
182 SortAndRemoveInvalidIntervals(&result.intervals_);
183 UnionOfSortedIntervals(&result.intervals_);
184 return result;
185}
186
188 absl::Span<const int64_t> flat_intervals) {
189 DCHECK(flat_intervals.size() % 2 == 0) << flat_intervals.size();
190 Domain result;
191 result.intervals_.reserve(flat_intervals.size() / 2);
192 for (int i = 0; i < flat_intervals.size(); i += 2) {
193 result.intervals_.push_back({flat_intervals[i], flat_intervals[i + 1]});
194 }
195 SortAndRemoveInvalidIntervals(&result.intervals_);
196 UnionOfSortedIntervals(&result.intervals_);
197 return result;
198}
199
200Domain Domain::FromFlatIntervals(const std::vector<int64_t>& flat_intervals) {
201 return FromFlatSpanOfIntervals(absl::MakeSpan(flat_intervals));
202}
203
205 const std::vector<std::vector<int64_t>>& intervals) {
206 Domain result;
207 for (const std::vector<int64_t>& interval : intervals) {
208 if (interval.size() == 1) {
209 result.intervals_.push_back({interval[0], interval[0]});
210 } else {
211 DCHECK_EQ(interval.size(), 2);
212 result.intervals_.push_back({interval[0], interval[1]});
213 }
214 }
215 SortAndRemoveInvalidIntervals(&result.intervals_);
216 UnionOfSortedIntervals(&result.intervals_);
217 return result;
218}
219
220bool Domain::IsEmpty() const { return intervals_.empty(); }
221
222bool Domain::IsFixed() const { return Min() == Max(); }
223
224int64_t Domain::Size() const {
225 int64_t size = 0;
226 for (const ClosedInterval interval : intervals_) {
228 size, operations_research::CapSub(interval.end, interval.start));
229 }
230 // Because the intervals are closed on both side above, with miss 1 per
231 // interval.
232 size = operations_research::CapAdd(size, intervals_.size());
233 return size;
234}
235
236int64_t Domain::Min() const {
237 DCHECK(!IsEmpty());
238 return intervals_.front().start;
239}
240
241int64_t Domain::Max() const {
242 DCHECK(!IsEmpty());
243 return intervals_.back().end;
244}
245
246int64_t Domain::SmallestValue() const {
247 DCHECK(!IsEmpty());
248 int64_t result = Min();
249 for (const ClosedInterval interval : intervals_) {
250 if (interval.start <= 0 && interval.end >= 0) return 0;
251 for (const int64_t b : {interval.start, interval.end}) {
252 if (b > 0 && b <= std::abs(result)) result = b;
253 if (b < 0 && -b < std::abs(result)) result = b;
254 }
255 }
256 return result;
257}
258
260 for (const ClosedInterval interval : intervals_) {
261 if (interval.start <= 0 && interval.end >= 0) {
262 return Domain(interval.start, interval.end);
263 }
264 }
265 return Domain();
266}
267
268// TODO(user): Use std::upper_bound() like in ValueAtOrBefore() ?
269int64_t Domain::ClosestValue(int64_t wanted) const {
270 DCHECK(!IsEmpty());
271 if (wanted <= intervals_[0].start) {
272 return intervals_[0].start;
273 }
274 int64_t best_point;
275 int64_t best_distance;
276 for (const ClosedInterval interval : intervals_) {
277 if (interval.start <= wanted && wanted <= interval.end) {
278 return wanted;
279 } else if (interval.start >= wanted) {
280 return CapSub(interval.start, wanted) <= best_distance ? interval.start
281 : best_point;
282 } else {
283 best_point = interval.end;
284 best_distance = CapSub(wanted, interval.end);
285 }
286 }
287 return best_point;
288}
289
290int64_t Domain::ValueAtOrBefore(int64_t input) const {
291 // Because we only compare by start and there is no duplicate starts, this
292 // should be the next interval after the one that has a chance to contains
293 // value.
294 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
296 if (it == intervals_.begin()) return input;
297 --it;
298 return input <= it->end ? input : it->end;
299}
300
301int64_t Domain::ValueAtOrAfter(int64_t input) const {
302 // Because we only compare by start and there is no duplicate starts, this
303 // should be the next interval after the one that has a chance to contains
304 // value.
305 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
307 if (it == intervals_.end()) return input;
308 const int64_t candidate = it->start;
309 if (it == intervals_.begin()) return candidate;
310 --it;
311 return input <= it->end ? input : candidate;
312}
313
314int64_t Domain::FixedValue() const {
315 DCHECK(IsFixed());
316 return intervals_.front().start;
317}
318
319bool Domain::Contains(int64_t value) const {
320 // Because we only compare by start and there is no duplicate starts, this
321 // should be the next interval after the one that has a chance to contains
322 // value.
323 auto it = std::upper_bound(intervals_.begin(), intervals_.end(),
324 ClosedInterval(value, value));
325 if (it == intervals_.begin()) return false;
326 --it;
327 return value <= it->end;
328}
329
330// TODO(user): Deal with overflow.
331int64_t Domain::Distance(int64_t value) const {
332 int64_t min_distance = std::numeric_limits<int64_t>::max();
333 for (const ClosedInterval interval : intervals_) {
334 if (value >= interval.start && value <= interval.end) return 0;
335 if (interval.start > value) {
336 min_distance = std::min(min_distance, interval.start - value);
337 break;
338 } else {
339 min_distance = value - interval.end;
340 }
341 }
342 return min_distance;
343}
344
345bool Domain::IsIncludedIn(const Domain& domain) const {
346 int i = 0;
347 const auto& others = domain.intervals_;
348 for (const ClosedInterval interval : intervals_) {
349 // Find the unique interval in others that contains interval if any.
350 for (; i < others.size() && interval.end > others[i].end; ++i) {
351 }
352 if (i == others.size()) return false;
353 if (interval.start < others[i].start) return false;
354 }
355 return true;
356}
357
358bool Domain::OverlapsWith(const Domain& domain) const {
359 const auto& a = intervals_;
360 const auto& b = domain.intervals_;
361 for (int i = 0, j = 0; i < a.size() && j < b.size();) {
362 if (a[i].start <= b[j].start) {
363 if (a[i].end < b[j].start) {
364 // Empty intersection. We advance past the first interval.
365 ++i;
366 } else { // a[i].end >= b[j].start
367 return true;
368 }
369 } else { // a[i].start > b[i].start.
370 // We do the exact same thing as above, but swapping a and b.
371 if (b[j].end < a[i].start) {
372 ++j;
373 } else { // b[j].end >= a[i].start
374 return true;
375 }
376 }
377 }
378 return false;
379}
380
382 Domain result;
383 int64_t next_start = kint64min;
384 result.intervals_.reserve(intervals_.size() + 1);
385 for (const ClosedInterval& interval : intervals_) {
386 if (interval.start != kint64min) {
387 result.intervals_.push_back({next_start, interval.start - 1});
388 }
389 if (interval.end == kint64max) return result;
390 next_start = interval.end + 1;
391 }
392 result.intervals_.push_back({next_start, kint64max});
393 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
394 return result;
395}
396
398 Domain result = *this;
399 result.NegateInPlace();
400 return result;
401}
402
403void Domain::NegateInPlace() {
404 if (intervals_.empty()) return;
405 std::reverse(intervals_.begin(), intervals_.end());
406 if (intervals_.back().end == kint64min) {
407 // corner-case
408 intervals_.pop_back();
409 }
410 for (ClosedInterval& ref : intervals_) {
411 std::swap(ref.start, ref.end);
412 ref.start = ref.start == kint64min ? kint64max : -ref.start;
413 ref.end = ref.end == kint64min ? kint64max : -ref.end;
414 }
415 DCHECK(IntervalsAreSortedAndNonAdjacent(intervals_));
416}
417
419 Domain result;
420 const auto& a = intervals_;
421 const auto& b = domain.intervals_;
422 for (int i = 0, j = 0; i < a.size() && j < b.size();) {
423 if (a[i].start <= b[j].start) {
424 if (a[i].end < b[j].start) {
425 // Empty intersection. We advance past the first interval.
426 ++i;
427 } else { // a[i].end >= b[j].start
428 // Non-empty intersection: push back the intersection of these two, and
429 // advance past the first interval to finish.
430 if (a[i].end <= b[j].end) {
431 result.intervals_.push_back({b[j].start, a[i].end});
432 ++i;
433 } else { // a[i].end > b[j].end.
434 result.intervals_.push_back({b[j].start, b[j].end});
435 ++j;
436 }
437 }
438 } else { // a[i].start > b[i].start.
439 // We do the exact same thing as above, but swapping a and b.
440 if (b[j].end < a[i].start) {
441 ++j;
442 } else { // b[j].end >= a[i].start
443 if (b[j].end <= a[i].end) {
444 result.intervals_.push_back({a[i].start, b[j].end});
445 ++j;
446 } else { // a[i].end > b[j].end.
447 result.intervals_.push_back({a[i].start, a[i].end});
448 ++i;
449 }
450 }
451 }
452 }
453 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
454 return result;
455}
456
457Domain Domain::UnionWith(const Domain& domain) const {
458 Domain result;
459 const auto& a = intervals_;
460 const auto& b = domain.intervals_;
461 result.intervals_.resize(a.size() + b.size());
462 std::merge(a.begin(), a.end(), b.begin(), b.end(), result.intervals_.begin());
463 UnionOfSortedIntervals(&result.intervals_);
464 return result;
465}
466
467// TODO(user): Use a better algorithm.
468Domain Domain::AdditionWith(const Domain& domain) const {
469 Domain result;
470
471 const auto& a = intervals_;
472 const auto& b = domain.intervals_;
473 result.intervals_.reserve(a.size() * b.size());
474 for (const ClosedInterval& i : a) {
475 for (const ClosedInterval& j : b) {
476 if (i.start > 0 && j.start > 0) {
477 if (AddOverflows(i.start, j.start)) continue; // empty.
478 result.intervals_.push_back({i.start + j.start, CapAdd(i.end, j.end)});
479 } else if (i.end < 0 && j.end < 0) {
480 if (AddOverflows(i.end, j.end)) continue; // empty.
481 result.intervals_.push_back({CapAdd(i.start, j.start), i.end + j.end});
482 } else {
483 result.intervals_.push_back(
484 {CapAdd(i.start, j.start), CapAdd(i.end, j.end)});
485 }
486 }
487 }
488
489 // The sort is not needed if one of the list is of size 1.
490 if (a.size() > 1 && b.size() > 1) {
491 std::sort(result.intervals_.begin(), result.intervals_.end());
492 }
493 UnionOfSortedIntervals(&result.intervals_);
494 return result;
495}
496
498 if (NumIntervals() > kDomainComplexityLimit) {
499 return Domain(Min(), Max());
500 } else {
501 return *this;
502 }
503}
504
505Domain Domain::MultiplicationBy(int64_t coeff, bool* exact) const {
506 if (exact != nullptr) *exact = true;
507 if (intervals_.empty()) return {};
508 if (coeff == 0) return Domain(0);
509
510 const int64_t abs_coeff = std::abs(coeff);
511 const int64_t size_if_non_trivial = abs_coeff > 1 ? Size() : 0;
512 if (size_if_non_trivial > kDomainComplexityLimit) {
513 if (exact != nullptr) *exact = false;
514 return ContinuousMultiplicationBy(coeff);
515 }
516
517 Domain result;
518 if (abs_coeff > 1) {
519 const int64_t max_value = kint64max / abs_coeff;
520 const int64_t min_value = kint64min / abs_coeff;
521 result.intervals_.reserve(size_if_non_trivial);
522 for (const ClosedInterval& i : intervals_) {
523 for (int64_t v = i.start;; ++v) {
524 // We ignore anything that overflow.
525 if (v >= min_value && v <= max_value) {
526 // Because abs_coeff > 1, all new values are disjoint.
527 const int64_t new_value = v * abs_coeff;
528 result.intervals_.push_back({new_value, new_value});
529 }
530
531 // This is to avoid doing ++v when v is kint64max!
532 if (v == i.end) break;
533 }
534 }
535 } else {
536 result = *this;
537 }
538 if (coeff < 0) result.NegateInPlace();
539 return result;
540}
541
543 Domain result = *this;
544 const int64_t abs_coeff = std::abs(coeff);
545 for (ClosedInterval& i : result.intervals_) {
546 i.start = CapProd(i.start, abs_coeff);
547 i.end = CapProd(i.end, abs_coeff);
548 }
549 UnionOfSortedIntervals(&result.intervals_);
550 if (coeff < 0) result.NegateInPlace();
551 return result;
552}
553
555 Domain result;
556 for (const ClosedInterval& i : this->intervals_) {
557 for (const ClosedInterval& j : domain.intervals_) {
558 ClosedInterval new_interval;
559 const int64_t a = CapProd(i.start, j.start);
560 const int64_t b = CapProd(i.end, j.end);
561 const int64_t c = CapProd(i.start, j.end);
562 const int64_t d = CapProd(i.end, j.start);
563 new_interval.start = std::min({a, b, c, d});
564 new_interval.end = std::max({a, b, c, d});
565 result.intervals_.push_back(new_interval);
566 }
567 }
568 std::sort(result.intervals_.begin(), result.intervals_.end());
569 UnionOfSortedIntervals(&result.intervals_);
570 return result;
571}
572
573Domain Domain::DivisionBy(int64_t coeff) const {
574 CHECK_NE(coeff, 0);
575 Domain result = *this;
576 const int64_t abs_coeff = std::abs(coeff);
577 for (ClosedInterval& i : result.intervals_) {
578 i.start = i.start / abs_coeff;
579 i.end = i.end / abs_coeff;
580 }
581 UnionOfSortedIntervals(&result.intervals_);
582 if (coeff < 0) result.NegateInPlace();
583 return result;
584}
585
586Domain Domain::InverseMultiplicationBy(const int64_t coeff) const {
587 if (coeff == 0) {
588 return Contains(0) ? Domain::AllValues() : Domain();
589 }
590 Domain result = *this;
591 int new_size = 0;
592 const int64_t abs_coeff = std::abs(coeff);
593 for (const ClosedInterval& i : result.intervals_) {
594 const int64_t start = CeilRatio(i.start, abs_coeff);
595 const int64_t end = FloorRatio(i.end, abs_coeff);
596 if (start > end) continue;
597 if (new_size > 0 && start == result.intervals_[new_size - 1].end + 1) {
598 result.intervals_[new_size - 1].end = end;
599 } else {
600 result.intervals_[new_size++] = {start, end};
601 }
602 }
603 result.intervals_.resize(new_size);
604 result.intervals_.shrink_to_fit();
605 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
606 if (coeff < 0) result.NegateInPlace();
607 return result;
608}
609
610namespace {
611Domain ModuloHelper(int64_t min, int64_t max, const Domain& modulo) {
612 DCHECK_GT(min, 0);
613 DCHECK_GT(modulo.Min(), 0);
614 const int64_t max_mod = modulo.Max() - 1;
615
616 // The min/max are exact if the modulo is fixed. Note that we could return the
617 // exact domain with a potential hole but we currently don't.
618 if (modulo.Min() == modulo.Max()) {
619 const int64_t size = max - min;
620 const int64_t v1 = min % modulo.Max();
621 if (v1 + size > max_mod) return Domain(0, max_mod);
622 return Domain(v1, v1 + size);
623 }
624
625 // TODO(user): This is a superset.
626 return Domain(0, std::min(max, max_mod));
627}
628} // namespace
629
631 if (IsEmpty()) return Domain();
632 CHECK_GT(modulo.Min(), 0);
633 const int64_t max_mod = modulo.Max() - 1;
634 if (Max() >= 0 && Min() <= 0) {
635 return Domain(std::max(Min(), -max_mod), std::min(Max(), max_mod));
636 }
637 if (Min() > 0) {
638 return ModuloHelper(Min(), Max(), modulo);
639 }
640 DCHECK_LT(Max(), 0);
641 return ModuloHelper(-Max(), -Min(), modulo).Negation();
642}
643
645 if (IsEmpty()) return Domain();
646 CHECK_GT(divisor.Min(), 0);
647 return Domain(std::min(Min() / divisor.Max(), Min() / divisor.Min()),
648 std::max(Max() / divisor.Min(), Max() / divisor.Max()));
649}
650
652 if (IsEmpty()) return Domain();
653 const Domain abs_domain =
654 IntersectionWith({0, std::numeric_limits<int64_t>::max()})
656 {0, std::numeric_limits<int64_t>::max()}));
657 if (abs_domain.Size() >= kDomainComplexityLimit) {
658 Domain result;
659 result.intervals_.reserve(abs_domain.NumIntervals());
660 for (const auto& interval : abs_domain) {
661 result.intervals_.push_back(
662 ClosedInterval(CapProd(interval.start, interval.start),
663 CapProd(interval.end, interval.end)));
664 }
665 UnionOfSortedIntervals(&result.intervals_);
666 return result;
667 } else {
668 std::vector<int64_t> values;
669 values.reserve(abs_domain.Size());
670 for (const int64_t value : abs_domain.Values()) {
671 values.push_back(CapProd(value, value));
672 }
673 return Domain::FromValues(std::move(values));
674 }
675}
676
677namespace {
678ClosedInterval EvaluateQuadraticProdInterval(int64_t a, int64_t b, int64_t c,
679 int64_t d, int64_t variable_min,
680 int64_t variable_max) {
681 // We have (a*x + b)(c*x + d) = a*c*x*x + (a*d + b*c)*x + b*d
682 // The minimum or maximum is at x = -(a*d + b*c)/(2*a*c)
683 //
684 // The minimum and maximum of the expression happens when x is one of the
685 // following:
686 // - variable_min;
687 // - variable_max;
688 // - the closest point to the parabola extreme, rounded down;
689 // - the closest point to the parabola extreme, rounded up.
690
691 const absl::int128 nominator =
692 -absl::int128{a} * absl::int128{d} - absl::int128{b} * absl::int128{c};
693 const absl::int128 denominator = absl::int128{a} * absl::int128{c};
694 const absl::int128 evaluated_minimum_point = (nominator / denominator) / 2;
695
696 const auto& evaluate = [&a, &b, &c, &d](const int64_t x) {
697 return CapProd(CapAdd(CapProd(a, x), b), CapAdd(CapProd(c, x), d));
698 };
699
700 const int64_t at_min_x = evaluate(variable_min);
701 const int64_t at_max_x = evaluate(variable_max);
702 int64_t min_var = std::min(at_min_x, at_max_x);
703 int64_t max_var = std::max(at_min_x, at_max_x);
704
705 if (evaluated_minimum_point > variable_min &&
706 evaluated_minimum_point < variable_max) {
707 const int64_t point_at_minimum_64 =
708 static_cast<int64_t>(evaluated_minimum_point);
709 const int rounder = ((nominator > 0) == (denominator > 0) ? 1 : -1);
710 const int64_t point1 = evaluate(point_at_minimum_64);
711 const int64_t point2 = evaluate(point_at_minimum_64 + rounder);
712 min_var = std::min(min_var, std::min(point1, point2));
713 max_var = std::max(max_var, std::max(point1, point2));
714 }
715
716 return ClosedInterval(min_var, max_var);
717}
718} // namespace
719
720Domain Domain::QuadraticSuperset(int64_t a, int64_t b, int64_t c,
721 int64_t d) const {
722 if (IsEmpty()) return Domain();
723
724 if (Size() < kDomainComplexityLimit) {
725 std::vector<int64_t> values;
726 values.reserve(Size());
727 for (const int64_t value : Values()) {
728 values.push_back( //
729 CapProd( //
730 CapAdd(CapProd(a, value), b), CapAdd(CapProd(c, value), d)));
731 }
732 return Domain::FromValues(std::move(values));
733 }
734
735 if (a == 0) {
736 return MultiplicationBy(CapProd(c, b)).AdditionWith(Domain(CapProd(d, b)));
737 }
738 if (c == 0) {
739 return MultiplicationBy(CapProd(a, d)).AdditionWith(Domain(CapProd(d, b)));
740 }
741
742 Domain result;
743 result.intervals_.reserve(NumIntervals());
744 for (const auto& interval : intervals_) {
745 result.intervals_.push_back(EvaluateQuadraticProdInterval(
746 a, b, c, d, interval.start, interval.end));
747 }
748 std::sort(result.intervals_.begin(), result.intervals_.end());
749 UnionOfSortedIntervals(&result.intervals_);
750 return result;
751}
752
753// It is a bit difficult to see, but this code is doing the same thing as
754// for all interval in this.UnionWith(implied_domain.Complement())):
755// - Take the two extreme points (min and max) in interval \inter implied.
756// - Append to result [min, max] if these points exists.
758 Domain result;
759 if (implied_domain.IsEmpty()) return result;
760
761 int i = 0;
762 int64_t min_point;
763 int64_t max_point;
764 bool started = false;
765 for (const ClosedInterval interval : intervals_) {
766 // We only "close" the new result interval if it cannot be extended by
767 // implied_domain.Complement(). The only extension possible look like:
768 // interval_: ...] [....
769 // implied : ...] [... i ...]
770 if (started && implied_domain.intervals_[i].start < interval.start) {
771 result.intervals_.push_back({min_point, max_point});
772 started = false;
773 }
774
775 // Find the two extreme points in interval \inter implied_domain.
776 // Always stop the loop at the first interval with and end strictly greater
777 // that interval.end.
778 for (; i < implied_domain.intervals_.size(); ++i) {
779 const ClosedInterval current = implied_domain.intervals_[i];
780 if (current.end >= interval.start && current.start <= interval.end) {
781 // Current and interval have a non-empty intersection.
782 const int64_t inter_max = std::min(interval.end, current.end);
783 if (!started) {
784 started = true;
785 min_point = std::max(interval.start, current.start);
786 max_point = inter_max;
787 } else {
788 // No need to update the min_point here, and the new inter_max must
789 // necessarily be > old one.
790 DCHECK_GE(inter_max, max_point);
791 max_point = inter_max;
792 }
793 }
794 if (current.end > interval.end) break;
795 }
796 if (i == implied_domain.intervals_.size()) break;
797 }
798 if (started) {
799 result.intervals_.push_back({min_point, max_point});
800 }
801 DCHECK(IntervalsAreSortedAndNonAdjacent(result.intervals_));
802 return result;
803}
804
805std::vector<int64_t> Domain::FlattenedIntervals() const {
806 std::vector<int64_t> result;
807 for (const ClosedInterval& interval : intervals_) {
808 result.push_back(interval.start);
809 result.push_back(interval.end);
810 }
811 return result;
812}
813
814bool Domain::operator<(const Domain& other) const {
815 const auto& d1 = intervals_;
816 const auto& d2 = other.intervals_;
817 const int common_size = std::min(d1.size(), d2.size());
818 for (int i = 0; i < common_size; ++i) {
819 const ClosedInterval& i1 = d1[i];
820 const ClosedInterval& i2 = d2[i];
821 if (i1.start < i2.start) return true;
822 if (i1.start > i2.start) return false;
823 if (i1.end < i2.end) return true;
824 if (i1.end > i2.end) return false;
825 }
826 return d1.size() < d2.size();
827}
828
829std::string Domain::ToString() const { return IntervalsAsString(intervals_); }
830
831int64_t SumOfKMinValueInDomain(const Domain& domain, int k) {
832 int64_t current_sum = 0.0;
833 int current_index = 0;
834 for (const ClosedInterval interval : domain) {
835 if (current_index >= k) break;
836 for (int v(interval.start); v <= interval.end; ++v) {
837 if (current_index >= k) break;
838 current_index++;
839 current_sum += v;
840 }
841 }
842 return current_sum;
843}
844
845int64_t SumOfKMaxValueInDomain(const Domain& domain, int k) {
846 return -SumOfKMinValueInDomain(domain.Negation(), k);
847}
848
850
852 const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
853 InsertIntervals(starts, ends);
854}
855
857 const std::vector<int>& starts, const std::vector<int>& ends) {
858 InsertIntervals(starts, ends);
859}
860
862 const std::vector<ClosedInterval>& intervals) {
863 for (ClosedInterval interval : intervals) {
864 InsertInterval(interval.start, interval.end);
865 }
866}
867
870 int64_t end) {
871 SortedDisjointIntervalList interval_list;
872 int64_t next_start = start;
873 for (auto it = FirstIntervalGreaterOrEqual(start); it != this->end(); ++it) {
874 const ClosedInterval& interval = *it;
875 const int64_t next_end = CapSub(interval.start, 1);
876 if (next_end > end) break;
877 if (next_start <= next_end) {
878 interval_list.InsertInterval(next_start, next_end);
879 }
880 next_start = CapAdd(interval.end, 1);
881 }
882 if (next_start <= end) {
883 interval_list.InsertInterval(next_start, end);
884 }
885 return interval_list;
886}
887
889 int64_t start, int64_t end) {
890 // start > end could mean an empty interval, but we prefer to LOG(DFATAL)
891 // anyway. Really, the user should never give us that.
892 if (start > end) {
893 LOG(DFATAL) << "Invalid interval: " << ClosedInterval({start, end});
894 return intervals_.end();
895 }
896
897 auto result = intervals_.insert({start, end});
898 if (!result.second) return result.first; // Duplicate: exit immediately.
899
900 // TODO(user): tune the algorithm below if it proves to be a bottleneck.
901 // For example, one could try to avoid an insertion if it's not needed
902 // (when the interval merges with a single existing interval or is fully
903 // contained by one).
904
905 // Iterate over the previous iterators whose end is after (or almost at) our
906 // start. After this, "it1" will point to the first interval that needs to be
907 // merged with the current interval (possibly pointing to the current interval
908 // itself, if no "earlier" interval should be merged).
909 auto it1 = result.first;
910 if (start == kint64min) { // Catch underflows
911 it1 = intervals_.begin();
912 } else {
913 const int64_t before_start = start - 1;
914 while (it1 != intervals_.begin()) {
915 auto prev_it = it1;
916 --prev_it;
917 if (prev_it->end < before_start) break;
918 it1 = prev_it;
919 }
920 }
921
922 // Ditto, on the other side: "it2" will point to the interval *after* the last
923 // one that should be merged with the current interval.
924 auto it2 = result.first;
925 if (end == kint64max) {
926 it2 = intervals_.end();
927 } else {
928 const int64_t after_end = end + 1;
929 do {
930 ++it2;
931 } while (it2 != intervals_.end() && it2->start <= after_end);
932 }
933
934 // [it1..it2) is the range (inclusive on it1, exclusive on it2) of intervals
935 // that should be merged together. We'll set it3 = it2-1 and erase [it1..it3)
936 // and set *it3 to the merged interval.
937 auto it3 = it2;
938 it3--;
939 if (it1 == it3) return it3; // Nothing was merged.
940 const int64_t new_start = std::min(it1->start, start);
941 const int64_t new_end = std::max(it3->end, end);
942 auto it = intervals_.erase(it1, it3);
943 // HACK(user): set iterators point to *const* values. Which is expected,
944 // because if one alters a set element's value, then it collapses the set
945 // property! But in this very special case, we know that we can just overwrite
946 // it->start, so we do it.
947 const_cast<ClosedInterval*>(&(*it))->start = new_start;
948 const_cast<ClosedInterval*>(&(*it))->end = new_end;
949 return it;
950}
951
952template <class T>
953void SortedDisjointIntervalList::InsertAll(const std::vector<T>& starts,
954 const std::vector<T>& ends) {
955 CHECK_EQ(starts.size(), ends.size());
956 for (int i = 0; i < starts.size(); ++i) InsertInterval(starts[i], ends[i]);
957}
958
960 const std::vector<int64_t>& starts, const std::vector<int64_t>& ends) {
961 InsertAll(starts, ends);
962}
963
964void SortedDisjointIntervalList::InsertIntervals(const std::vector<int>& starts,
965 const std::vector<int>& ends) {
966 // TODO(user): treat kint32min and kint32max as their kint64 variants.
967 InsertAll(starts, ends);
968}
969
972 const auto it = intervals_.upper_bound({value, kint64max});
973 if (it == begin()) return it;
974 auto it_prev = it;
975 it_prev--;
976 DCHECK_LE(it_prev->start, value);
977 return it_prev->end >= value ? it_prev : it;
978}
979
982 const auto it = intervals_.upper_bound({value, kint64max});
983 if (it == begin()) return end();
984 auto it_prev = it;
985 it_prev--;
986 return it_prev;
987}
988
990 std::string str;
991 for (const ClosedInterval& interval : intervals_) {
992 str += interval.DebugString();
993 }
994 return str;
995}
996
997} // namespace operations_research
Domain SimplifyUsingImpliedDomain(const Domain &implied_domain) const
static Domain FromVectorIntervals(const std::vector< std::vector< int64_t > > &intervals)
bool OverlapsWith(const Domain &domain) const
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
static Domain LowerOrEqual(int64_t value)
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
static Domain GreaterOrEqual(int64_t value)
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)
OR-Tools root namespace.
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)
ClosedInterval::Iterator end(ClosedInterval interval)
int64_t SumOfKMinValueInDomain(const Domain &domain, int k)
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)
static int input(yyscan_t yyscanner)
Definition model.h:50
static const int64_t kint64max
Definition types.h:32
static const int64_t kint64min
Definition types.h:31