parquet_geospatial/
interval.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use arrow_schema::ArrowError;
19
20/// Generic 1D intervals with wraparound support
21///
22/// This trait specifies common behaviour implemented by the [Interval] and
23/// [WraparoundInterval].
24///
25/// Briefly, "wraparound" support was included in the Parquet specification
26/// to ensure that geometries or geographies with components on both sides of
27/// antimeridian (180 degrees longitude) can be reasonably summarized. This
28/// concept was borrowed from the widely used GeoJSON and matches identical
29/// bounding box specifications for GeoParquet and STAC, among others.
30///
31/// Because the Parquet specification also states that longitude values
32/// are always stored as `x` values (i.e., the first coordinate component),
33/// this contingency is only available for the `xmin`/`xmax` component of the
34/// GeoStatistics. Thus, the `xmin`/`xmax` pair may either represent a regular
35/// interval (specified by xmin = 10 and xmax = 20):
36///
37/// ```text
38///           10         20
39///            |==========|
40/// ```
41///
42/// ...or a "wraparound" interval (specified by xmin = 20 and xmax = 10). This
43/// interval is the union of the two regular intervals (-Inf, 10] and (20, Inf).
44/// Infinity was chosen rather than any particular value to ensure that Parquet
45/// implementations did not have to consider the value of the coordinate
46/// reference system when comparing intervals.
47///
48/// ```text
49///           10         20
50/// <==========|          |============>
51/// ```
52///
53/// In general, one should use [Interval] unless specifically working with
54/// wraparound, as the contingency of wraparound incurs overhead (particularly
55/// in a loop). This trait is mostly used to simplify testing and unify
56/// documentation for the two concrete implementations.
57pub trait IntervalTrait: std::fmt::Debug + PartialEq {
58    /// Create an interval from lo and hi values
59    fn new(lo: f64, hi: f64) -> Self;
60
61    /// Create an empty interval that intersects nothing (except the full interval)
62    fn empty() -> Self;
63
64    /// Create the full interval (that intersects everything, including the empty interval)
65    fn full() -> Self;
66
67    /// Lower bound
68    ///
69    /// If `is_wraparound()` returns false, this is also the minimum value. When empty,
70    /// this value is Infinity; when full, this value is -Infinity.
71    fn lo(&self) -> f64;
72
73    /// Upper bound
74    ///
75    /// If `is_wraparound()` returns false, this is also the maximum value. When empty,
76    /// this value is -Infinity; when full, this value is Infinity.
77    fn hi(&self) -> f64;
78
79    /// Check for wraparound
80    ///
81    /// If `is_wraparound()` returns false, this interval represents the values that are
82    /// between lo and hi. If `is_wraparound()` returns true, this interval represents
83    /// the values that are *not* between lo and hi.
84    ///
85    /// It is recommended to work directly with an [Interval] where this is guaranteed to
86    /// return false unless wraparound support is specifically required.
87    fn is_wraparound(&self) -> bool;
88
89    /// Check for potential intersection with a value
90    ///
91    /// Note that intervals always contain their endpoints (for both the wraparound and
92    /// non-wraparound case).
93    fn intersects_value(&self, value: f64) -> bool;
94
95    /// Check for potential intersection with an interval
96    ///
97    /// Note that intervals always contain their endpoints (for both the wraparound and
98    /// non-wraparound case).
99    ///
100    /// This method accepts Self for performance reasons to prevent unnecessary checking of
101    /// `is_wraparound()` when not required for an implementation.
102    fn intersects_interval(&self, other: &Self) -> bool;
103
104    /// Check for potential containment of an interval
105    ///
106    /// Note that intervals always contain their endpoints (for both the wraparound and
107    /// non-wraparound case).
108    ///
109    /// This method accepts Self for performance reasons to prevent unnecessary checking of
110    /// `is_wraparound()` when not required for an implementation.
111    fn contains_interval(&self, other: &Self) -> bool;
112
113    /// The width of the interval
114    ///
115    /// For the non-wraparound case, this is the distance between lo and hi. For the wraparound
116    /// case, this is infinity.
117    fn width(&self) -> f64;
118
119    /// The midpoint of the interval
120    ///
121    /// For the non-wraparound case, this is the point exactly between lo and hi. For the wraparound
122    /// case, this is arbitrarily chosen as infinity (to preserve the property that intervals intersect
123    /// their midpoint).
124    fn mid(&self) -> f64;
125
126    /// True if this interval is empty (i.e. intersects no values)
127    fn is_empty(&self) -> bool;
128
129    /// Compute a new interval that is the union of both
130    ///
131    /// When accumulating intervals in a loop, use [Interval::update_interval].
132    fn merge_interval(&self, other: &Self) -> Self;
133
134    /// Compute a new interval that is the union of both
135    ///
136    /// When accumulating intervals in a loop, use [Interval::update_value].
137    fn merge_value(&self, other: f64) -> Self;
138
139    /// Expand this interval by a given distance
140    ///
141    /// Returns a new interval where both endpoints are moved outward by the given distance.
142    /// For regular intervals, this expands both lo and hi by the distance.
143    /// For wraparound intervals, this may result in the full interval if expansion is large enough.
144    fn expand_by(&self, distance: f64) -> Self;
145}
146
147/// 1D Interval that never wraps around
148///
149/// Represents a minimum and maximum value without wraparound logic (see [WraparoundInterval]
150/// for a wraparound implementation).
151#[derive(Debug, Clone, Copy, PartialEq)]
152pub struct Interval {
153    /// Lower bound
154    lo: f64,
155
156    /// Upper bound
157    hi: f64,
158}
159
160impl Interval {
161    /// Expand this interval to the union of self and other in place
162    ///
163    /// Note that NaN values are ignored when updating bounds.
164    pub fn update_interval(&mut self, other: &Self) {
165        self.lo = self.lo.min(other.lo);
166        self.hi = self.hi.max(other.hi);
167    }
168
169    /// Expand this interval to the union of self and other in place
170    ///
171    /// Note that NaN values are ignored when updating bounds.
172    pub fn update_value(&mut self, other: f64) {
173        self.lo = self.lo.min(other);
174        self.hi = self.hi.max(other);
175    }
176}
177
178impl From<(f64, f64)> for Interval {
179    fn from(value: (f64, f64)) -> Self {
180        Interval::new(value.0, value.1)
181    }
182}
183
184impl From<(i32, i32)> for Interval {
185    fn from(value: (i32, i32)) -> Self {
186        Interval::new(value.0 as f64, value.1 as f64)
187    }
188}
189
190impl TryFrom<WraparoundInterval> for Interval {
191    type Error = ArrowError;
192
193    fn try_from(value: WraparoundInterval) -> Result<Self, Self::Error> {
194        if value.is_wraparound() {
195            Err(ArrowError::InvalidArgumentError(format!(
196                "Can't convert wraparound interval {value:?} to Interval"
197            )))
198        } else {
199            Ok(Interval::new(value.lo(), value.hi()))
200        }
201    }
202}
203
204impl IntervalTrait for Interval {
205    fn new(lo: f64, hi: f64) -> Self {
206        Self { lo, hi }
207    }
208
209    fn empty() -> Self {
210        Self {
211            lo: f64::INFINITY,
212            hi: -f64::INFINITY,
213        }
214    }
215
216    fn full() -> Self {
217        Self {
218            lo: -f64::INFINITY,
219            hi: f64::INFINITY,
220        }
221    }
222
223    fn lo(&self) -> f64 {
224        self.lo
225    }
226
227    fn hi(&self) -> f64 {
228        self.hi
229    }
230
231    fn is_wraparound(&self) -> bool {
232        false
233    }
234
235    fn intersects_value(&self, value: f64) -> bool {
236        value >= self.lo && value <= self.hi
237    }
238
239    fn intersects_interval(&self, other: &Self) -> bool {
240        self.lo <= other.hi && other.lo <= self.hi
241    }
242
243    fn contains_interval(&self, other: &Self) -> bool {
244        self.lo <= other.lo && self.hi >= other.hi
245    }
246
247    fn width(&self) -> f64 {
248        self.hi - self.lo
249    }
250
251    fn mid(&self) -> f64 {
252        self.lo + self.width() / 2.0
253    }
254
255    fn is_empty(&self) -> bool {
256        self.width() == -f64::INFINITY
257    }
258
259    fn merge_interval(&self, other: &Self) -> Self {
260        let mut out = *self;
261        out.update_interval(other);
262        out
263    }
264
265    fn merge_value(&self, other: f64) -> Self {
266        let mut out = *self;
267        out.update_value(other);
268        out
269    }
270
271    fn expand_by(&self, distance: f64) -> Self {
272        if self.is_empty() || distance.is_nan() || distance < 0.0 {
273            return *self;
274        }
275
276        Self::new(self.lo - distance, self.hi + distance)
277    }
278}
279
280/// 1D Interval that may or may not wrap around
281///
282/// Concrete implementation that handles both the wraparound and regular
283/// interval case. This is separated from the [Interval] because the
284/// [Interval] is faster and most operations will use it directly (invoking
285/// this struct when it is specifically required).
286#[derive(Debug, Clone, Copy, PartialEq)]
287pub struct WraparoundInterval {
288    inner: Interval,
289}
290
291impl WraparoundInterval {
292    /// Splits this interval into exactly two non-wraparound intervals
293    ///
294    /// If this interval does not wrap around, one of these intervals will
295    /// be empty.
296    fn split(&self) -> (Interval, Interval) {
297        if self.is_wraparound() {
298            (
299                Interval {
300                    lo: -f64::INFINITY,
301                    hi: self.inner.hi,
302                },
303                Interval {
304                    lo: self.inner.lo,
305                    hi: f64::INFINITY,
306                },
307            )
308        } else {
309            (self.inner, Interval::empty())
310        }
311    }
312}
313
314impl From<(f64, f64)> for WraparoundInterval {
315    fn from(value: (f64, f64)) -> Self {
316        WraparoundInterval::new(value.0, value.1)
317    }
318}
319
320impl From<(i32, i32)> for WraparoundInterval {
321    fn from(value: (i32, i32)) -> Self {
322        WraparoundInterval::new(value.0 as f64, value.1 as f64)
323    }
324}
325
326impl From<Interval> for WraparoundInterval {
327    fn from(value: Interval) -> Self {
328        WraparoundInterval::new(value.lo(), value.hi())
329    }
330}
331
332impl IntervalTrait for WraparoundInterval {
333    fn new(lo: f64, hi: f64) -> Self {
334        Self {
335            inner: Interval::new(lo, hi),
336        }
337    }
338
339    fn empty() -> Self {
340        Self {
341            inner: Interval::empty(),
342        }
343    }
344
345    fn full() -> Self {
346        Self {
347            inner: Interval::full(),
348        }
349    }
350
351    fn lo(&self) -> f64 {
352        self.inner.lo
353    }
354
355    fn hi(&self) -> f64 {
356        self.inner.hi
357    }
358
359    fn is_wraparound(&self) -> bool {
360        !self.is_empty() && self.inner.width() < 0.0
361    }
362
363    fn intersects_value(&self, value: f64) -> bool {
364        let (left, right) = self.split();
365        left.intersects_value(value) || right.intersects_value(value)
366    }
367
368    fn intersects_interval(&self, other: &Self) -> bool {
369        let (left, right) = self.split();
370        let (other_left, other_right) = other.split();
371        left.intersects_interval(&other_left)
372            || left.intersects_interval(&other_right)
373            || right.intersects_interval(&other_left)
374            || right.intersects_interval(&other_right)
375    }
376
377    fn contains_interval(&self, other: &Self) -> bool {
378        let (left, right) = self.split();
379        let (other_left, other_right) = other.split();
380        left.contains_interval(&other_left) && right.contains_interval(&other_right)
381    }
382
383    fn width(&self) -> f64 {
384        if self.is_wraparound() {
385            f64::INFINITY
386        } else {
387            self.inner.width()
388        }
389    }
390
391    fn mid(&self) -> f64 {
392        if self.is_wraparound() {
393            f64::INFINITY
394        } else {
395            self.inner.mid()
396        }
397    }
398
399    fn is_empty(&self) -> bool {
400        self.inner.is_empty()
401    }
402
403    fn merge_interval(&self, other: &Self) -> Self {
404        if self.is_empty() {
405            return *other;
406        }
407
408        if other.is_empty() {
409            return *self;
410        }
411
412        let (wraparound, not_wraparound) = match (self.is_wraparound(), other.is_wraparound()) {
413            // Handle wraparound/not wraparound below
414            (true, false) => (self, other),
415            (false, true) => (other, self),
416            // Both are wraparound: Merge the two left intervals, then merge the two right intervals
417            // and check if we need the full interval
418            (true, true) => {
419                let (left, right) = self.split();
420                let (other_left, other_right) = other.split();
421
422                let new_left = left.merge_interval(&other_left);
423                let new_right = right.merge_interval(&other_right);
424
425                // If the left and right intervals intersect each other, we need the full interval
426                if new_left.intersects_interval(&new_right) {
427                    return WraparoundInterval::full();
428                } else {
429                    return WraparoundInterval::new(new_right.lo(), new_left.hi());
430                }
431            }
432            // Neither are wraparound: just merge the inner intervals
433            (false, false) => {
434                return Self {
435                    inner: self.inner.merge_interval(&other.inner),
436                };
437            }
438        };
439
440        let (left, right) = wraparound.split();
441        let distance_not_wraparound_left = (not_wraparound.mid() - left.hi()).abs();
442        let distance_not_wraparound_right = (not_wraparound.mid() - right.lo()).abs();
443        let (new_left, new_right) = if distance_not_wraparound_left < distance_not_wraparound_right
444        {
445            (left.merge_interval(&not_wraparound.inner), right)
446        } else {
447            (left, right.merge_interval(&not_wraparound.inner))
448        };
449
450        // If the left and right intervals intersect each other, we need the full interval
451        if new_left.intersects_interval(&new_right) {
452            WraparoundInterval::full()
453        } else {
454            WraparoundInterval::new(new_right.lo(), new_left.hi())
455        }
456    }
457
458    fn merge_value(&self, value: f64) -> Self {
459        if self.intersects_value(value) || value.is_nan() {
460            return *self;
461        }
462
463        if !self.is_wraparound() {
464            return Self {
465                inner: self.inner.merge_value(value),
466            };
467        }
468
469        // Move only one of the endpoints
470        let distance_left = value - self.inner.hi;
471        let distance_right = self.inner.lo - value;
472        debug_assert!(distance_left > 0.0);
473        debug_assert!(distance_right > 0.0);
474        if distance_left < distance_right {
475            Self {
476                inner: Interval {
477                    lo: self.inner.lo,
478                    hi: value,
479                },
480            }
481        } else {
482            Self {
483                inner: Interval {
484                    lo: value,
485                    hi: self.inner.hi,
486                },
487            }
488        }
489    }
490
491    fn expand_by(&self, distance: f64) -> Self {
492        if self.is_empty() || distance.is_nan() || distance < 0.0 {
493            return *self;
494        }
495
496        if !self.is_wraparound() {
497            // For non-wraparound, just expand the inner interval
498            return Self {
499                inner: self.inner.expand_by(distance),
500            };
501        }
502
503        // For wraparound intervals, expanding means including more values
504        // Wraparound interval (a, b) where a > b excludes the region (b, a)
505        // To expand by distance d, we shrink the excluded region from (b, a) to (b+d, a-d)
506        // This means the new wraparound interval becomes (a-d, b+d)
507        let excluded_lo = self.inner.hi + distance; // b + d
508        let excluded_hi = self.inner.lo - distance; // a - d
509
510        // If the excluded region disappears (excluded_lo >= excluded_hi), we get the full interval
511        if excluded_lo >= excluded_hi {
512            return Self::full();
513        }
514
515        // The new wraparound interval excludes (excluded_lo, excluded_hi)
516        // So the interval itself is (excluded_hi, excluded_lo)
517        Self::new(excluded_hi, excluded_lo)
518    }
519}
520
521#[cfg(test)]
522mod test {
523    use core::f64;
524
525    use super::*;
526
527    fn test_empty<T: IntervalTrait>(empty: T) {
528        // Equals itself
529        #[allow(clippy::eq_op)]
530        {
531            assert_eq!(empty, empty);
532        }
533
534        // Empty intersects no values
535        assert!(!empty.intersects_value(0.0));
536        assert!(!empty.intersects_value(f64::INFINITY));
537        assert!(!empty.intersects_value(-f64::INFINITY));
538        assert!(!empty.intersects_value(f64::NAN));
539
540        // Empty intersects no intervals
541        assert!(!empty.intersects_interval(&T::new(-10.0, 10.0)));
542        assert!(!empty.intersects_interval(&T::empty()));
543
544        // ...except the full interval
545        assert!(empty.intersects_interval(&T::full()));
546
547        // Empty contains no intervals
548        assert!(!empty.contains_interval(&T::new(-10.0, 10.0)));
549        assert!(!empty.contains_interval(&T::full()));
550
551        // ...except empty itself (empty set is subset of itself)
552        assert!(empty.contains_interval(&T::empty()));
553
554        // Merging NaN is still empty
555        assert_eq!(empty.merge_value(f64::NAN), empty);
556
557        // Merging an empty interval results in an empty interval
558        assert_eq!(empty.merge_interval(&empty), empty);
559
560        // Merging a value results in a interval with equal lo/hi
561        assert_eq!(empty.merge_value(12.0), T::new(12.0, 12.0));
562
563        // Merging a non-empty interval results in the other interval
564        assert_eq!(
565            empty.merge_interval(&T::new(10.0, 20.0)),
566            T::new(10.0, 20.0)
567        );
568
569        // Expanding empty interval keeps it empty
570        assert_eq!(empty.expand_by(5.0), empty);
571        assert_eq!(empty.expand_by(0.0), empty);
572        assert_eq!(empty.expand_by(-1.0), empty);
573        assert_eq!(empty.expand_by(f64::NAN), empty);
574    }
575
576    #[test]
577    fn interval_empty() {
578        let empty = Interval::empty();
579        test_empty(empty);
580    }
581
582    #[test]
583    fn wraparound_interval_empty() {
584        let empty = WraparoundInterval::empty();
585
586        // Should pass all the regular interval tests
587        test_empty(empty);
588
589        // Empty shouldn't be treated as wraparound
590        assert!(!empty.is_wraparound());
591
592        // When merging an interval where the other one is a
593        // wraparound, we should get the other interval
594        assert_eq!(
595            empty.merge_interval(&WraparoundInterval::new(20.0, 10.0)),
596            WraparoundInterval::new(20.0, 10.0)
597        );
598    }
599
600    fn test_finite<T: IntervalTrait>(finite: T) {
601        // Check accessors
602        assert_eq!(finite.lo(), 10.0);
603        assert_eq!(finite.hi(), 20.0);
604        assert_eq!(finite.mid(), 15.0);
605        assert_eq!(finite.width(), 10.0);
606        assert!(!finite.is_wraparound());
607        assert!(!finite.is_empty());
608
609        // Intersects endpoints and midpoint
610        assert!(finite.intersects_value(10.0));
611        assert!(finite.intersects_value(15.0));
612        assert!(finite.intersects_value(20.0));
613
614        // Doesn't intersect infinite values, NaN, or finite values outside
615        // the range
616        assert!(!finite.intersects_value(0.0));
617        assert!(!finite.intersects_value(f64::INFINITY));
618        assert!(!finite.intersects_value(-f64::INFINITY));
619        assert!(!finite.intersects_value(f64::NAN));
620
621        // Intervals that intersect
622        assert!(finite.intersects_interval(&T::new(14.0, 16.0)));
623        assert!(finite.intersects_interval(&T::new(5.0, 15.0)));
624        assert!(finite.intersects_interval(&T::new(15.0, 25.0)));
625        assert!(finite.intersects_interval(&T::new(5.0, 25.0)));
626        assert!(finite.intersects_interval(&T::full()));
627
628        // Barely touching ones count
629        assert!(finite.intersects_interval(&T::new(5.0, 10.0)));
630        assert!(finite.intersects_interval(&T::new(20.0, 25.0)));
631
632        // Intervals that don't intersect
633        assert!(!finite.intersects_interval(&T::new(0.0, 5.0)));
634        assert!(!finite.intersects_interval(&T::new(25.0, 30.0)));
635        assert!(!finite.intersects_interval(&T::empty()));
636
637        // Intervals that are contained
638        assert!(finite.contains_interval(&T::new(14.0, 16.0)));
639        assert!(finite.contains_interval(&T::new(10.0, 15.0)));
640        assert!(finite.contains_interval(&T::new(15.0, 20.0)));
641        assert!(finite.contains_interval(&T::new(10.0, 20.0))); // itself
642        assert!(finite.contains_interval(&T::empty()));
643
644        // Intervals that are not contained
645        assert!(!finite.contains_interval(&T::new(5.0, 15.0))); // extends below
646        assert!(!finite.contains_interval(&T::new(15.0, 25.0))); // extends above
647        assert!(!finite.contains_interval(&T::new(5.0, 25.0))); // extends both ways
648        assert!(!finite.contains_interval(&T::new(0.0, 5.0))); // completely below
649        assert!(!finite.contains_interval(&T::new(25.0, 30.0))); // completely above
650        assert!(!finite.contains_interval(&T::full())); // full interval is larger
651
652        // Merging NaN
653        assert_eq!(finite.merge_value(f64::NAN), finite);
654
655        // Merging Infinities
656        assert_eq!(
657            finite.merge_value(f64::INFINITY),
658            T::new(finite.lo(), f64::INFINITY)
659        );
660        assert_eq!(
661            finite.merge_value(-f64::INFINITY),
662            T::new(-f64::INFINITY, finite.hi())
663        );
664
665        // Merging a value within the interval
666        assert_eq!(finite.merge_value(15.0), finite);
667
668        // Merging a value above
669        assert_eq!(finite.merge_value(25.0), T::new(10.0, 25.0));
670
671        // Merging a value below
672        assert_eq!(finite.merge_value(5.0), T::new(5.0, 20.0));
673
674        // Merging an empty interval
675        assert_eq!(finite.merge_interval(&T::empty()), finite);
676
677        // Merging an interval with itself
678        assert_eq!(finite.merge_interval(&finite), finite);
679
680        // Merging an interval with the full interval
681        assert_eq!(finite.merge_interval(&T::full()), T::full());
682
683        // Merging an interval within the interval
684        assert_eq!(finite.merge_interval(&T::new(14.0, 16.0)), finite);
685
686        // Merging a partially overlapping interval below
687        assert_eq!(finite.merge_interval(&T::new(5.0, 15.0)), T::new(5.0, 20.0));
688
689        // Merging a partially overlapping interval above
690        assert_eq!(
691            finite.merge_interval(&T::new(15.0, 25.0)),
692            T::new(10.0, 25.0)
693        );
694
695        // Merging a disjoint interval below
696        assert_eq!(finite.merge_interval(&T::new(0.0, 5.0)), T::new(0.0, 20.0));
697
698        // Merging a disjoint interval above
699        assert_eq!(
700            finite.merge_interval(&T::new(25.0, 30.0)),
701            T::new(10.0, 30.0)
702        );
703
704        // Expanding by positive distance
705        assert_eq!(finite.expand_by(2.0), T::new(8.0, 22.0));
706        assert_eq!(finite.expand_by(5.0), T::new(5.0, 25.0));
707
708        // Expanding by zero does nothing
709        assert_eq!(finite.expand_by(0.0), finite);
710
711        // Expanding by negative distance does nothing
712        assert_eq!(finite.expand_by(-1.0), finite);
713
714        // Expanding by NaN does nothing
715        assert_eq!(finite.expand_by(f64::NAN), finite);
716    }
717
718    #[test]
719    fn interval_finite() {
720        let finite = Interval::new(10.0, 20.0);
721        test_finite(finite);
722    }
723
724    #[test]
725    fn wraparound_interval_finite() {
726        let finite = WraparoundInterval::new(10.0, 20.0);
727        test_finite(finite);
728
729        // Convert to an Interval
730        let interval: Interval = finite.try_into().unwrap();
731        assert_eq!(interval, Interval::new(10.0, 20.0));
732    }
733
734    #[test]
735    fn wraparound_interval_actually_wraparound_accessors() {
736        // Everything *except* the interval (10, 20)
737        let wraparound = WraparoundInterval::new(20.0, 10.0);
738        assert!(wraparound.is_wraparound());
739        assert!(!wraparound.is_empty());
740        assert_eq!(wraparound.mid(), f64::INFINITY);
741    }
742
743    #[test]
744    fn wraparound_interval_actually_wraparound_intersects_value() {
745        // Everything *except* the interval (10, 20)
746        let wraparound = WraparoundInterval::new(20.0, 10.0);
747
748        // Intersects endpoints but not a point between them
749        assert!(wraparound.intersects_value(10.0));
750        assert!(wraparound.intersects_value(20.0));
751        assert!(!wraparound.intersects_value(15.0));
752
753        // Intersects positive and negative infinity
754        assert!(wraparound.intersects_value(f64::INFINITY));
755        assert!(wraparound.intersects_value(-f64::INFINITY));
756
757        // ...but not NaN
758        assert!(!wraparound.intersects_value(f64::NAN));
759    }
760
761    #[test]
762    fn wraparound_interval_actually_wraparound_intersects_interval() {
763        // Everything *except* the interval (10, 20)
764        let wraparound = WraparoundInterval::new(20.0, 10.0);
765
766        // Intersects itself
767        assert!(wraparound.intersects_interval(&wraparound));
768
769        // Intersects the full interval
770        assert!(wraparound.intersects_interval(&WraparoundInterval::full()));
771
772        // Interval completely between endpoints doesn't intersect
773        assert!(!wraparound.intersects_interval(&WraparoundInterval::new(14.0, 16.0)));
774        // ...unless it's also wraparound
775        assert!(wraparound.intersects_interval(&WraparoundInterval::new(16.0, 14.0)));
776
777        // Intervals overlapping endpoints intersect whether the are or aren't wraparound
778        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 15.0)));
779        assert!(wraparound.intersects_interval(&WraparoundInterval::new(15.0, 5.0)));
780        assert!(wraparound.intersects_interval(&WraparoundInterval::new(15.0, 25.0)));
781        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 15.0)));
782
783        // Barely touching ones still intersect whether the are or aren't wraparound
784        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 10.0)));
785        assert!(wraparound.intersects_interval(&WraparoundInterval::new(10.0, 5.0)));
786        assert!(wraparound.intersects_interval(&WraparoundInterval::new(20.0, 25.0)));
787        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 20.0)));
788
789        // Intervals completely above and below endpoints do intersect whether they
790        // are or aren't wraparound
791        assert!(wraparound.intersects_interval(&WraparoundInterval::new(0.0, 5.0)));
792        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 0.0)));
793        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 30.0)));
794        assert!(wraparound.intersects_interval(&WraparoundInterval::new(30.0, 25.0)));
795    }
796
797    #[test]
798    fn wraparound_interval_actually_wraparound_contains_interval() {
799        // Everything *except* the interval (10, 20)
800        let wraparound = WraparoundInterval::new(20.0, 10.0);
801
802        // Contains itself
803        assert!(wraparound.contains_interval(&wraparound));
804
805        // Empty is contained by everything
806        assert!(wraparound.contains_interval(&WraparoundInterval::empty()));
807
808        // Does not contain the full interval
809        assert!(!wraparound.contains_interval(&WraparoundInterval::full()));
810
811        // Regular interval completely between endpoints is not contained
812        assert!(!wraparound.contains_interval(&WraparoundInterval::new(14.0, 16.0)));
813
814        // Wraparound intervals that exclude more (narrower included regions) are contained
815        assert!(wraparound.contains_interval(&WraparoundInterval::new(22.0, 8.0))); // excludes (8,22) which is larger than (10,20)
816        assert!(!wraparound.contains_interval(&WraparoundInterval::new(18.0, 12.0))); // excludes (12,18) which is smaller than (10,20)
817
818        // Regular intervals don't work the same way due to the split logic
819        // For a regular interval (a, b), split gives (left=(a,b), right=empty)
820        // For wraparound to contain it, we need both parts to be contained
821        // This means (-inf, 10] must contain (a,b) AND [20, inf) must contain empty
822        // The second is always true, but the first requires b <= 10
823        assert!(wraparound.contains_interval(&WraparoundInterval::new(0.0, 5.0))); // completely within left part
824        assert!(wraparound.contains_interval(&WraparoundInterval::new(-5.0, 10.0))); // fits in left part
825        assert!(!wraparound.contains_interval(&WraparoundInterval::new(25.0, 30.0))); // doesn't fit in left part
826        assert!(!wraparound.contains_interval(&WraparoundInterval::new(20.0, 25.0))); // doesn't fit in left part
827
828        // Regular intervals that overlap the excluded zone are not contained
829        assert!(!wraparound.contains_interval(&WraparoundInterval::new(5.0, 15.0))); // overlaps excluded zone
830        assert!(!wraparound.contains_interval(&WraparoundInterval::new(15.0, 25.0))); // overlaps excluded zone
831
832        // Wraparound intervals that exclude less (wider included regions) are not contained
833        assert!(!wraparound.contains_interval(&WraparoundInterval::new(15.0, 5.0))); // excludes (5,15) which is smaller
834        assert!(!wraparound.contains_interval(&WraparoundInterval::new(25.0, 15.0)));
835        // excludes (15,25) which is smaller
836    }
837
838    #[test]
839    fn wraparound_interval_actually_wraparound_merge_value() {
840        // Everything *except* the interval (10, 20)
841        let wraparound = WraparoundInterval::new(20.0, 10.0);
842
843        // Merging NaN
844        assert_eq!(wraparound.merge_value(f64::NAN), wraparound);
845
846        // Merging a value closer to the left endpoint should move
847        // that endpoint
848        assert_eq!(
849            wraparound.merge_value(12.0),
850            WraparoundInterval::new(20.0, 12.0)
851        );
852
853        // Merging a value closer to the right endpoint should move
854        // that endpoint
855        assert_eq!(
856            wraparound.merge_value(18.0),
857            WraparoundInterval::new(18.0, 10.0)
858        );
859
860        // Merging a value that is already intersecting shouldn't change the interval
861        assert_eq!(wraparound.merge_value(5.0), wraparound);
862        assert_eq!(wraparound.merge_value(10.0), wraparound);
863        assert_eq!(wraparound.merge_value(20.0), wraparound);
864        assert_eq!(wraparound.merge_value(25.0), wraparound);
865    }
866
867    #[test]
868    fn wraparound_interval_actually_wraparound_merge_interval() {
869        // Everything *except* the interval (10, 20)
870        let wraparound = WraparoundInterval::new(20.0, 10.0);
871
872        // Merging an empty interval
873        assert_eq!(
874            wraparound.merge_interval(&WraparoundInterval::empty()),
875            wraparound
876        );
877
878        // Merging an interval with itself
879        assert_eq!(wraparound.merge_interval(&wraparound), wraparound);
880
881        // Merging a wraparound interval with a "larger" wraparound interval
882        //           10         20
883        // <==========|          |============>
884        // <==============|  |================>
885        //               14  16
886        assert_eq!(
887            wraparound.merge_interval(&WraparoundInterval::new(16.0, 14.0)),
888            WraparoundInterval::new(16.0, 14.0)
889        );
890
891        // Merging a wraparound interval with a "smaller" wraparound interval
892        //           10         20
893        // <==========|          |============>
894        // <=====|                    |=======>
895        //       5                    25
896        // <==========|          |============>
897        assert_eq!(
898            wraparound.merge_interval(&WraparoundInterval::new(25.0, 5.0)),
899            wraparound
900        );
901
902        // Merge with partially intersecting wraparounds
903        //           10         20
904        // <==========|          |============>
905        // <=====|          |=================>
906        //       5          15
907        // <==========|     |=================>
908        assert_eq!(
909            wraparound.merge_interval(&WraparoundInterval::new(15.0, 5.0)),
910            WraparoundInterval::new(15.0, 10.0)
911        );
912
913        //           10         20
914        // <==========|          |============>
915        // <================|          |======>
916        //                  15         25
917        // <================|    |============>
918        assert_eq!(
919            wraparound.merge_interval(&WraparoundInterval::new(25.0, 15.0)),
920            WraparoundInterval::new(20.0, 15.0)
921        );
922
923        // Merge wraparound with wraparound whose union is the full interval
924        //           10         20
925        // <==========|          |=========================>
926        // <=============================|          |======>
927        //                               25         30
928        // <===============================================>
929        assert_eq!(
930            wraparound.merge_interval(&WraparoundInterval::new(30.0, 25.0)),
931            WraparoundInterval::full()
932        );
933
934        //                    10         20
935        // <===================|          |================>
936        // <==|          |=================================>
937        //    0          5
938        // <===============================================>
939        assert_eq!(
940            wraparound.merge_interval(&WraparoundInterval::new(5.0, 0.0)),
941            WraparoundInterval::full()
942        );
943
944        // Merge wraparound with a regular interval completely contained by the original
945        //                  10         20
946        // <=================|          |==================>
947        //                                   |=========|
948        //                                  25         30
949        // <=================|          |==================>
950        assert_eq!(
951            wraparound.merge_interval(&WraparoundInterval::new(25.0, 30.0)),
952            wraparound
953        );
954
955        //                  10         20
956        // <=================|          |==================>
957        //  |=========|
958        //  0         5
959        // <=================|          |==================>
960        assert_eq!(
961            wraparound.merge_interval(&WraparoundInterval::new(0.0, 5.0)),
962            wraparound
963        );
964
965        // Merge wraparound with a partially intersecting regular interval that
966        // should extend the left side
967        //                  10         20
968        // <=================|          |==================>
969        //              |=========|
970        //              5         15
971        // <======================|     |==================>
972        assert_eq!(
973            wraparound.merge_interval(&WraparoundInterval::new(5.0, 15.0)),
974            WraparoundInterval::new(20.0, 15.0)
975        );
976
977        // Merge wraparound with a partially intersecting regular interval that
978        // should extend the right side
979        //                  10         20
980        // <=================|          |==================>
981        //                         |=========|
982        //                         15        25
983        // <=================|     |==================>
984        assert_eq!(
985            wraparound.merge_interval(&WraparoundInterval::new(15.0, 25.0)),
986            WraparoundInterval::new(15.0, 10.0)
987        );
988
989        // Merge wraparound with a disjoint regular interval that should extend the left side
990        //                  10         20
991        // <=================|          |==================>
992        //                     |==|
993        //                    12  15
994        // <======================|     |==================>
995        assert_eq!(
996            wraparound.merge_interval(&WraparoundInterval::new(12.0, 15.0)),
997            WraparoundInterval::new(20.0, 15.0)
998        );
999
1000        // Merge wraparound with a disjoint regular interval that should extend the right side
1001        //                  10         20
1002        // <=================|          |==================>
1003        //                         |==|
1004        //                        15  18
1005        // <=================|     |==================>
1006        assert_eq!(
1007            wraparound.merge_interval(&WraparoundInterval::new(15.0, 18.0)),
1008            WraparoundInterval::new(15.0, 10.0)
1009        );
1010    }
1011
1012    #[test]
1013    fn wraparound_interval_actually_wraparound_expand_by() {
1014        // Everything *except* the interval (10, 20)
1015        let wraparound = WraparoundInterval::new(20.0, 10.0);
1016
1017        // Expanding by a small amount shrinks the excluded region
1018        // Original excludes (10, 20), expanding by 2 should exclude (12, 18)
1019        // So the new interval should be (18, 12) = everything except (12, 18)
1020        assert_eq!(
1021            wraparound.expand_by(2.0),
1022            WraparoundInterval::new(18.0, 12.0)
1023        ); // now excludes (12, 18)
1024
1025        // Expanding by 4 should exclude (14, 16)
1026        assert_eq!(
1027            wraparound.expand_by(4.0),
1028            WraparoundInterval::new(16.0, 14.0)
1029        ); // now excludes (14, 16)
1030
1031        // Expanding by 5.0 should exactly eliminate the excluded region
1032        // excluded region (10, 20) shrinks to (15, 15) which is empty
1033        assert_eq!(wraparound.expand_by(5.0), WraparoundInterval::full()); // excluded region disappears
1034
1035        // Any expansion greater than 5.0 should also give full interval
1036        assert_eq!(wraparound.expand_by(6.0), WraparoundInterval::full());
1037
1038        assert_eq!(wraparound.expand_by(100.0), WraparoundInterval::full());
1039
1040        // Expanding by zero does nothing
1041        assert_eq!(wraparound.expand_by(0.0), wraparound);
1042
1043        // Expanding by negative distance does nothing
1044        assert_eq!(wraparound.expand_by(-1.0), wraparound);
1045
1046        // Expanding by NaN does nothing
1047        assert_eq!(wraparound.expand_by(f64::NAN), wraparound);
1048
1049        // Test a finite (non-wraparound) wraparound interval
1050        let non_wraparound = WraparoundInterval::new(10.0, 20.0);
1051        assert!(!non_wraparound.is_wraparound());
1052        assert_eq!(
1053            non_wraparound.expand_by(2.0),
1054            WraparoundInterval::new(8.0, 22.0)
1055        );
1056
1057        // Test another wraparound case - excludes (5, 15) with width 10
1058        let wraparound2 = WraparoundInterval::new(15.0, 5.0);
1059        // Expanding by 3 should shrink excluded region from (5, 15) to (8, 12)
1060        assert_eq!(
1061            wraparound2.expand_by(3.0),
1062            WraparoundInterval::new(12.0, 8.0)
1063        );
1064
1065        // Expanding by 5 should make excluded region disappear: (5+5, 15-5) = (10, 10)
1066        assert_eq!(wraparound2.expand_by(5.0), WraparoundInterval::full());
1067    }
1068
1069    #[test]
1070    fn wraparound_interval_actually_wraparound_convert() {
1071        // Everything *except* the interval (10, 20)
1072        let wraparound = WraparoundInterval::new(20.0, 10.0);
1073
1074        // Can't convert a wraparound interval that actually wraps around to an Interval
1075        let err = Interval::try_from(wraparound).unwrap_err();
1076        assert!(
1077            err.to_string()
1078                .contains("Can't convert wraparound interval")
1079        );
1080    }
1081}