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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! Property-based tests for batch tensor operations.
//!
//! **Feature: Syna-ai-native, Property 20: Batch Tensor Shape Consistency**
//! **Validates: Requirements 2.1, 2.2**
//!
//! This test verifies that:
//! - Storing a tensor and loading it back preserves the shape
//! - The number of elements in the returned tensor matches the stored count
//! - Data round-trips correctly through put_tensor/get_tensor
use proptest::prelude::*;
use synadb::tensor::{DType, TensorEngine};
use synadb::SynaDB;
use tempfile::tempdir;
/// Generator for valid tensor element counts (10-1000 elements).
fn arb_n_elements() -> impl Strategy<Value = usize> {
10usize..1000
}
/// Generator for valid data types.
fn arb_dtype() -> impl Strategy<Value = DType> {
prop_oneof![
Just(DType::Float32),
Just(DType::Float64),
Just(DType::Int32),
Just(DType::Int64),
]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
/// **Feature: Syna-ai-native, Property 20: Batch Tensor Shape Consistency**
///
/// For any batch tensor operation with specified shape S, the returned tensor
/// SHALL have shape S, and the total elements SHALL equal the product of dimensions.
///
/// This property tests that:
/// 1. put_tensor stores the correct number of elements
/// 2. get_tensor returns a tensor with shape matching the stored element count
/// 3. The round-trip preserves the data
///
/// **Validates: Requirements 2.1, 2.2**
#[test]
fn prop_tensor_roundtrip_preserves_shape(
n_elements in arb_n_elements(),
) {
let dir = tempdir().expect("failed to create temp dir");
let db_path = dir.path().join("test.db");
let db = SynaDB::new(&db_path).expect("failed to create db");
let mut engine = TensorEngine::new(db);
// Create tensor data with n_elements Float64 values
let original_values: Vec<f64> = (0..n_elements)
.map(|i| i as f64 * 0.1)
.collect();
let data: Vec<u8> = original_values
.iter()
.flat_map(|f| f.to_le_bytes())
.collect();
// Store tensor with shape [n_elements]
let shape = vec![n_elements];
let stored_count = engine
.put_tensor("tensor/", &data, &shape, DType::Float64)
.expect("put_tensor should succeed");
// Verify stored count matches expected
prop_assert_eq!(
stored_count,
n_elements,
"stored count should equal n_elements"
);
// Load tensor back
let (loaded_data, loaded_shape) = engine
.get_tensor("tensor/*", DType::Float64)
.expect("get_tensor should succeed");
// Verify shape matches
prop_assert_eq!(
loaded_shape.len(),
1,
"loaded shape should be 1D"
);
prop_assert_eq!(
loaded_shape[0],
n_elements,
"loaded shape[0] should equal n_elements"
);
// Verify data size matches
let expected_bytes = n_elements * DType::Float64.size();
prop_assert_eq!(
loaded_data.len(),
expected_bytes,
"loaded data size should match expected bytes"
);
// Verify values round-trip correctly
let loaded_values: Vec<f64> = loaded_data
.chunks(8)
.map(|chunk| {
let arr: [u8; 8] = chunk.try_into().expect("chunk should be 8 bytes");
f64::from_le_bytes(arr)
})
.collect();
prop_assert_eq!(
loaded_values.len(),
original_values.len(),
"loaded values count should match original"
);
for (i, (&expected, &actual)) in original_values.iter().zip(loaded_values.iter()).enumerate() {
prop_assert!(
(expected - actual).abs() < 1e-10,
"value at index {} should match: expected {}, got {}",
i,
expected,
actual
);
}
}
/// **Feature: Syna-ai-native, Property 20: Batch Tensor Shape Consistency (Multi-dtype)**
///
/// For any data type, storing and loading a tensor should preserve the element count.
///
/// **Validates: Requirements 2.1, 2.2**
#[test]
fn prop_tensor_shape_consistency_all_dtypes(
n_elements in 10usize..500,
dtype in arb_dtype(),
) {
let dir = tempdir().expect("failed to create temp dir");
let db_path = dir.path().join("test.db");
let db = SynaDB::new(&db_path).expect("failed to create db");
let mut engine = TensorEngine::new(db);
// Create tensor data based on dtype
let element_size = dtype.size();
let data: Vec<u8> = match dtype {
DType::Float32 => {
(0..n_elements)
.flat_map(|i| (i as f32 * 0.1).to_le_bytes())
.collect()
}
DType::Float64 => {
(0..n_elements)
.flat_map(|i| (i as f64 * 0.1).to_le_bytes())
.collect()
}
DType::Int32 => {
(0..n_elements)
.flat_map(|i| (i as i32).to_le_bytes())
.collect()
}
DType::Int64 => {
(0..n_elements)
.flat_map(|i| (i as i64).to_le_bytes())
.collect()
}
};
// Store tensor
let shape = vec![n_elements];
let stored_count = engine
.put_tensor("data/", &data, &shape, dtype)
.expect("put_tensor should succeed");
prop_assert_eq!(
stored_count,
n_elements,
"stored count should equal n_elements for {:?}",
dtype
);
// Load tensor back
let (loaded_data, loaded_shape) = engine
.get_tensor("data/*", dtype)
.expect("get_tensor should succeed");
// Verify shape
prop_assert_eq!(
loaded_shape[0],
n_elements,
"loaded shape should equal n_elements for {:?}",
dtype
);
// Verify data size
let expected_bytes = n_elements * element_size;
prop_assert_eq!(
loaded_data.len(),
expected_bytes,
"loaded data size should match for {:?}",
dtype
);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Basic unit test to verify the property test setup works.
#[test]
fn test_tensor_roundtrip_basic() {
let dir = tempdir().expect("failed to create temp dir");
let db_path = dir.path().join("test.db");
let db = SynaDB::new(&db_path).expect("failed to create db");
let mut engine = TensorEngine::new(db);
// Store 10 float64 values
let values: Vec<f64> = (0..10).map(|i| i as f64).collect();
let data: Vec<u8> = values.iter().flat_map(|f| f.to_le_bytes()).collect();
let count = engine
.put_tensor("test/", &data, &[10], DType::Float64)
.expect("put_tensor should succeed");
assert_eq!(count, 10);
let (loaded_data, shape) = engine
.get_tensor("test/*", DType::Float64)
.expect("get_tensor should succeed");
assert_eq!(shape, vec![10]);
assert_eq!(loaded_data.len(), 80); // 10 * 8 bytes
}
}