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(¬_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 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}