Skip to main content

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                return if new_left.intersects_interval(&new_right) {
427                    WraparoundInterval::full()
428                } else {
429                    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 super::*;
524
525    fn test_empty<T: IntervalTrait>(empty: T) {
526        // Equals itself
527        #[expect(clippy::eq_op)]
528        {
529            assert_eq!(empty, empty);
530        }
531
532        // Empty intersects no values
533        assert!(!empty.intersects_value(0.0));
534        assert!(!empty.intersects_value(f64::INFINITY));
535        assert!(!empty.intersects_value(-f64::INFINITY));
536        assert!(!empty.intersects_value(f64::NAN));
537
538        // Empty intersects no intervals
539        assert!(!empty.intersects_interval(&T::new(-10.0, 10.0)));
540        assert!(!empty.intersects_interval(&T::empty()));
541
542        // ...except the full interval
543        assert!(empty.intersects_interval(&T::full()));
544
545        // Empty contains no intervals
546        assert!(!empty.contains_interval(&T::new(-10.0, 10.0)));
547        assert!(!empty.contains_interval(&T::full()));
548
549        // ...except empty itself (empty set is subset of itself)
550        assert!(empty.contains_interval(&T::empty()));
551
552        // Merging NaN is still empty
553        assert_eq!(empty.merge_value(f64::NAN), empty);
554
555        // Merging an empty interval results in an empty interval
556        assert_eq!(empty.merge_interval(&empty), empty);
557
558        // Merging a value results in a interval with equal lo/hi
559        assert_eq!(empty.merge_value(12.0), T::new(12.0, 12.0));
560
561        // Merging a non-empty interval results in the other interval
562        assert_eq!(
563            empty.merge_interval(&T::new(10.0, 20.0)),
564            T::new(10.0, 20.0)
565        );
566
567        // Expanding empty interval keeps it empty
568        assert_eq!(empty.expand_by(5.0), empty);
569        assert_eq!(empty.expand_by(0.0), empty);
570        assert_eq!(empty.expand_by(-1.0), empty);
571        assert_eq!(empty.expand_by(f64::NAN), empty);
572    }
573
574    #[test]
575    fn interval_empty() {
576        let empty = Interval::empty();
577        test_empty(empty);
578    }
579
580    #[test]
581    fn wraparound_interval_empty() {
582        let empty = WraparoundInterval::empty();
583
584        // Should pass all the regular interval tests
585        test_empty(empty);
586
587        // Empty shouldn't be treated as wraparound
588        assert!(!empty.is_wraparound());
589
590        // When merging an interval where the other one is a
591        // wraparound, we should get the other interval
592        assert_eq!(
593            empty.merge_interval(&WraparoundInterval::new(20.0, 10.0)),
594            WraparoundInterval::new(20.0, 10.0)
595        );
596    }
597
598    fn test_finite<T: IntervalTrait>(finite: T) {
599        // Check accessors
600        assert_eq!(finite.lo(), 10.0);
601        assert_eq!(finite.hi(), 20.0);
602        assert_eq!(finite.mid(), 15.0);
603        assert_eq!(finite.width(), 10.0);
604        assert!(!finite.is_wraparound());
605        assert!(!finite.is_empty());
606
607        // Intersects endpoints and midpoint
608        assert!(finite.intersects_value(10.0));
609        assert!(finite.intersects_value(15.0));
610        assert!(finite.intersects_value(20.0));
611
612        // Doesn't intersect infinite values, NaN, or finite values outside
613        // the range
614        assert!(!finite.intersects_value(0.0));
615        assert!(!finite.intersects_value(f64::INFINITY));
616        assert!(!finite.intersects_value(-f64::INFINITY));
617        assert!(!finite.intersects_value(f64::NAN));
618
619        // Intervals that intersect
620        assert!(finite.intersects_interval(&T::new(14.0, 16.0)));
621        assert!(finite.intersects_interval(&T::new(5.0, 15.0)));
622        assert!(finite.intersects_interval(&T::new(15.0, 25.0)));
623        assert!(finite.intersects_interval(&T::new(5.0, 25.0)));
624        assert!(finite.intersects_interval(&T::full()));
625
626        // Barely touching ones count
627        assert!(finite.intersects_interval(&T::new(5.0, 10.0)));
628        assert!(finite.intersects_interval(&T::new(20.0, 25.0)));
629
630        // Intervals that don't intersect
631        assert!(!finite.intersects_interval(&T::new(0.0, 5.0)));
632        assert!(!finite.intersects_interval(&T::new(25.0, 30.0)));
633        assert!(!finite.intersects_interval(&T::empty()));
634
635        // Intervals that are contained
636        assert!(finite.contains_interval(&T::new(14.0, 16.0)));
637        assert!(finite.contains_interval(&T::new(10.0, 15.0)));
638        assert!(finite.contains_interval(&T::new(15.0, 20.0)));
639        assert!(finite.contains_interval(&T::new(10.0, 20.0))); // itself
640        assert!(finite.contains_interval(&T::empty()));
641
642        // Intervals that are not contained
643        assert!(!finite.contains_interval(&T::new(5.0, 15.0))); // extends below
644        assert!(!finite.contains_interval(&T::new(15.0, 25.0))); // extends above
645        assert!(!finite.contains_interval(&T::new(5.0, 25.0))); // extends both ways
646        assert!(!finite.contains_interval(&T::new(0.0, 5.0))); // completely below
647        assert!(!finite.contains_interval(&T::new(25.0, 30.0))); // completely above
648        assert!(!finite.contains_interval(&T::full())); // full interval is larger
649
650        // Merging NaN
651        assert_eq!(finite.merge_value(f64::NAN), finite);
652
653        // Merging Infinities
654        assert_eq!(
655            finite.merge_value(f64::INFINITY),
656            T::new(finite.lo(), f64::INFINITY)
657        );
658        assert_eq!(
659            finite.merge_value(-f64::INFINITY),
660            T::new(-f64::INFINITY, finite.hi())
661        );
662
663        // Merging a value within the interval
664        assert_eq!(finite.merge_value(15.0), finite);
665
666        // Merging a value above
667        assert_eq!(finite.merge_value(25.0), T::new(10.0, 25.0));
668
669        // Merging a value below
670        assert_eq!(finite.merge_value(5.0), T::new(5.0, 20.0));
671
672        // Merging an empty interval
673        assert_eq!(finite.merge_interval(&T::empty()), finite);
674
675        // Merging an interval with itself
676        assert_eq!(finite.merge_interval(&finite), finite);
677
678        // Merging an interval with the full interval
679        assert_eq!(finite.merge_interval(&T::full()), T::full());
680
681        // Merging an interval within the interval
682        assert_eq!(finite.merge_interval(&T::new(14.0, 16.0)), finite);
683
684        // Merging a partially overlapping interval below
685        assert_eq!(finite.merge_interval(&T::new(5.0, 15.0)), T::new(5.0, 20.0));
686
687        // Merging a partially overlapping interval above
688        assert_eq!(
689            finite.merge_interval(&T::new(15.0, 25.0)),
690            T::new(10.0, 25.0)
691        );
692
693        // Merging a disjoint interval below
694        assert_eq!(finite.merge_interval(&T::new(0.0, 5.0)), T::new(0.0, 20.0));
695
696        // Merging a disjoint interval above
697        assert_eq!(
698            finite.merge_interval(&T::new(25.0, 30.0)),
699            T::new(10.0, 30.0)
700        );
701
702        // Expanding by positive distance
703        assert_eq!(finite.expand_by(2.0), T::new(8.0, 22.0));
704        assert_eq!(finite.expand_by(5.0), T::new(5.0, 25.0));
705
706        // Expanding by zero does nothing
707        assert_eq!(finite.expand_by(0.0), finite);
708
709        // Expanding by negative distance does nothing
710        assert_eq!(finite.expand_by(-1.0), finite);
711
712        // Expanding by NaN does nothing
713        assert_eq!(finite.expand_by(f64::NAN), finite);
714    }
715
716    #[test]
717    fn interval_finite() {
718        let finite = Interval::new(10.0, 20.0);
719        test_finite(finite);
720    }
721
722    #[test]
723    fn wraparound_interval_finite() {
724        let finite = WraparoundInterval::new(10.0, 20.0);
725        test_finite(finite);
726
727        // Convert to an Interval
728        let interval: Interval = finite.try_into().unwrap();
729        assert_eq!(interval, Interval::new(10.0, 20.0));
730    }
731
732    #[test]
733    fn wraparound_interval_actually_wraparound_accessors() {
734        // Everything *except* the interval (10, 20)
735        let wraparound = WraparoundInterval::new(20.0, 10.0);
736        assert!(wraparound.is_wraparound());
737        assert!(!wraparound.is_empty());
738        assert_eq!(wraparound.mid(), f64::INFINITY);
739    }
740
741    #[test]
742    fn wraparound_interval_actually_wraparound_intersects_value() {
743        // Everything *except* the interval (10, 20)
744        let wraparound = WraparoundInterval::new(20.0, 10.0);
745
746        // Intersects endpoints but not a point between them
747        assert!(wraparound.intersects_value(10.0));
748        assert!(wraparound.intersects_value(20.0));
749        assert!(!wraparound.intersects_value(15.0));
750
751        // Intersects positive and negative infinity
752        assert!(wraparound.intersects_value(f64::INFINITY));
753        assert!(wraparound.intersects_value(-f64::INFINITY));
754
755        // ...but not NaN
756        assert!(!wraparound.intersects_value(f64::NAN));
757    }
758
759    #[test]
760    fn wraparound_interval_actually_wraparound_intersects_interval() {
761        // Everything *except* the interval (10, 20)
762        let wraparound = WraparoundInterval::new(20.0, 10.0);
763
764        // Intersects itself
765        assert!(wraparound.intersects_interval(&wraparound));
766
767        // Intersects the full interval
768        assert!(wraparound.intersects_interval(&WraparoundInterval::full()));
769
770        // Interval completely between endpoints doesn't intersect
771        assert!(!wraparound.intersects_interval(&WraparoundInterval::new(14.0, 16.0)));
772        // ...unless it's also wraparound
773        assert!(wraparound.intersects_interval(&WraparoundInterval::new(16.0, 14.0)));
774
775        // Intervals overlapping endpoints intersect whether the are or aren't wraparound
776        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 15.0)));
777        assert!(wraparound.intersects_interval(&WraparoundInterval::new(15.0, 5.0)));
778        assert!(wraparound.intersects_interval(&WraparoundInterval::new(15.0, 25.0)));
779        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 15.0)));
780
781        // Barely touching ones still intersect whether the are or aren't wraparound
782        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 10.0)));
783        assert!(wraparound.intersects_interval(&WraparoundInterval::new(10.0, 5.0)));
784        assert!(wraparound.intersects_interval(&WraparoundInterval::new(20.0, 25.0)));
785        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 20.0)));
786
787        // Intervals completely above and below endpoints do intersect whether they
788        // are or aren't wraparound
789        assert!(wraparound.intersects_interval(&WraparoundInterval::new(0.0, 5.0)));
790        assert!(wraparound.intersects_interval(&WraparoundInterval::new(5.0, 0.0)));
791        assert!(wraparound.intersects_interval(&WraparoundInterval::new(25.0, 30.0)));
792        assert!(wraparound.intersects_interval(&WraparoundInterval::new(30.0, 25.0)));
793    }
794
795    #[test]
796    fn wraparound_interval_actually_wraparound_contains_interval() {
797        // Everything *except* the interval (10, 20)
798        let wraparound = WraparoundInterval::new(20.0, 10.0);
799
800        // Contains itself
801        assert!(wraparound.contains_interval(&wraparound));
802
803        // Empty is contained by everything
804        assert!(wraparound.contains_interval(&WraparoundInterval::empty()));
805
806        // Does not contain the full interval
807        assert!(!wraparound.contains_interval(&WraparoundInterval::full()));
808
809        // Regular interval completely between endpoints is not contained
810        assert!(!wraparound.contains_interval(&WraparoundInterval::new(14.0, 16.0)));
811
812        // Wraparound intervals that exclude more (narrower included regions) are contained
813        assert!(wraparound.contains_interval(&WraparoundInterval::new(22.0, 8.0))); // excludes (8,22) which is larger than (10,20)
814        assert!(!wraparound.contains_interval(&WraparoundInterval::new(18.0, 12.0))); // excludes (12,18) which is smaller than (10,20)
815
816        // Regular intervals don't work the same way due to the split logic
817        // For a regular interval (a, b), split gives (left=(a,b), right=empty)
818        // For wraparound to contain it, we need both parts to be contained
819        // This means (-inf, 10] must contain (a,b) AND [20, inf) must contain empty
820        // The second is always true, but the first requires b <= 10
821        assert!(wraparound.contains_interval(&WraparoundInterval::new(0.0, 5.0))); // completely within left part
822        assert!(wraparound.contains_interval(&WraparoundInterval::new(-5.0, 10.0))); // fits in left part
823        assert!(!wraparound.contains_interval(&WraparoundInterval::new(25.0, 30.0))); // doesn't fit in left part
824        assert!(!wraparound.contains_interval(&WraparoundInterval::new(20.0, 25.0))); // doesn't fit in left part
825
826        // Regular intervals that overlap the excluded zone are not contained
827        assert!(!wraparound.contains_interval(&WraparoundInterval::new(5.0, 15.0))); // overlaps excluded zone
828        assert!(!wraparound.contains_interval(&WraparoundInterval::new(15.0, 25.0))); // overlaps excluded zone
829
830        // Wraparound intervals that exclude less (wider included regions) are not contained
831        assert!(!wraparound.contains_interval(&WraparoundInterval::new(15.0, 5.0))); // excludes (5,15) which is smaller
832        assert!(!wraparound.contains_interval(&WraparoundInterval::new(25.0, 15.0)));
833        // excludes (15,25) which is smaller
834    }
835
836    #[test]
837    fn wraparound_interval_actually_wraparound_merge_value() {
838        // Everything *except* the interval (10, 20)
839        let wraparound = WraparoundInterval::new(20.0, 10.0);
840
841        // Merging NaN
842        assert_eq!(wraparound.merge_value(f64::NAN), wraparound);
843
844        // Merging a value closer to the left endpoint should move
845        // that endpoint
846        assert_eq!(
847            wraparound.merge_value(12.0),
848            WraparoundInterval::new(20.0, 12.0)
849        );
850
851        // Merging a value closer to the right endpoint should move
852        // that endpoint
853        assert_eq!(
854            wraparound.merge_value(18.0),
855            WraparoundInterval::new(18.0, 10.0)
856        );
857
858        // Merging a value that is already intersecting shouldn't change the interval
859        assert_eq!(wraparound.merge_value(5.0), wraparound);
860        assert_eq!(wraparound.merge_value(10.0), wraparound);
861        assert_eq!(wraparound.merge_value(20.0), wraparound);
862        assert_eq!(wraparound.merge_value(25.0), wraparound);
863    }
864
865    #[test]
866    fn wraparound_interval_actually_wraparound_merge_interval() {
867        // Everything *except* the interval (10, 20)
868        let wraparound = WraparoundInterval::new(20.0, 10.0);
869
870        // Merging an empty interval
871        assert_eq!(
872            wraparound.merge_interval(&WraparoundInterval::empty()),
873            wraparound
874        );
875
876        // Merging an interval with itself
877        assert_eq!(wraparound.merge_interval(&wraparound), wraparound);
878
879        // Merging a wraparound interval with a "larger" wraparound interval
880        //           10         20
881        // <==========|          |============>
882        // <==============|  |================>
883        //               14  16
884        assert_eq!(
885            wraparound.merge_interval(&WraparoundInterval::new(16.0, 14.0)),
886            WraparoundInterval::new(16.0, 14.0)
887        );
888
889        // Merging a wraparound interval with a "smaller" wraparound interval
890        //           10         20
891        // <==========|          |============>
892        // <=====|                    |=======>
893        //       5                    25
894        // <==========|          |============>
895        assert_eq!(
896            wraparound.merge_interval(&WraparoundInterval::new(25.0, 5.0)),
897            wraparound
898        );
899
900        // Merge with partially intersecting wraparounds
901        //           10         20
902        // <==========|          |============>
903        // <=====|          |=================>
904        //       5          15
905        // <==========|     |=================>
906        assert_eq!(
907            wraparound.merge_interval(&WraparoundInterval::new(15.0, 5.0)),
908            WraparoundInterval::new(15.0, 10.0)
909        );
910
911        //           10         20
912        // <==========|          |============>
913        // <================|          |======>
914        //                  15         25
915        // <================|    |============>
916        assert_eq!(
917            wraparound.merge_interval(&WraparoundInterval::new(25.0, 15.0)),
918            WraparoundInterval::new(20.0, 15.0)
919        );
920
921        // Merge wraparound with wraparound whose union is the full interval
922        //           10         20
923        // <==========|          |=========================>
924        // <=============================|          |======>
925        //                               25         30
926        // <===============================================>
927        assert_eq!(
928            wraparound.merge_interval(&WraparoundInterval::new(30.0, 25.0)),
929            WraparoundInterval::full()
930        );
931
932        //                    10         20
933        // <===================|          |================>
934        // <==|          |=================================>
935        //    0          5
936        // <===============================================>
937        assert_eq!(
938            wraparound.merge_interval(&WraparoundInterval::new(5.0, 0.0)),
939            WraparoundInterval::full()
940        );
941
942        // Merge wraparound with a regular interval completely contained by the original
943        //                  10         20
944        // <=================|          |==================>
945        //                                   |=========|
946        //                                  25         30
947        // <=================|          |==================>
948        assert_eq!(
949            wraparound.merge_interval(&WraparoundInterval::new(25.0, 30.0)),
950            wraparound
951        );
952
953        //                  10         20
954        // <=================|          |==================>
955        //  |=========|
956        //  0         5
957        // <=================|          |==================>
958        assert_eq!(
959            wraparound.merge_interval(&WraparoundInterval::new(0.0, 5.0)),
960            wraparound
961        );
962
963        // Merge wraparound with a partially intersecting regular interval that
964        // should extend the left side
965        //                  10         20
966        // <=================|          |==================>
967        //              |=========|
968        //              5         15
969        // <======================|     |==================>
970        assert_eq!(
971            wraparound.merge_interval(&WraparoundInterval::new(5.0, 15.0)),
972            WraparoundInterval::new(20.0, 15.0)
973        );
974
975        // Merge wraparound with a partially intersecting regular interval that
976        // should extend the right side
977        //                  10         20
978        // <=================|          |==================>
979        //                         |=========|
980        //                         15        25
981        // <=================|     |==================>
982        assert_eq!(
983            wraparound.merge_interval(&WraparoundInterval::new(15.0, 25.0)),
984            WraparoundInterval::new(15.0, 10.0)
985        );
986
987        // Merge wraparound with a disjoint regular interval that should extend the left side
988        //                  10         20
989        // <=================|          |==================>
990        //                     |==|
991        //                    12  15
992        // <======================|     |==================>
993        assert_eq!(
994            wraparound.merge_interval(&WraparoundInterval::new(12.0, 15.0)),
995            WraparoundInterval::new(20.0, 15.0)
996        );
997
998        // Merge wraparound with a disjoint regular interval that should extend the right side
999        //                  10         20
1000        // <=================|          |==================>
1001        //                         |==|
1002        //                        15  18
1003        // <=================|     |==================>
1004        assert_eq!(
1005            wraparound.merge_interval(&WraparoundInterval::new(15.0, 18.0)),
1006            WraparoundInterval::new(15.0, 10.0)
1007        );
1008    }
1009
1010    #[test]
1011    fn wraparound_interval_actually_wraparound_expand_by() {
1012        // Everything *except* the interval (10, 20)
1013        let wraparound = WraparoundInterval::new(20.0, 10.0);
1014
1015        // Expanding by a small amount shrinks the excluded region
1016        // Original excludes (10, 20), expanding by 2 should exclude (12, 18)
1017        // So the new interval should be (18, 12) = everything except (12, 18)
1018        assert_eq!(
1019            wraparound.expand_by(2.0),
1020            WraparoundInterval::new(18.0, 12.0)
1021        ); // now excludes (12, 18)
1022
1023        // Expanding by 4 should exclude (14, 16)
1024        assert_eq!(
1025            wraparound.expand_by(4.0),
1026            WraparoundInterval::new(16.0, 14.0)
1027        ); // now excludes (14, 16)
1028
1029        // Expanding by 5.0 should exactly eliminate the excluded region
1030        // excluded region (10, 20) shrinks to (15, 15) which is empty
1031        assert_eq!(wraparound.expand_by(5.0), WraparoundInterval::full()); // excluded region disappears
1032
1033        // Any expansion greater than 5.0 should also give full interval
1034        assert_eq!(wraparound.expand_by(6.0), WraparoundInterval::full());
1035
1036        assert_eq!(wraparound.expand_by(100.0), WraparoundInterval::full());
1037
1038        // Expanding by zero does nothing
1039        assert_eq!(wraparound.expand_by(0.0), wraparound);
1040
1041        // Expanding by negative distance does nothing
1042        assert_eq!(wraparound.expand_by(-1.0), wraparound);
1043
1044        // Expanding by NaN does nothing
1045        assert_eq!(wraparound.expand_by(f64::NAN), wraparound);
1046
1047        // Test a finite (non-wraparound) wraparound interval
1048        let non_wraparound = WraparoundInterval::new(10.0, 20.0);
1049        assert!(!non_wraparound.is_wraparound());
1050        assert_eq!(
1051            non_wraparound.expand_by(2.0),
1052            WraparoundInterval::new(8.0, 22.0)
1053        );
1054
1055        // Test another wraparound case - excludes (5, 15) with width 10
1056        let wraparound2 = WraparoundInterval::new(15.0, 5.0);
1057        // Expanding by 3 should shrink excluded region from (5, 15) to (8, 12)
1058        assert_eq!(
1059            wraparound2.expand_by(3.0),
1060            WraparoundInterval::new(12.0, 8.0)
1061        );
1062
1063        // Expanding by 5 should make excluded region disappear: (5+5, 15-5) = (10, 10)
1064        assert_eq!(wraparound2.expand_by(5.0), WraparoundInterval::full());
1065    }
1066
1067    #[test]
1068    fn wraparound_interval_actually_wraparound_convert() {
1069        // Everything *except* the interval (10, 20)
1070        let wraparound = WraparoundInterval::new(20.0, 10.0);
1071
1072        // Can't convert a wraparound interval that actually wraps around to an Interval
1073        let err = Interval::try_from(wraparound).unwrap_err();
1074        assert!(
1075            err.to_string()
1076                .contains("Can't convert wraparound interval")
1077        );
1078    }
1079}