sigma  1.0.0
Loading...
Searching...
No Matches
interval.hpp
Go to the documentation of this file.
1#pragma once
2#include <algorithm>
3#include <boost/numeric/interval.hpp>
4#include <iostream>
5#include <optional>
6#include <type_traits>
7
11
12namespace sigma {
13
22template<typename ValueType>
23class Interval {
24public:
26 using value_t = ValueType;
27
35
44 Interval(value_t value) : Interval(value, value, false, false) {}
45
58 bool right_open = false);
59
71 value_t width() const {
72 assert_not_empty_();
73 return boost::numeric::width(*m_interval_);
74 }
75
82 value_t median() const {
83 assert_not_empty_();
84 return boost::numeric::median(*m_interval_);
85 }
86
96 value_t radius() const { return width() / value_t{2}; }
97
106 bool empty() const { return !m_interval_; }
107
114 bool left_open() const { return m_is_left_open_; }
115
122 bool left_closed() const { return !left_open(); }
123
130 bool right_open() const { return m_is_right_open_; }
131
138 bool right_closed() const { return !right_open(); }
139
146 value_t lower() const {
147 assert_not_empty_();
148 return m_interval_->lower();
149 }
150
157 value_t upper() const {
158 assert_not_empty_();
159 return m_interval_->upper();
160 }
161
175 bool contains(value_t value) const;
176
180 bool contains(const Interval& other) const;
181
194 Interval set_union(const Interval& other) const;
195
206 Interval set_intersection(const Interval& other) const;
207
208 // -- Arithmetic in-place operators ----------------------------------------
209
220 if(empty()) { return Interval(); }
221 return Interval(-upper(), -lower(), right_open(), left_open());
222 }
223
232
240 Interval& operator+=(value_t rhs) { return *this += Interval(rhs, rhs); }
241
249 Interval& operator-=(const Interval& rhs) { return *this += -rhs; }
250
258 Interval& operator-=(value_t rhs) { return *this -= Interval(rhs, rhs); }
259
268
276 Interval& operator*=(value_t rhs) { return *this *= Interval(rhs); }
277
286
294 Interval& operator/=(value_t rhs) { return *this /= Interval(rhs, rhs); }
295
305 std::string print_interval_form() const;
306
307private:
310 void assert_not_empty_() const {
311 if(empty()) { throw std::domain_error("Interval is empty"); }
312 }
313
315 using interval_t = boost::numeric::interval<value_t>;
316
318 bool m_is_left_open_ = false;
320 bool m_is_right_open_ = false;
321
323 std::optional<interval_t> m_interval_;
324
325}; // class Interval
326
327// -- Out-of-line Definitions --------------------------------------------------
328
329template<typename ValueType>
331 bool right_open) :
332 m_is_left_open_(left_open),
333 m_is_right_open_(right_open),
334 m_interval_(interval_t(std::min(lower, upper), std::max(lower, upper))) {
335 // A single value in an open interval is actually empty
336 if((left_open || right_open) && lower == upper) *this = Interval();
337}
338
339template<typename ValueType>
341 if(empty()) { return false; }
342
343 // Check if the value is definitely outside the interval
344 if(value < lower() || value > upper()) { return false; }
345
346 // Now we know it's squarely in the interval (or one of the bounds)
347
348 // Not allowed to be a bound if the interval is open on that side
349 if(left_open() && value == lower()) { return false; }
350 if(right_open() && value == upper()) { return false; }
351
352 return true;
353}
354
355template<typename ValueType>
356bool Interval<ValueType>::contains(const Interval& other) const {
357 if(other.empty()) { return true; }
358 if(empty()) { return false; }
359
360 // Check if definitely outside the interval
361 if(other.lower() < lower() || other.upper() > upper()) { return false; }
362
363 // Check if the interval is squarely inside the interval
364 if(other.lower() > lower() && other.upper() < upper()) { return true; }
365
366 // *this and other must share at least one bound
367 // For each bound the choices are:
368 // *this open and other closed -> not okay
369 // *this open and other open -> okay
370 // *this closed and other closed -> okay
371 // *this closed and other open -> okay
372 if(other.lower() == lower()) {
373 if(left_open() && other.left_closed()) { return false; }
374 }
375 if(other.upper() == upper()) {
376 if(right_open() && other.right_closed()) { return false; }
377 }
378 return true;
379}
380
381template<typename ValueType>
383 const Interval& other) const {
384 if(empty()) { return other; }
385 if(other.empty()) { return *this; }
386 if(set_intersection(other).empty()) {
387 throw std::domain_error("Intervals do not overlap");
388 }
389 value_t low = lower();
390 value_t hi = upper();
391 bool lower_is_open = false;
392 bool upper_is_open = false;
393 if(lower() < other.lower()) { // *this has the lower bound
394 lower_is_open = left_open();
395 } else if(lower() == other.lower()) {
396 // If both are open, then result is open; otherwise closed
397 lower_is_open = left_open() && other.left_open();
398 } else {
399 low = other.lower();
400 lower_is_open = other.left_open();
401 }
402
403 if(upper() > other.upper()) {
404 upper_is_open = right_open();
405 } else if(upper() == other.upper()) {
406 upper_is_open = right_open() && other.right_open();
407 } else {
408 hi = other.upper();
409 upper_is_open = other.right_open();
410 }
411
412 return Interval(low, hi, lower_is_open, upper_is_open);
413}
414
415template<typename ValueType>
417 const Interval& other) const {
418 if(empty() || other.empty()) { return Interval(); }
419 if(other.upper() < lower() || other.lower() > upper()) {
420 return Interval();
421 }
422 value_t low = lower();
423 value_t hi = upper();
424 bool lower_is_open = left_open();
425 bool upper_is_open = right_open();
426
427 if(lower() < other.lower()) {
428 low = other.lower();
429 lower_is_open = other.left_open();
430 } else if(lower() > other.lower()) {
431 low = lower();
432 lower_is_open = left_open();
433 } else {
434 lower_is_open = left_open() || other.left_open();
435 }
436
437 if(upper() > other.upper()) {
438 hi = other.upper();
439 upper_is_open = other.right_open();
440 } else if(upper() < other.upper()) {
441 hi = upper();
442 upper_is_open = right_open();
443 } else {
444 upper_is_open = right_open() || other.right_open();
445 }
446 return Interval(low, hi, lower_is_open, upper_is_open);
447}
448
449template<typename ValueType>
451 if(empty()) {
452 return *this = rhs;
453 } else if(rhs.empty()) {
454 return *this;
455 } else {
456 m_interval_ = *m_interval_ + *rhs.m_interval_;
457 m_is_left_open_ = left_open() || rhs.left_open();
458 m_is_right_open_ = right_open() || rhs.right_open();
459 }
460 return *this;
461}
462
463template<typename ValueType>
465 /* Putting this here in case it's helpful for optimizing.
466 * Given two intervals X = [a, b] and Y = [c, d], the product X * Y is:
467 * [min(ac, ad, bc, bd), max(ac, ad, bc, bd)]. Thus the entire product
468 * is determined by four sub-products: ac, ad, bc, and bd. Simple
469 * combinatorics suggests that there are 16 possible products for X*Y,
470 * (i.e., chose one sub-product for the new lower bound and one for the
471 * new upper bound).
472 *
473 * If we know that a < b and c < d, i.e., our intervals are not points,
474 * we can immediately rule out eight products:
475 *
476 * - [ac, ac], [ad, ad], [bc, bc], and [bd,bd].
477 * - [ac, ad], [ac, bc], [bd, bc], and [bd,ad].
478 *
479 * Proofs that these products are impossible are given below.
480 *
481 * To prove the first four products are impossible consider [ac, ac],
482 * which implies:
483 *
484 * - ac <= ad <= ac,
485 * - ac <= bc <= ac, and
486 * -ac <= bd <= ac
487 *
488 * The first inequality means c == d, the second means a == b. Which
489 * means X = [a, a] and Y = [c, c], i.e. X and Y must be points. This
490 * contridicts our assumption that X and Y are not points and thus these
491 * four products will never appear. Similar logic holds for the other
492 * three products of the form [xy, xy].
493 *
494 * Now consider the product [ac, ad]. This implies:
495 *
496 * - ac <= bc <= ad, and
497 * - ac <= bd <= ad
498 *
499 * If a is negative, then d <= bc/a <= c, which is not possible with the
500 * assumption c < d. If a is zero, the product [ac, ad] is [0, 0], which
501 * means bc and bd must be zero. b can't be zero because then
502 * X = [0, 0]. This would then mean that both c and d must be zero, but
503 * c and d both can't be zero because then Y = [0, 0]. Thus a must be
504 * positive. If a is positive, then so is b. If c >= 0 then d is
505 * positive and then we have that bd > ad, which is not consistent with
506 * bd <= ad. c also can't be negative because then bc < ac. We thus
507 * conclude that [ac, ad] is not possible.
508 *
509 * The remaining three proofs follow similarly. Let "target interval"
510 * refer to the interval we are proving is not possible. Then the proof
511 * proceeds by showing:
512 *
513 * - We get two inequalities by forcing the remaining two sub-products
514 * to be bounded by the target interval. Above this showed that bc and
515 * bd must be in the interval [ac, ad]. We term these the "implicit
516 * sub-products" because they do not show up explicitly in the target
517 * interval.
518 * - One of X or Y's bounds will appear twice in the target interval. We
519 * term this the "common bound". Above this bound was "a".
520 * - If the common bound is the lower bound of X(Y) try assuming the
521 * commonbound is negative and dividing the inequalities by it. If the
522 * common bound is the upper bound, instead divide the inequalities by
523 * the positive of it. This will cause one of the two inequalities to
524 * be a contradiction eliminating that possibility. Above this ruled
525 * out "a" being negative.
526 * - The common bound can't be zero otherwise X and Y collapse to
527 * point intervals.
528 * - This means the common bound must be positive (negative) for a lower
529 * (upper) bound. In turn, the "other bound" must be positive
530 * (negative) too. Above this "other bound" was "b".
531 * - The implicit sub-products will share other bound. If other bound is
532 * the upper (lower) bound, then assume that the lower (upper) bound
533 * of the other interval is positive (negative) or zero. This
534 * will force the upper (lower) bound of the other interval to be
535 * positive (negative) too. All bounds will now have the same sign
536 * and violate one of the inequalities.
537 * - The last possibility is then that the lower (upper) bound of the
538 * other interval has the opposite sign as the common bound. This too
539 * will violate one of the inequalities and we conclude that the
540 * target interval is not possible.
541 */
542
543 if(empty() || rhs.empty()) {
544 return *this = Interval();
545 } else if(width() == 0 && rhs.width() == 0) { // Both points
546 value_t prod = lower() * rhs.lower();
547 return *this = Interval(prod);
548 } else if(width() == 0) { // *this is a point, rhs is an interval
549 auto l = lower();
550 auto ll = l * rhs.lower();
551 auto lh = l * rhs.upper();
552 value_t low = std::min(ll, lh);
553 value_t high = std::max(ll, lh);
554 bool lower_is_open = rhs.left_open();
555 bool upper_is_open = rhs.right_open();
556 if(low != ll) {
557 lower_is_open = rhs.right_open();
558 upper_is_open = rhs.right_open();
559 }
560 return *this = Interval(low, high, lower_is_open, upper_is_open);
561 } else if(rhs.width() == 0) { // rhs is point, *this is an interval
562 // Dispatch to *this is point, rhs is interval
563 return *this = (rhs * (*this));
564 }
565 auto ll = lower() * rhs.lower();
566 auto lh = lower() * rhs.upper();
567 auto hl = upper() * rhs.lower();
568 auto hh = upper() * rhs.upper();
569 auto low = std::min(std::min(ll, lh), std::min(hl, hh));
570 auto high = std::max(std::max(ll, lh), std::max(hl, hh));
571 bool lower_is_open = left_open() || rhs.left_open();
572 bool upper_is_open = right_open() || rhs.right_open();
573 // if(low == ll) // default is correct for this case
574 if(low == lh) {
575 lower_is_open = left_open() || rhs.right_open();
576 } else if(low == hl) {
577 lower_is_open = right_open() || rhs.left_open();
578 } else if(low == hh) {
579 lower_is_open = right_open() || rhs.right_open();
580 }
581
582 // if(high == hh) // default is correct for this case
583 if(high == lh) {
584 upper_is_open = left_open() || rhs.right_open();
585 } else if(high == hl) {
586 upper_is_open = right_open() || rhs.left_open();
587 } else if(high == ll) {
588 upper_is_open = left_open() || rhs.left_open();
589 }
590
591 return *this = Interval(low, high, lower_is_open, upper_is_open);
592}
593
594template<typename ValueType>
596 if(empty() || rhs.empty()) {
597 *this = Interval();
598 return *this;
599 }
600 return *this *= value_t(1.0) / rhs;
601}
602
603template<typename ValueType>
605 if(empty()) { return "[∅]"; }
606 std::string left_open_str = left_open() ? "(" : "[";
607 std::string right_open_str = right_open() ? ")" : "]";
608
609 return left_open_str + std::to_string(lower()) + ", " +
610 std::to_string(upper()) + right_open_str;
611}
612
613// -- Utility functions --------------------------------------------------------
614
627template<typename ValueType>
628std::ostream& operator<<(std::ostream& os, const Interval<ValueType>& i) {
629 if(i.empty()) {
630 os << "∅";
631 return os;
632 }
633 os << i.median() << "+/-" << i.radius();
634 return os;
635}
636
647template<typename T1, typename T2>
648bool operator==(const Interval<T1>& lhs, const Interval<T2>& rhs) {
649 if constexpr(!std::is_same_v<T1, T2>) return false;
650 if(lhs.empty() && rhs.empty()) { return true; }
651 if(lhs.empty() || rhs.empty()) { return false; }
652 if(lhs.left_open() != rhs.left_open()) { return false; }
653 if(lhs.right_open() != rhs.right_open()) { return false; }
654 return lhs.lower() == rhs.lower() && lhs.upper() == rhs.upper();
655}
656
667template<typename T1, typename T2>
668bool operator!=(const Interval<T1>& lhs, const Interval<T2>& rhs) {
669 return !(lhs == rhs);
670}
671
672// -- Arithmetic free functions ------------------------------------------------
673
685template<typename T>
687 return lhs += rhs;
688}
689
691template<typename T>
693 return lhs += rhs;
694}
695
697template<typename T>
699 return rhs + lhs;
700}
701
713template<typename T>
715 return lhs -= rhs;
716}
717
719template<typename T>
721 return lhs -= rhs;
722}
723
725template<typename T>
727 return Interval<T>(lhs, lhs) -= rhs;
728}
729
741template<typename T>
743 return lhs *= rhs;
744}
745
747template<typename T>
749 return lhs *= rhs;
750}
751
753template<typename T>
755 return rhs * lhs;
756}
757
772template<typename T>
774 return lhs /= rhs;
775}
776
778template<typename T>
780 if(rhs == 0) { throw std::domain_error("Division by zero"); }
781 return lhs *= T(1.0 / rhs);
782}
783
785template<typename T>
787 if(rhs.empty()) { return rhs; }
788 if(rhs.contains(0)) { throw std::domain_error("Division by zero"); }
789 return Interval<T>(lhs / rhs.upper(), lhs / rhs.lower(), rhs.right_open(),
790 rhs.left_open());
791}
792
795
798
799} // namespace sigma
800
801namespace std {
802
804template<typename ValueType>
805struct hash<sigma::Interval<ValueType>> {
808 size_t operator()(const sigma::Interval<ValueType>& i) const {
809 std::size_t hash_low = std::hash<ValueType>()(i.lower());
810 std::size_t hash_high = std::hash<ValueType>()(i.upper());
811 std::size_t seed = hash_low;
812 seed ^= hash_high + 0x9e3779b9 + (seed << 6) + (seed >> 2);
813 return seed;
814 }
815};
816
817} // namespace std
818
Models a numeric interval.
Definition interval.hpp:23
Interval & operator-=(const Interval &rhs)
In-place subtraction of another interval.
Definition interval.hpp:249
Interval set_intersection(const Interval &other) const
Returns the intersection of this interval and another interval.
Definition interval.hpp:416
value_t median() const
Returns the midpoint of the interval.
Definition interval.hpp:82
value_t width() const
Returns the distance between the interval's bounds.
Definition interval.hpp:71
Interval< T > operator/(Interval< T > lhs, const Interval< T > &rhs)
Division of two intervals.
Definition interval.hpp:773
Interval & operator-=(value_t rhs)
In-place subtraction of a scalar.
Definition interval.hpp:258
bool right_closed() const
Is upeer contained in the interval?
Definition interval.hpp:138
value_t value_t
Definition interval.hpp:26
Interval & operator+=(value_t rhs)
In-place addition of a scalar.
Definition interval.hpp:240
value_t upper() const
Definition interval.hpp:157
Interval< T > operator-(Interval< T > lhs, const Interval< T > &rhs)
Subtraction of two intervals.
Definition interval.hpp:714
bool left_open() const
Definition interval.hpp:114
std::string print_interval_form() const
Print the interval in interval form.
Definition interval.hpp:604
value_t lower() const
Definition interval.hpp:146
bool right_open() const
Definition interval.hpp:130
Interval & operator/=(value_t rhs)
In-place division by a scalar.
Definition interval.hpp:294
Interval set_union(const Interval &other) const
Returns the union of this interval and another interval.
Definition interval.hpp:382
bool contains(value_t value) const
Whether a scalar lies in this interval.
Definition interval.hpp:340
Interval & operator*=(const Interval &rhs)
In-place multiplication by another interval.
Definition interval.hpp:464
bool operator==(const Interval< T1 > &lhs, const Interval< T2 > &rhs)
Compare two intervals for equality.
Definition interval.hpp:648
bool left_closed() const
Is lower() contained in the interval?
Definition interval.hpp:122
std::ostream & operator<<(std::ostream &os, const Interval< ValueType > &i)
Overload stream insertion to print an interval.
Definition interval.hpp:628
Interval()
Default constructor.
Definition interval.hpp:34
value_t radius() const
Returns the half-width of the interval.
Definition interval.hpp:96
Interval operator-() const
Negation of an interval.
Definition interval.hpp:219
Interval & operator/=(const Interval &rhs)
In-place division by another interval.
Definition interval.hpp:595
bool empty() const
Is *this the empty interval?
Definition interval.hpp:106
Interval & operator+=(const Interval &rhs)
In-place addition of another interval.
Definition interval.hpp:450
bool contains(const Interval &other) const
Is other fully contained in this interval?
Definition interval.hpp:356
Interval< T > operator*(Interval< T > lhs, const Interval< T > &rhs)
Multiplication of two intervals.
Definition interval.hpp:742
Interval & operator*=(value_t rhs)
In-place multiplication by a scalar.
Definition interval.hpp:276
Interval< T > operator+(Interval< T > lhs, const Interval< T > &rhs)
Addition of two intervals.
Definition interval.hpp:686
Interval(value_t value)
Construct an interval from a single value.
Definition interval.hpp:44
Interval(value_t lower, value_t upper, bool left_open=false, bool right_open=false)
Construct an interval from two bounds.
Definition interval.hpp:330
bool operator!=(const Interval< T1 > &lhs, const Interval< T2 > &rhs)
Compare two intervals for inequality.
Definition interval.hpp:668
Components for compatibility with Eigen.
Convenience header for interval operations.
The primary namespace for the sigma library.
Definition affine.hpp:12
Interval< T > operator/(Interval< T > lhs, T rhs)
Definition interval.hpp:779
Interval< float > IFloat
Typedef for an interval of floats.
Definition interval.hpp:794
Interval< T > operator-(Interval< T > lhs, T rhs)
Definition interval.hpp:720
Interval< T > operator*(Interval< T > lhs, T rhs)
Definition interval.hpp:748
Interval< T > operator+(Interval< T > lhs, T rhs)
Definition interval.hpp:692
Interval< double > IDouble
Typedef for an interval of doubles.
Definition interval.hpp:797
size_t operator()(const sigma::Interval< ValueType > &i) const
Definition interval.hpp:808