Skip to content

Commit 35503b8

Browse files
view: coordinate-rank bijection tests on extents (#737)
Summary: coordinate–rank isomorphism tests Pull Request resolved: #737 Reviewed By: mariusae Differential Revision: D79495963 Pulled By: shayne-fletcher fbshipit-source-id: 4b00f232ee3cff61825194199804a5b0333d3000
1 parent ff6bd79 commit 35503b8

File tree

2 files changed

+113
-6
lines changed

2 files changed

+113
-6
lines changed

ndslice/src/slice.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -401,21 +401,21 @@ impl Slice {
401401
/// multidimensional point in a row-major flattened array,
402402
/// assuming dense gapless storage with zero offset:
403403
///
404-
/// ```lang=text
404+
/// ```plain
405405
/// index := Σ(coordinate[i] × ∏(sizes[j] for j > i))
406406
/// ```
407407
///
408408
/// For example, given a 3x2 row-major base array B:
409409
///
410-
/// ```lang=text
410+
/// ```plain
411411
/// 0 1 2 1
412412
/// B = 3 4 5 V = 4
413413
/// 6 7 8 7
414414
/// ```
415415
///
416416
/// Let V be the first column of B. Then,
417417
///
418-
/// ```lang=text
418+
/// ```plain
419419
/// V | loc | index
420420
/// -------+-------+------
421421
/// (0, 0) | 1 | 0
@@ -428,17 +428,17 @@ impl Slice {
428428
/// The physical offset formula computes the memory location of a
429429
/// point `p` as:
430430
///
431-
/// ```lang=text
431+
/// ```plain
432432
/// loc := offset + Σ(coordinate[i] × stride[i])
433433
/// ```
434434
///
435435
/// Let the layout be dense row-major and offset = 0.
436436
/// Then,
437-
/// ```lang=text
437+
/// ```plain
438438
/// stride[i] := ∏(sizes[j] for j > i).
439439
/// ```
440440
/// and substituting into the physical offset formula:
441-
/// ```lang=text
441+
/// ```plain
442442
/// loc = Σ(coordinate[i] × stride[i])
443443
/// = Σ(coordinate[i] × ∏(sizes[j] for j > i))
444444
/// = index.

ndslice/src/strategy.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use crate::selection::EvalOpts;
4141
use crate::selection::Selection;
4242
use crate::selection::dsl;
4343
use crate::shape::Range;
44+
use crate::view::Extent;
4445

4546
/// Generates a random [`Slice`] with up to `max_dims` dimensions,
4647
/// 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
7677
prop::collection::vec(1..=max_len, 1..=max_dims).prop_map(Slice::new_row_major)
7778
}
7879

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+
7999
/// Generates a pair `(base, subview)` where:
80100
/// - `base` is a randomly shaped row-major `Slice`,
81101
/// - `subview` is a valid rectangular region within `base`.
@@ -620,4 +640,91 @@ mod tests {
620640
}
621641
}
622642
}
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+
}
623730
}

0 commit comments

Comments
 (0)