ndslice/
strategy.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Property-based generators for [`Selection`] and related types.
10//!
11//! These strategies are used in `proptest`-based tests to construct
12//! randomized selection expressions for testing evaluation, routing,
13//! and normalization logic.
14//!
15//! The main entry point is [`gen_selection(depth)`], which generates
16//! a structurally diverse [`Selection`] of bounded depth, supporting
17//! the `True`, `Range`, `All`, `Union`, and `Intersection`
18//! constructors.
19//!
20//! Example usage:
21//!
22//! ```
23//! use ndslice::strategy::gen_selection;
24//! use proptest::prelude::*;
25//!
26//! proptest! {
27//!     #[test]
28//!     fn test_selection(s in gen_selection(3)) {
29//!         // Use `s` as input to evaluation or routing tests
30//!     }
31//! }
32//! ```
33
34use proptest::prelude::*;
35
36use crate::Slice;
37use crate::selection::EvalOpts;
38use crate::selection::Selection;
39use crate::selection::dsl;
40use crate::shape::Range;
41use crate::view::Extent;
42use crate::view::Region;
43
44/// Generates a random [`Slice`] with up to `max_dims` dimensions,
45/// where each dimension has a size between 1 and `max_len`
46/// (inclusive).
47///
48/// The slice is constructed using standard row-major layout with no
49/// offset, making it suitable for use in evaluation, routing, and
50/// normalization tests.
51///
52/// This generator is used in property-based tests to provide diverse
53/// input shapes for selection and routing logic.
54///
55/// # Parameters
56///
57/// - `max_dims`: Maximum number of dimensions in the generated slice.
58/// - `max_len`: Maximum size per dimension.
59///
60/// # Example
61///
62/// ```
63/// use ndslice::strategy::gen_slice;
64/// use proptest::prelude::*;
65///
66/// proptest! {
67///     fn test_slice_generation(slice in gen_slice(4, 8)) {
68///         assert!(!slice.sizes().is_empty());
69///     }
70/// }
71/// ```
72pub fn gen_slice(max_dims: usize, max_len: usize) -> impl Strategy<Value = Slice> {
73    prop::collection::vec(1..=max_len, 1..=max_dims).prop_map(Slice::new_row_major)
74}
75
76/// Generate a random [`Extent`] with `dims` dimensions, where each
77/// size is in `1..=max_len`.
78///
79/// For example, `gen_extent(1..=4, 1..=8)` generates extents like:
80/// - x=3
81/// - x=2, y=4
82/// - x=2, y=4, z=1, w=5
83pub fn gen_extent(
84    dims: std::ops::RangeInclusive<usize>,
85    max_len: usize,
86) -> impl Strategy<Value = Extent> {
87    prop::collection::vec(1..=max_len, dims).prop_map(|sizes| {
88        let labels = (0..sizes.len())
89            .map(|i| format!("d/{}", i))
90            .collect::<Vec<_>>();
91        Extent::new(labels, sizes).unwrap()
92    })
93}
94
95/// Generate a random [`Region`] strategy for property tests.
96///
97/// This builds on [`gen_extent`], producing a region with the same
98/// randomly chosen dimensionality and sizes, but wrapped as a full
99/// [`Region`] (with labels and strides).
100///
101/// - `dims`: inclusive range of allowed dimensionalities (e.g.
102///   `1..=4`)
103/// - `max_len`: maximum size of any dimension
104pub fn gen_region(
105    dims: std::ops::RangeInclusive<usize>,
106    max_len: usize,
107) -> impl proptest::strategy::Strategy<Value = Region> {
108    gen_extent(dims, max_len).prop_map(Into::into)
109}
110
111/// Generate a random [`Region`] strategy with striding for property
112/// tests.
113///
114/// Similar to [`gen_region`], but each dimension may additionally use
115/// a non-unit step. This produces regions whose underlying slice has
116/// non-contiguous strides, useful for testing strided layouts.
117///
118/// - `dims`: inclusive range of allowed dimensionalities (e.g.
119///   `1..=4`)
120/// - `max_len`: maximum size of any dimension
121/// - `max_step`: maximum stride step size applied to each dimension
122/// - `_max_offset`: reserved for future use (currently ignored)
123pub fn gen_region_strided(
124    dims: std::ops::RangeInclusive<usize>,
125    max_len: usize,
126    max_step: usize,
127    _max_offset: usize,
128) -> impl Strategy<Value = Region> {
129    use crate::view::ViewExt;
130
131    prop::collection::vec(1..=max_len, dims)
132        .prop_flat_map(move |sizes| {
133            let n = sizes.len();
134            let labels: Vec<String> = (0..n).map(|i| format!("d/{}", i)).collect();
135
136            let steps_raw = prop::collection::vec(1..=max_step.max(1), n);
137            let begins_unclamped = prop::collection::vec(proptest::num::usize::ANY, n);
138
139            (Just((labels, sizes)), steps_raw, begins_unclamped)
140        })
141        .prop_map(move |((labels, sizes), steps_raw, begins_unclamped)| {
142            // 1) Make steps obey the divisibility chain: step[i] %
143            // step[i+1] == 0
144            let mut steps = steps_raw;
145            if !steps.is_empty() {
146                // innermost is free
147                let last = steps.len() - 1;
148                steps[last] = steps[last].max(1).min(max_step.max(1));
149                // Each outer step is an integer multiple of the next
150                // inner step.
151                for i in (0..last).rev() {
152                    let inner = steps[i + 1].max(1);
153                    let max_mult = (max_step / inner).max(1);
154                    // Clamp current to be a multiple of `inner`
155                    // within [inner, max_step].
156                    let m = ((steps[i].max(1) - 1) % max_mult) + 1;
157                    steps[i] = inner.saturating_mul(m);
158                }
159            }
160
161            // 2) Build from a row-major region and compose per-axis
162            // ranges
163            let mut region: Region = Extent::new(labels.clone(), sizes.clone()).unwrap().into();
164            for i in 0..sizes.len() {
165                let begin = begins_unclamped[i] % sizes[i]; // in [0, size-1]
166                let step = steps[i].max(1);
167                region = region
168                    .range(&labels[i], Range(begin, None, step))
169                    .expect("range stayed rectangular");
170            }
171            region
172        })
173}
174
175/// Generates a pair `(base, subview)` where:
176/// - `base` is a randomly shaped row-major `Slice`,
177/// - `subview` is a valid rectangular region within `base`.
178pub fn gen_slice_and_subview(
179    max_dims: usize,
180    max_len: usize,
181) -> impl Strategy<Value = (Slice, Slice)> {
182    assert!(max_dims <= 8, "Supports up to 4 dimensions explicitly");
183
184    gen_slice(max_dims, max_len).prop_flat_map(|base| {
185        let sizes = base.sizes().to_vec();
186
187        // Strategy per dimension
188        let dim_strat = |extent| {
189            if extent == 0 {
190                Just((0, 0)).boxed()
191            } else {
192                (0..extent)
193                    .prop_flat_map(move |start| {
194                        (1..=extent - start).prop_map(move |len| (start, len))
195                    })
196                    .boxed()
197            }
198        };
199
200        // Explicit match based on dims
201        match sizes.len() {
202            1 => (dim_strat(sizes[0]),).prop_map(|(a,)| vec![a]).boxed(),
203            2 => (dim_strat(sizes[0]), dim_strat(sizes[1]))
204                .prop_map(|(a, b)| vec![a, b])
205                .boxed(),
206            3 => (
207                dim_strat(sizes[0]),
208                dim_strat(sizes[1]),
209                dim_strat(sizes[2]),
210            )
211                .prop_map(|(a, b, c)| vec![a, b, c])
212                .boxed(),
213            4 => (
214                dim_strat(sizes[0]),
215                dim_strat(sizes[1]),
216                dim_strat(sizes[2]),
217                dim_strat(sizes[3]),
218            )
219                .prop_map(|(a, b, c, d)| vec![a, b, c, d])
220                .boxed(),
221            5 => (
222                dim_strat(sizes[0]),
223                dim_strat(sizes[1]),
224                dim_strat(sizes[2]),
225                dim_strat(sizes[3]),
226                dim_strat(sizes[4]),
227            )
228                .prop_map(|(a, b, c, d, e)| vec![a, b, c, d, e])
229                .boxed(),
230            6 => (
231                dim_strat(sizes[0]),
232                dim_strat(sizes[1]),
233                dim_strat(sizes[2]),
234                dim_strat(sizes[3]),
235                dim_strat(sizes[4]),
236                dim_strat(sizes[5]),
237            )
238                .prop_map(|(a, b, c, d, e, f)| vec![a, b, c, d, e, f])
239                .boxed(),
240            7 => (
241                dim_strat(sizes[0]),
242                dim_strat(sizes[1]),
243                dim_strat(sizes[2]),
244                dim_strat(sizes[3]),
245                dim_strat(sizes[4]),
246                dim_strat(sizes[5]),
247                dim_strat(sizes[6]),
248            )
249                .prop_map(|(a, b, c, d, e, f, g)| vec![a, b, c, d, e, f, g])
250                .boxed(),
251            8 => (
252                dim_strat(sizes[0]),
253                dim_strat(sizes[1]),
254                dim_strat(sizes[2]),
255                dim_strat(sizes[3]),
256                dim_strat(sizes[4]),
257                dim_strat(sizes[5]),
258                dim_strat(sizes[6]),
259                dim_strat(sizes[7]),
260            )
261                .prop_map(|(a, b, c, d, e, f, g, h)| vec![a, b, c, d, e, f, g, h])
262                .boxed(),
263            _ => unreachable!("max_dims constrained to 8"),
264        }
265        .prop_map(move |ranges| {
266            let (starts, lens): (Vec<_>, Vec<_>) = ranges.into_iter().unzip();
267            let subview = base.subview(&starts, &lens).expect("valid subview");
268            (base.clone(), subview)
269        })
270    })
271}
272
273/// Recursively generates a random `Selection` expression of bounded
274/// depth, aligned with the given slice `shape`.
275///
276/// Each recursive call corresponds to one dimension of the shape,
277/// starting from `dim`, and constructs a selection operator (`range`,
278/// `all`, `intersection`, etc.) that applies at that level.
279///
280/// The recursion proceeds until either:
281/// - `depth == 0`, which limits structural complexity, or
282/// - `dim >= shape.len()`, which prevents exceeding the
283///   dimensionality.
284///
285/// In both cases, the recursion terminates with a `true_()` leaf
286/// node, effectively selecting all remaining elements.
287///
288/// The resulting selections are guaranteed to be valid under a strict
289/// validation regime: they contain no empty ranges, no out-of-bounds
290/// accesses, and no dynamic constructs like `any` or `first`.
291pub fn gen_selection(depth: u32, shape: Vec<usize>, dim: usize) -> BoxedStrategy<Selection> {
292    let leaf = Just(dsl::true_()).boxed();
293
294    if depth == 0 || dim >= shape.len() {
295        return leaf;
296    }
297
298    let recur = {
299        let shape = shape.clone();
300        move || gen_selection(depth - 1, shape.clone(), dim + 1)
301    };
302
303    let range_strategy = {
304        let dim_size = shape[dim];
305        (0..dim_size)
306            .prop_flat_map(move |start| {
307                let max_len = dim_size - start;
308                (1..=max_len).prop_flat_map(move |len| {
309                    (1..=len).prop_map(move |step| {
310                        let r = Range(start, Some(start + len), step);
311                        dsl::range(r, dsl::true_())
312                    })
313                })
314            })
315            .boxed()
316    };
317
318    let all = recur().prop_map(dsl::all).boxed();
319
320    let union = (recur(), recur())
321        .prop_map(|(a, b)| dsl::union(a, b))
322        .boxed();
323
324    let inter = (recur(), recur())
325        .prop_map(|(a, b)| dsl::intersection(a, b))
326        .boxed();
327
328    prop_oneof![
329        2 => leaf,
330        3 => range_strategy,
331        3 => all,
332        2 => union,
333        2 => inter,
334    ]
335    .prop_filter("valid selection", move |s| {
336        let slice = Slice::new_row_major(shape.clone());
337        let eval_opts = EvalOpts {
338            disallow_dynamic_selections: true,
339            ..EvalOpts::strict()
340        };
341        s.validate(&eval_opts, &slice).is_ok()
342    })
343    .boxed()
344}
345
346#[cfg(test)]
347mod tests {
348    use std::collections::HashMap;
349    use std::collections::HashSet;
350
351    use proptest::strategy::ValueTree;
352    use proptest::test_runner::Config;
353    use proptest::test_runner::TestRunner;
354
355    use super::*;
356    use crate::selection::EvalOpts;
357    use crate::selection::routing::RoutingFrame;
358    use crate::selection::test_utils::collect_commactor_routing_tree;
359    use crate::selection::test_utils::collect_routed_paths;
360
361    #[test]
362    fn print_some_slices() {
363        let mut runner = TestRunner::new(Config::default());
364
365        for _ in 0..256 {
366            let strat = gen_slice(4, 8); // up to 4 dimensions, max size per dim = 8
367            let value = strat.new_tree(&mut runner).unwrap().current();
368            println!("{:?}", value);
369        }
370    }
371
372    proptest! {
373        #[test]
374        fn test_slice_properties(slice in gen_slice(4, 8)) {
375            let total_size: usize = slice.sizes().iter().product();
376            prop_assert!(total_size > 0);
377        }
378    }
379
380    #[test]
381    fn print_some_selections() {
382        let mut runner = TestRunner::new(Config::default());
383
384        for _ in 0..256 {
385            let strat = gen_selection(3, vec![2, 4, 8], 0);
386            let value = strat.new_tree(&mut runner).unwrap().current();
387            println!("{:?}", value);
388        }
389    }
390
391    // Test `trace_route` exhibits path determinism.
392    //
393    // This test instantiates a general theorem about the selection
394    // algebra and its routing semantics:
395    //
396    //   ∀ `S`, `T`, and `n`,
397    //     `n ∈ eval(S, slice)` ∧ `n ∈ eval(T, slice)` ⇒
398    //     `route(n, S) == route(n, T)`.
399    //
400    // This property enables us to enforce in-order delivery using
401    // only per-sender, per-peer sequence numbers. Since every message
402    // to a given destination follows the same deterministic path
403    // through the mesh, intermediate nodes can forward messages in
404    // order, and receivers can detect missing or out-of-order
405    // messages using only local state.
406    //
407    // This test uses `trace_route` to observe the path to each
408    // overlapping destination node under `S` and `T`, asserting that
409    // the results agree.
410    proptest! {
411        #![proptest_config(ProptestConfig {
412            cases: 8, ..ProptestConfig::default()
413        })]
414        #[test]
415        fn trace_route_path_determinism(
416            slice in gen_slice(4, 8)
417        ) {
418            let shape = slice.sizes().to_vec();
419
420            let mut runner = TestRunner::default();
421            let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
422            let t = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
423
424            let eval_opts = EvalOpts::strict();
425            let sel_s: HashSet<_> = s.eval(&eval_opts, &slice).unwrap().collect();
426            let sel_t: HashSet<_> = t.eval(&eval_opts, &slice).unwrap().collect();
427            let ranks: Vec<_> = sel_s.intersection(&sel_t).cloned().collect();
428
429            if ranks.is_empty() {
430                println!("skipping empty intersection");
431            } else {
432                println!("testing {} nodes", ranks.len());
433                for rank in ranks {
434                    let coords = slice.coordinates(rank).unwrap();
435                    let start_s = RoutingFrame::root(s.clone(), slice.clone());
436                    let start_t = RoutingFrame::root(t.clone(), slice.clone());
437                    let path_s = start_s.trace_route(&coords).unwrap();
438                    let path_t = start_t.trace_route(&coords).unwrap();
439                    prop_assert_eq!(
440                        path_s.clone(),
441                        path_t.clone(),
442                        "path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
443                        rank, path_s, path_t
444                    );
445                }
446            }
447        }
448    }
449
450    // Test `collect_routed_paths` exhibits path determinism.
451    //
452    // This test instantiates the same fundamental property as the
453    // `trace_route` test, but does so using `collect_routed_paths`,
454    // which performs a breadth-first traversal of the routing tree.
455    //
456    //   ∀ `S`, `T`, and `n`,
457    //     `n ∈ eval(S, slice)` ∧ `n ∈ eval(T, slice)` ⇒
458    //     `route(n, S) == route(n, T)`.
459    //
460    // The property guarantees that every destination node reachable
461    // by both `S` and `T` is routed to via the same deterministic
462    // path.
463    //
464    // This test avoids calls to `eval` by intersecting the routed
465    // destinations directly. For each rank routed to by both
466    // selections, it compares the path returned by
467    // `collect_routed_paths`, ensuring the selection algebra routes
468    // consistently regardless of expression structure or traversal
469    // order.
470    proptest! {
471        #![proptest_config(ProptestConfig {
472            cases: 128, ..ProptestConfig::default()
473        })]
474        #[test]
475        fn collect_routed_path_determinism(
476            slice in gen_slice(4, 8)
477        ) {
478            let shape = slice.sizes().to_vec();
479
480            let mut runner = TestRunner::default();
481            let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
482            let t = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
483
484            let paths_s = collect_routed_paths(&s, &slice);
485            let paths_t = collect_routed_paths(&t, &slice);
486            let ranks: Vec<_> = paths_s.delivered.keys()
487                .filter(|r| paths_t.delivered.contains_key(*r))
488                .cloned()
489                .collect();
490
491            if ranks.is_empty() {
492                println!("skipping empty intersection");
493            } else {
494                println!("testing {} nodes", ranks.len());
495                for rank in ranks {
496                    let path_s = paths_s.delivered.get(&rank).unwrap();
497                    let path_t = paths_t.delivered.get(&rank).unwrap();
498                    prop_assert_eq!(
499                        path_s.clone(),
500                        path_t.clone(),
501                        "path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
502                        rank, path_s, path_t
503                    );
504                }
505            }
506        }
507    }
508
509    // Test `collect_commactor_routing_tree` exhibits path
510    // determinism.
511    //
512    // This test instantiates the same routing path determinism
513    // property as in `collect_routed_path_determinism`, but uses the
514    // full CommActor-style routing simulation instead.
515    //
516    //   ∀ `S`, `T`, and `n`,
517    //     `n ∈ eval(S, slice)` ∧ `n ∈ eval(T, slice)` ⇒
518    //     `route(n, S) == route(n, T)`.
519    //
520    // This ensures that every destination rank reachable by both `S`
521    // and `T` receives its message along the same logical path, even
522    // when selection expressions differ structurally.
523    //
524    // The test avoids explicit calls to eval by intersecting the
525    // delivered ranks from both traversals. For each rank delivered
526    // to by both selections, it compares the delivery path recorded
527    // in `CommActorRoutingTree::delivered`. This validates that
528    // CommActor message routing is structurally deterministic.
529    proptest! {
530        #![proptest_config(ProptestConfig {
531            cases: 128, ..ProptestConfig::default()
532        })]
533        #[test]
534        fn collect_commactor_routed_path_determinism(
535            slice in gen_slice(4, 8)
536        ) {
537            let extents = slice.sizes().to_vec();
538
539            let mut runner = TestRunner::default();
540            let s = gen_selection(4, extents.clone(), 0).new_tree(&mut runner).unwrap().current();
541            let t = gen_selection(4, extents.clone(), 0).new_tree(&mut runner).unwrap().current();
542
543            let tree_s = collect_commactor_routing_tree(&s, &slice);
544            let tree_t = collect_commactor_routing_tree(&t, &slice);
545
546            let ranks: Vec<_> = tree_s
547                .delivered
548                .keys()
549                .filter(|r| tree_t.delivered.contains_key(*r))
550                .cloned()
551                .collect();
552
553            if ranks.is_empty() {
554                println!("skipping empty intersection");
555            } else {
556                println!("testing {} nodes", ranks.len());
557                for rank in ranks {
558                    let path_s = &tree_s.delivered[&rank];
559                    let path_t = &tree_t.delivered[&rank];
560                    prop_assert_eq!(
561                        path_s.clone(),
562                        path_t.clone(),
563                        "path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
564                        rank, path_s, path_t
565                    );
566                }
567            }
568        }
569    }
570
571    // Property test: Unique Predecessor Theorem
572    //
573    // This test verifies a structural invariant of the routing graph
574    // produced by `collect_routed_paths`, which performs a
575    // breadth-first traversal of a selection over a multidimensional
576    // mesh.
577    //
578    // ───────────────────────────────────────────────────────────────
579    // Unique Predecessor Theorem
580    //
581    // In a full routing traversal, each coordinate `x` is the target
582    // of at most one `RoutingStep::Forward` from a distinct
583    // coordinate `y ≠ x`.
584    //
585    // Any additional frames that reach `x` arise only from:
586    //   - self-forwarding (i.e., `x → x`)
587    //   - structural duplication from the same parent node (e.g., via
588    //     unions)
589    //
590    // This ensures that routing paths form a tree-like structure
591    // rooted at the origin, with no multiple distinct predecessors
592    // except in the degenerate (self-loop) or duplicated-parent
593    // cases.
594    proptest! {
595        #![proptest_config(ProptestConfig {
596            cases: 256, ..ProptestConfig::default()
597        })]
598        #[test]
599        fn collect_routed_paths_unique_predecessor(
600            slice in gen_slice(4, 8)
601        ) {
602            let shape = slice.sizes().to_vec();
603
604            let mut runner = TestRunner::default();
605            let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
606
607            let tree = collect_routed_paths(&s, &slice);
608
609            for (node, preds) in tree.predecessors {
610                let non_self_preds: Vec<_> = preds.clone().into_iter()
611                    .filter(|&p| p != node)
612                    .collect();
613
614                prop_assert!(
615                    non_self_preds.len() <= 1,
616                    "Node {} had multiple non-self predecessors: {:?} (selection: {})",
617                    node,
618                    non_self_preds,
619                    s,
620                );
621            }
622        }
623    }
624
625    // Property test: Unique Predecessor Theorem (CommActor Routing)
626    //
627    // This test verifies structural invariants of the routing graph
628    // produced by `collect_commactor_routing_tree`, which simulates
629    // CommActor-style peer-to-peer multicast forwarding.
630    //
631    // ───────────────────────────────────────────────────────────────
632    // Unique Predecessor Theorem
633    //
634    // In a full routing traversal, each coordinate `x` is the target
635    // of at most one `RoutingStep::Forward` from a distinct
636    // coordinate `y ≠ x`.
637    //
638    // Any additional frames that reach `x` arise only from:
639    //   - structural duplication from the same parent node (e.g., via
640    //     unions)
641    //
642    // Unlike the general `collect_routed_paths`, CommActor routing
643    // never performs self-forwarding (`x → x`). This test confirms
644    // that as well.
645    proptest! {
646        #![proptest_config(ProptestConfig {
647            cases: 256, ..ProptestConfig::default()
648        })]
649        #[test]
650        fn commactor_routed_paths_unique_predecessor(
651            slice in gen_slice(4, 8)
652        ) {
653            let shape = slice.sizes().to_vec();
654
655            let mut runner = TestRunner::default();
656            let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
657
658            let tree = collect_commactor_routing_tree(&s, &slice);
659
660            let mut preds: HashMap<usize, HashSet<usize>> = HashMap::new();
661
662            for (from, frames) in &tree.forwards {
663                for frame in frames {
664                    let to = slice.location(&frame.here).unwrap();
665
666                    // We assert that a CommActor never forwards to
667                    // itself.
668                    prop_assert_ne!(
669                        *from, to,
670                        "CommActor forwarded to itself: {} → {} (selection: {})",
671                        from, to, s
672                    );
673
674                    preds.entry(to).or_default().insert(*from);
675                }
676            }
677
678            for (node, parents) in preds {
679                let non_self_preds: Vec<_> = parents.into_iter()
680                    .filter(|&p| p != node)
681                    .collect();
682
683                prop_assert!(
684                    non_self_preds.len() <= 1,
685                    "Node {} had multiple non-self predecessors: {:?} (selection: {})",
686                    node,
687                    non_self_preds,
688                    s,
689                );
690            }
691        }
692    }
693
694    // Theorem (Subview-Coordinate Inclusion):
695    //
696    // For any rectangular subview `V` of a base slice `B`, each
697    // coordinate `v` in `V` maps to a coordinate `b = view_offset +
698    // v` that must be in `B`.
699    //
700    // This test verifies that all coordinates of a generated subview
701    // are valid when translated back into the coordinate system of
702    // the base slice.
703    proptest! {
704        #[test]
705        fn test_gen_slice_and_subview((base, subview) in gen_slice_and_subview(4, 8)) {
706            for idx in subview.iter() {
707                let v = subview.coordinates(idx).unwrap();
708                let view_offset_in_base = base.coordinates(subview.offset()).unwrap();
709
710                // b = view_offset + v
711                let b: Vec<_> = v.iter()
712                    .zip(&view_offset_in_base)
713                    .map(|(sub_c, offset)| sub_c + offset)
714                    .collect();
715
716                assert!(base.location(&b).is_ok());
717            }
718        }
719    }
720
721    // Coordinate–Rank Isomorphism for Extents
722
723    // Theorem 1: Rank is injective on valid points
724    //
725    // For a given Extent, every distinct coordinate (i.e. Point)
726    // maps to a unique rank.
727    //
728    // ∀ p ≠ q ∈ extent.iter(),  p.rank() ≠ q.rank()
729    proptest! {
730      #[test]
731      fn rank_is_injective(extent in gen_extent(1..=4, 8)) {
732        let mut seen = HashSet::new();
733        for point in extent.points() {
734          let rank = point.rank();
735          prop_assert!(
736            seen.insert(rank),
737            "Duplicate rank {} for point {}",
738            rank,
739            point
740          );
741        }
742      }
743    }
744
745    // Theorem 2: Row-major monotonicity
746    //
747    // The rank function is monotonic in lexicographic (row-major)
748    // coordinate order.
749    //
750    // ∀ p, q ∈ ℕᵈ, p ≺ q ⇒ rank(p) < rank(q)
751    proptest! {
752      #[test]
753      fn rank_is_monotonic(extent in gen_extent(1..=4, 8)) {
754        let mut last_rank = None;
755        for point in extent.points() {
756          let rank = point.rank();
757          if let Some(prev) = last_rank {
758            prop_assert!(prev < rank, "Rank not monotonic: {} >= {}", prev, rank);
759          }
760          last_rank = Some(rank);
761        }
762      }
763    }
764
765    // Theorem 3: Rank bounds
766    //
767    // For any point p in extent E, the rank of p is in the range:
768    //     0 ≤ rank(p) < E.len()
769    //
770    // ∀ p ∈ E, 0 ≤ rank(p) < |E|
771    proptest! {
772      #[test]
773      fn rank_bounds(extent in gen_extent(1..=4, 8)) {
774        let len = extent.num_ranks();
775        for point in extent.points() {
776          let rank = point.rank();
777          prop_assert!(rank < len, "Rank {} out of bounds for extent of size {}", rank, len);
778        }
779      }
780    }
781
782    // Theorem 4: Isomorphism (Rank-point round-trip is identity on
783    // all ranks)
784    //
785    // For every valid rank r ∈ [0, extent.len()), converting it to a
786    // point and back gives the same rank:
787    //
788    //     rank(point_of_rank(r)) = r
789    //
790    // In categorical terms: rank ∘ point_of_rank = 𝟙
791    proptest! {
792        #[test]
793        fn rank_point_trip(extent in gen_extent(1..=4, 8)) {
794            for r in 0..extent.num_ranks() {
795                let point = extent.point_of_rank(r).unwrap();
796                prop_assert_eq!(
797                    point.rank(),
798                    r,
799                    "point_of_rank({}) returned {}, which maps to rank {}",
800                    r,
801                    point,
802                    point.rank()
803                );
804            }
805        }
806    }
807
808    // Theorem 5: Isomorphism (Point–Coords–Rank round-trip is
809    // identity on all points)
810    //
811    // For every point p ∈ extent.points(), converting its coordinates
812    // back to a rank yields the same rank:
813    //
814    //     rank_of_coords(coords(p)) = rank(p)
815    //
816    // In categorical terms: rank_of_coords ∘ coords = rank
817    proptest! {
818        #[test]
819        fn coords_to_rank_roundtrip(extent in gen_extent(0..=4, 8)) {
820            for p in extent.points() {
821                let c = p.coords();
822                let r = extent.rank_of_coords(&c).expect("coords from Point must be valid");
823                prop_assert_eq!(r, p.rank(), "coords->rank mismatch for {}", p);
824            }
825        }
826    }
827}