@@ -41,6 +41,7 @@ use crate::selection::EvalOpts;
41
41
use crate :: selection:: Selection ;
42
42
use crate :: selection:: dsl;
43
43
use crate :: shape:: Range ;
44
+ use crate :: view:: Extent ;
44
45
45
46
/// Generates a random [`Slice`] with up to `max_dims` dimensions,
46
47
/// where each dimension has a size between 1 and `max_len`
@@ -76,6 +77,25 @@ pub fn gen_slice(max_dims: usize, max_len: usize) -> impl Strategy<Value = Slice
76
77
prop:: collection:: vec ( 1 ..=max_len, 1 ..=max_dims) . prop_map ( Slice :: new_row_major)
77
78
}
78
79
80
+ /// Generate a random [`Extent`] with `dims` dimensions, where each
81
+ /// size is in `1..=max_len`.
82
+ ///
83
+ /// For example, `gen_extent(1..=4, 1..=8)` generates extents like:
84
+ /// - x=3
85
+ /// - x=2, y=4
86
+ /// - x=2, y=4, z=1, w=5
87
+ pub fn gen_extent (
88
+ dims : std:: ops:: RangeInclusive < usize > ,
89
+ max_len : usize ,
90
+ ) -> impl Strategy < Value = Extent > {
91
+ prop:: collection:: vec ( 1 ..=max_len, dims) . prop_map ( |sizes| {
92
+ let labels = ( 0 ..sizes. len ( ) )
93
+ . map ( |i| format ! ( "d/{}" , i) )
94
+ . collect :: < Vec < _ > > ( ) ;
95
+ Extent :: new ( labels, sizes) . unwrap ( )
96
+ } )
97
+ }
98
+
79
99
/// Generates a pair `(base, subview)` where:
80
100
/// - `base` is a randomly shaped row-major `Slice`,
81
101
/// - `subview` is a valid rectangular region within `base`.
@@ -620,4 +640,91 @@ mod tests {
620
640
}
621
641
}
622
642
}
643
+
644
+ // Coordinate–Rank Isomorphism for Extents
645
+
646
+ // Theorem 1: Rank is injective on valid points
647
+ //
648
+ // For a given Extent, every distinct coordinate (i.e. Point)
649
+ // maps to a unique rank.
650
+ //
651
+ // ∀ p ≠ q ∈ extent.iter(), p.rank() ≠ q.rank()
652
+ proptest ! {
653
+ #[ test]
654
+ fn rank_is_injective( extent in gen_extent( 1 ..=4 , 8 ) ) {
655
+ let mut seen = HashSet :: new( ) ;
656
+ for point in extent. iter( ) {
657
+ let rank = point. rank( ) ;
658
+ prop_assert!(
659
+ seen. insert( rank) ,
660
+ "Duplicate rank {} for point {}" ,
661
+ rank,
662
+ point
663
+ ) ;
664
+ }
665
+ }
666
+ }
667
+
668
+ // Theorem 2: Row-major monotonicity
669
+ //
670
+ // The rank function is monotonic in lexicographic (row-major)
671
+ // coordinate order.
672
+ //
673
+ // ∀ p, q ∈ ℕᵈ, p ≺ q ⇒ rank(p) < rank(q)
674
+ proptest ! {
675
+ #[ test]
676
+ fn rank_is_monotonic( extent in gen_extent( 1 ..=4 , 8 ) ) {
677
+ let mut last_rank = None ;
678
+ for point in extent. iter( ) {
679
+ let rank = point. rank( ) ;
680
+ if let Some ( prev) = last_rank {
681
+ prop_assert!( prev < rank, "Rank not monotonic: {} >= {}" , prev, rank) ;
682
+ }
683
+ last_rank = Some ( rank) ;
684
+ }
685
+ }
686
+ }
687
+
688
+ // Theorem 3: Rank bounds
689
+ //
690
+ // For any point p in extent E, the rank of p is in the range:
691
+ // 0 ≤ rank(p) < E.len()
692
+ //
693
+ // ∀ p ∈ E, 0 ≤ rank(p) < |E|
694
+ proptest ! {
695
+ #[ test]
696
+ fn rank_bounds( extent in gen_extent( 1 ..=4 , 8 ) ) {
697
+ let len = extent. len( ) ;
698
+ for point in extent. iter( ) {
699
+ let rank = point. rank( ) ;
700
+ prop_assert!( rank < len, "Rank {} out of bounds for extent of size {}" , rank, len) ;
701
+ }
702
+ }
703
+ }
704
+
705
+ // Theorem 4: Isomorphism (Rank-point round-trip is identity on
706
+ // all ranks)
707
+ //
708
+ // For every valid rank ∈ [0, extent.len()), converting it to a
709
+ // point and back gives the same rank:
710
+ //
711
+ // rank(point_of_rank(r)) = r
712
+ //
713
+ // In categorical terms: point_of_rank ∘ rank = 𝟙ₙ
714
+ proptest ! {
715
+ #[ test]
716
+ fn rank_point_trip( extent in gen_extent( 1 ..=4 , 8 ) ) {
717
+ for r in 0 ..extent. len( ) {
718
+ let point = extent. point_of_rank( r) . unwrap( ) ;
719
+ prop_assert_eq!(
720
+ point. rank( ) ,
721
+ r,
722
+ "point_of_rank({}) returned {}, which maps to rank {}" ,
723
+ r,
724
+ point,
725
+ point. rank( )
726
+ ) ;
727
+ }
728
+ }
729
+ }
623
730
}
0 commit comments