1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Slicing on dynamic tensors (RFC-102).
//!
//! Four of these tests exist specifically because the corresponding bug is
//! invisible to a value-only assertion: a slice that copies instead of
//! sharing, or nests views instead of composing, produces plausible-looking
//! output. Storage identity (`Arc::ptr_eq`) and the E9 outer-shape trap
//! (`get_element` on a slice) are asserted directly, not inferred.
mod dynamic_slicing_tests {
use crate::Tensor;
use crate::dynamic::Element;
/// `Arc::ptr_eq` on the two tensors' dynamic storage — the only way to
/// tell "shared" from "copied with identical contents" from the public
/// API. Both fields are `pub(crate)`, reachable from this in-crate test.
fn shares_storage(a: &Tensor, b: &Tensor) -> bool {
std::sync::Arc::ptr_eq(
&a.dynamic.as_ref().unwrap().storage,
&b.dynamic.as_ref().unwrap().storage,
)
}
fn grid_2x3() -> Tensor {
// [ [0, 1, 2],
// [3, 4, 5] ] -- flat storage order 0..6
Tensor::from_elements((0..6).map(Element::Int).collect(), &[2, 3])
}
// T1 -- slice() on a dynamic tensor: is_dynamic() true, shape correct.
#[test]
fn builder_slice_on_dynamic_returns_dynamic() {
let t = grid_2x3();
let s = t.slice().range(0..1).all().build().unwrap();
assert!(s.is_dynamic());
assert_eq!(s.shape(), &[1, 3]);
}
// T2 -- slice_str() likewise.
#[test]
fn slice_str_on_dynamic_returns_dynamic() {
let t = grid_2x3();
let s = t.slice_str("0:1,:").unwrap();
assert!(s.is_dynamic());
assert_eq!(s.shape(), &[1, 3]);
}
// T3 -- storage is SHARED, asserted via Arc identity, not value equality.
// A slice that copied instead of sharing would pass every assertion below
// except this one.
#[test]
fn slice_shares_storage_with_the_source() {
let t = grid_2x3();
let s = t.slice().index(0).all().build().unwrap();
assert!(
shares_storage(&t, &s),
"slice must Arc::clone the source's storage, not copy it"
);
}
// T4 -- composition: slicing a slice must map through the existing view
// rather than nesting. Asserts BOTH the values (a nesting bug is correct
// for the first slice only) AND that the second slice still shares the
// ORIGINAL tensor's storage (not just its immediate parent's).
#[test]
fn slicing_a_slice_composes_and_still_shares_the_original_storage() {
// [ [0,1,2], [3,4,5], [6,7,8] ]
let t = Tensor::from_elements((0..9).map(Element::Int).collect(), &[3, 3]);
let rows_1_2 = t.slice().range(1..3).all().build().unwrap(); // [[3,4,5],[6,7,8]]
assert_eq!(rows_1_2.shape(), &[2, 3]);
assert!(shares_storage(&t, &rows_1_2));
let second_row = rows_1_2.slice().index(1).all().build().unwrap(); // [6,7,8]
assert_eq!(second_row.shape(), &[3]);
assert_eq!(
(0..3)
.map(|i| second_row.get_element(&[i]).unwrap())
.collect::<Vec<_>>(),
vec![Element::Int(6), Element::Int(7), Element::Int(8)],
"a nesting bug is correct for the first slice only -- this is the second"
);
assert!(
shares_storage(&t, &second_row),
"composition must map through rows_1_2's existing Indexed view into t's \
storage, not nest a view over a view"
);
}
// T5 -- rank-0 collapse: a fully-indexed slice yields shape [] and reads
// its one element.
#[test]
fn fully_indexed_slice_collapses_to_rank_0() {
let t = grid_2x3();
let s = t.slice().index(1).index(2).build().unwrap();
assert!(s.is_dynamic());
assert_eq!(s.shape(), &[] as &[usize]);
assert_eq!(s.get_element(&[]), Some(Element::Int(5)));
}
// T6 -- Text, None, and Bool survive a slice unchanged (round-trip via
// get_element), not just Int/Float.
#[test]
fn text_none_and_bool_survive_a_slice_unchanged() {
let t = Tensor::from_elements(
vec![
Element::text("a"),
Element::None,
Element::Bool(true),
Element::text("b"),
Element::Int(9),
Element::Bool(false),
],
&[2, 3],
);
let s = t.slice().index(1).all().build().unwrap(); // second row
assert_eq!(s.get_element(&[0]), Some(Element::text("b")));
assert_eq!(s.get_element(&[1]), Some(Element::Int(9)));
assert_eq!(s.get_element(&[2]), Some(Element::Bool(false)));
let first_col = t.slice().all().index(0).build().unwrap(); // first column: [a, b]
assert_eq!(first_col.get_element(&[0]), Some(Element::text("a")));
assert_eq!(first_col.get_element(&[1]), Some(Element::text("b")));
let middle_col = t.slice().all().index(1).build().unwrap(); // middle column: [None, 9]
assert_eq!(middle_col.get_element(&[0]), Some(Element::None));
assert_eq!(middle_col.get_element(&[1]), Some(Element::Int(9)));
}
// T7 -- the E9 trap: get_element on a slice must resolve through the
// SLICE's own shape, not the parent's. Column 1 of a 2x3 tensor makes the
// two definitely differ: a wrong outer shape would misread as element
// [0,0] of the parent (0) instead of [0,1] (1).
#[test]
fn get_element_on_a_slice_reads_the_slices_own_position() {
let t = grid_2x3(); // [[0,1,2],[3,4,5]]
let column_1 = t.slice().all().index(1).build().unwrap(); // [1, 4]
assert_eq!(column_1.shape(), &[2]);
assert_eq!(
column_1.get_element(&[0]),
Some(Element::Int(1)),
"must read the slice's own position (t[0,1]=1), not the parent's t[0,0]=0"
);
assert_eq!(column_1.get_element(&[1]), Some(Element::Int(4)));
}
// T8 (numeric side) -- a numeric slice on a dynamic-feature build behaves
// identically to the non-dynamic build; the full pre-existing numeric
// suite (crates/matten/src/slice/tests.rs, 35 tests) is unmodified and
// run in both feature profiles as part of the gate, not duplicated here.
#[test]
fn numeric_slice_is_unaffected_by_the_dynamic_branch() {
let t = Tensor::new((0..6).map(|x| x as f64).collect(), &[2, 3]);
let s = t.slice().index(1).all().build().unwrap();
assert!(!s.is_dynamic());
assert_eq!(s.as_slice(), &[3.0, 4.0, 5.0]);
}
// slice_str's grammar (negative indices, step, All) is shared code (E5) --
// spot-check it reaches the dynamic path too, not just the builder.
#[test]
fn slice_str_step_and_negative_index_reach_the_dynamic_path() {
let t = Tensor::from_elements((0..10).map(Element::Int).collect(), &[10]);
let stepped = t.slice_str("0:10:2").unwrap();
assert_eq!(
(0..5)
.map(|i| stepped.get_element(&[i]).unwrap())
.collect::<Vec<_>>(),
vec![0, 2, 4, 6, 8]
.into_iter()
.map(Element::Int)
.collect::<Vec<_>>()
);
let last = t.slice_str("-1").unwrap();
assert_eq!(last.shape(), &[] as &[usize]);
assert_eq!(last.get_element(&[]), Some(Element::Int(9)));
}
// The old rejection is gone: neither entry point returns Unsupported for
// a dynamic tensor any more (RFC-102 §8 risk 3 -- this is intended).
#[test]
fn dynamic_slice_no_longer_returns_unsupported() {
let t = grid_2x3();
let err = t.slice().index(0).all().build();
assert!(err.is_ok());
let err_str = t.slice_str("0,:");
assert!(err_str.is_ok());
}
}