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(¬_wraparound.inner), right)
446 } else {
447 (left, right.merge_interval(¬_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}