1use std::collections::HashMap;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum LayoutOrder {
18 RowMajor,
20 ColMajor,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct TensorShape {
31 pub dims: Vec<usize>,
33}
34
35impl TensorShape {
36 pub fn new(dims: Vec<usize>) -> Self {
38 Self { dims }
39 }
40
41 #[inline]
43 pub fn ndim(&self) -> usize {
44 self.dims.len()
45 }
46
47 pub fn total_elements(&self) -> usize {
50 self.dims
51 .iter()
52 .copied()
53 .fold(1usize, usize::saturating_mul)
54 }
55
56 pub fn is_scalar(&self) -> bool {
58 self.dims.is_empty() || self.total_elements() == 1
59 }
60}
61
62#[derive(Clone, Debug)]
68pub struct LayoutDescriptor {
69 pub shape: TensorShape,
71 pub strides: Vec<usize>,
73 pub byte_offset: usize,
75 pub element_size_bytes: usize,
77 pub order: LayoutOrder,
79}
80
81impl LayoutDescriptor {
82 pub fn row_major_strides(dims: &[usize]) -> Vec<usize> {
92 let n = dims.len();
93 if n == 0 {
94 return Vec::new();
95 }
96 let mut strides = vec![0usize; n];
97 strides[n - 1] = 1;
98 for i in (0..n - 1).rev() {
100 strides[i] = strides[i + 1].saturating_mul(dims[i + 1]);
101 }
102 strides
103 }
104
105 pub fn col_major_strides(dims: &[usize]) -> Vec<usize> {
111 let n = dims.len();
112 if n == 0 {
113 return Vec::new();
114 }
115 let mut strides = vec![0usize; n];
116 strides[0] = 1;
117 for i in 1..n {
118 strides[i] = strides[i - 1].saturating_mul(dims[i - 1]);
119 }
120 strides
121 }
122
123 pub fn new(shape: TensorShape, order: LayoutOrder, element_size_bytes: usize) -> Self {
131 let strides = match order {
132 LayoutOrder::RowMajor => Self::row_major_strides(&shape.dims),
133 LayoutOrder::ColMajor => Self::col_major_strides(&shape.dims),
134 };
135 Self {
136 shape,
137 strides,
138 byte_offset: 0,
139 element_size_bytes,
140 order,
141 }
142 }
143
144 pub fn linear_index(&self, indices: &[usize]) -> Option<usize> {
155 let ndim = self.shape.ndim();
156 if indices.len() != ndim {
157 return None;
158 }
159 let mut idx = 0usize;
160 for (i, &coord) in indices.iter().enumerate() {
161 if coord >= self.shape.dims[i] {
162 return None;
163 }
164 idx = idx.saturating_add(coord.saturating_mul(self.strides[i]));
165 }
166 Some(idx)
167 }
168
169 pub fn byte_offset_for(&self, indices: &[usize]) -> Option<usize> {
173 let lin = self.linear_index(indices)?;
174 Some(
175 lin.saturating_mul(self.element_size_bytes)
176 .saturating_add(self.byte_offset),
177 )
178 }
179
180 pub fn is_contiguous(&self) -> bool {
186 self.strides == Self::row_major_strides(&self.shape.dims)
187 }
188
189 pub fn total_bytes(&self) -> usize {
191 self.shape
192 .total_elements()
193 .saturating_mul(self.element_size_bytes)
194 }
195
196 pub fn transposed(&self) -> Self {
206 let mut new_dims = self.shape.dims.clone();
207 new_dims.reverse();
208 let mut new_strides = self.strides.clone();
209 new_strides.reverse();
210 let new_order = match self.order {
211 LayoutOrder::RowMajor => LayoutOrder::ColMajor,
212 LayoutOrder::ColMajor => LayoutOrder::RowMajor,
213 };
214 Self {
215 shape: TensorShape::new(new_dims),
216 strides: new_strides,
217 byte_offset: self.byte_offset,
218 element_size_bytes: self.element_size_bytes,
219 order: new_order,
220 }
221 }
222}
223
224#[derive(Clone, Debug, Default)]
230pub struct LayoutStats {
231 pub total_layouts_created: u64,
233 pub total_transpositions: u64,
235 pub contiguous_count: u64,
237 pub non_contiguous_count: u64,
239}
240
241#[derive(Debug)]
250pub struct TensorMemoryLayout {
251 layouts: HashMap<u64, LayoutDescriptor>,
252 next_id: u64,
253 stats: LayoutStats,
254}
255
256impl TensorMemoryLayout {
257 pub fn new() -> Self {
259 Self {
260 layouts: HashMap::new(),
261 next_id: 0,
262 stats: LayoutStats::default(),
263 }
264 }
265
266 pub fn create(
268 &mut self,
269 shape: TensorShape,
270 order: LayoutOrder,
271 element_size_bytes: usize,
272 ) -> u64 {
273 let descriptor = LayoutDescriptor::new(shape, order, element_size_bytes);
274 if descriptor.is_contiguous() {
275 self.stats.contiguous_count += 1;
276 } else {
277 self.stats.non_contiguous_count += 1;
278 }
279 self.stats.total_layouts_created += 1;
280 let id = self.next_id;
281 self.next_id += 1;
282 self.layouts.insert(id, descriptor);
283 id
284 }
285
286 pub fn transpose(&mut self, layout_id: u64) -> Option<u64> {
291 let transposed = self.layouts.get(&layout_id)?.transposed();
292 self.stats.total_transpositions += 1;
293 if transposed.is_contiguous() {
294 self.stats.contiguous_count += 1;
295 } else {
296 self.stats.non_contiguous_count += 1;
297 }
298 self.stats.total_layouts_created += 1;
299 let new_id = self.next_id;
300 self.next_id += 1;
301 self.layouts.insert(new_id, transposed);
302 Some(new_id)
303 }
304
305 pub fn get(&self, layout_id: u64) -> Option<&LayoutDescriptor> {
307 self.layouts.get(&layout_id)
308 }
309
310 pub fn stats(&self) -> &LayoutStats {
312 &self.stats
313 }
314}
315
316impl Default for TensorMemoryLayout {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322pub type MemoryLayoutShape = TensorShape;
329
330#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
341 fn tensor_shape_ndim() {
342 let s = TensorShape::new(vec![3, 4, 5]);
343 assert_eq!(s.ndim(), 3);
344 }
345
346 #[test]
347 fn tensor_shape_ndim_empty() {
348 let s = TensorShape::new(vec![]);
349 assert_eq!(s.ndim(), 0);
350 }
351
352 #[test]
353 fn tensor_shape_total_elements_3d() {
354 let s = TensorShape::new(vec![3, 4, 5]);
355 assert_eq!(s.total_elements(), 60);
356 }
357
358 #[test]
359 fn tensor_shape_total_elements_empty() {
360 let s = TensorShape::new(vec![]);
361 assert_eq!(s.total_elements(), 1);
362 }
363
364 #[test]
365 fn tensor_shape_is_scalar_empty_dims() {
366 let s = TensorShape::new(vec![]);
367 assert!(s.is_scalar());
368 }
369
370 #[test]
371 fn tensor_shape_is_scalar_single_element() {
372 let s = TensorShape::new(vec![1, 1, 1]);
373 assert!(s.is_scalar());
374 }
375
376 #[test]
377 fn tensor_shape_is_not_scalar_multi_element() {
378 let s = TensorShape::new(vec![3, 4]);
379 assert!(!s.is_scalar());
380 }
381
382 #[test]
385 fn row_major_strides_3d() {
386 let strides = LayoutDescriptor::row_major_strides(&[3, 4, 5]);
387 assert_eq!(strides, vec![20, 5, 1]);
388 }
389
390 #[test]
391 fn row_major_strides_2d() {
392 let strides = LayoutDescriptor::row_major_strides(&[4, 6]);
393 assert_eq!(strides, vec![6, 1]);
394 }
395
396 #[test]
397 fn row_major_strides_1d() {
398 let strides = LayoutDescriptor::row_major_strides(&[7]);
399 assert_eq!(strides, vec![1]);
400 }
401
402 #[test]
403 fn row_major_strides_empty() {
404 let strides = LayoutDescriptor::row_major_strides(&[]);
405 assert!(strides.is_empty());
406 }
407
408 #[test]
409 fn col_major_strides_3d() {
410 let strides = LayoutDescriptor::col_major_strides(&[3, 4, 5]);
411 assert_eq!(strides, vec![1, 3, 12]);
412 }
413
414 #[test]
415 fn col_major_strides_2d() {
416 let strides = LayoutDescriptor::col_major_strides(&[4, 6]);
417 assert_eq!(strides, vec![1, 4]);
418 }
419
420 #[test]
421 fn col_major_strides_1d() {
422 let strides = LayoutDescriptor::col_major_strides(&[5]);
423 assert_eq!(strides, vec![1]);
424 }
425
426 #[test]
427 fn col_major_strides_empty() {
428 let strides = LayoutDescriptor::col_major_strides(&[]);
429 assert!(strides.is_empty());
430 }
431
432 #[test]
435 fn linear_index_row_major_corner() {
436 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
437 assert_eq!(desc.linear_index(&[0, 0, 0]), Some(0));
439 }
440
441 #[test]
442 fn linear_index_row_major_middle() {
443 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
444 assert_eq!(desc.linear_index(&[1, 2, 3]), Some(33));
446 }
447
448 #[test]
449 fn linear_index_col_major() {
450 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::ColMajor, 8);
451 assert_eq!(desc.linear_index(&[1, 2, 3]), Some(43));
453 }
454
455 #[test]
456 fn linear_index_none_wrong_rank() {
457 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
458 assert_eq!(desc.linear_index(&[0, 0]), None);
459 }
460
461 #[test]
462 fn linear_index_none_out_of_bounds() {
463 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
464 assert_eq!(desc.linear_index(&[3, 0, 0]), None);
466 }
467
468 #[test]
469 fn linear_index_none_inner_dim_oob() {
470 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
471 assert_eq!(desc.linear_index(&[0, 4, 0]), None);
472 }
473
474 #[test]
477 fn byte_offset_f32_element_size() {
478 let desc = LayoutDescriptor::new(
479 TensorShape::new(vec![3, 4, 5]),
480 LayoutOrder::RowMajor,
481 4, );
483 assert_eq!(desc.byte_offset_for(&[1, 2, 3]), Some(132));
485 }
486
487 #[test]
488 fn byte_offset_f64_element_size() {
489 let desc = LayoutDescriptor::new(
490 TensorShape::new(vec![3, 4, 5]),
491 LayoutOrder::RowMajor,
492 8, );
494 assert_eq!(desc.byte_offset_for(&[1, 2, 3]), Some(264));
496 }
497
498 #[test]
499 fn byte_offset_none_oob() {
500 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
501 assert_eq!(desc.byte_offset_for(&[3, 0, 0]), None);
502 }
503
504 #[test]
507 fn is_contiguous_row_major_fresh() {
508 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
509 assert!(desc.is_contiguous());
510 }
511
512 #[test]
513 fn is_contiguous_false_after_transpose() {
514 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
515 let t = desc.transposed();
516 assert!(!t.is_contiguous());
518 }
519
520 #[test]
521 fn is_contiguous_col_major_fresh() {
522 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::ColMajor, 4);
524 assert!(!desc.is_contiguous());
526 }
527
528 #[test]
531 fn transposed_reverses_dims() {
532 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
533 let t = desc.transposed();
534 assert_eq!(t.shape.dims, vec![5, 4, 3]);
535 }
536
537 #[test]
538 fn transposed_reverses_strides() {
539 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
540 let t = desc.transposed();
542 assert_eq!(t.strides, vec![1, 5, 20]);
543 }
544
545 #[test]
546 fn transposed_flips_order_row_to_col() {
547 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
548 assert_eq!(desc.transposed().order, LayoutOrder::ColMajor);
549 }
550
551 #[test]
552 fn transposed_flips_order_col_to_row() {
553 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::ColMajor, 4);
554 assert_eq!(desc.transposed().order, LayoutOrder::RowMajor);
555 }
556
557 #[test]
558 fn transposed_preserves_element_size() {
559 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 8);
560 assert_eq!(desc.transposed().element_size_bytes, 8);
561 }
562
563 #[test]
566 fn total_bytes_f32() {
567 let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
568 assert_eq!(desc.total_bytes(), 60 * 4);
569 }
570
571 #[test]
572 fn total_bytes_f64() {
573 let desc = LayoutDescriptor::new(TensorShape::new(vec![2, 3]), LayoutOrder::RowMajor, 8);
574 assert_eq!(desc.total_bytes(), 6 * 8);
575 }
576
577 #[test]
580 fn manager_create_returns_sequential_ids() {
581 let mut mgr = TensorMemoryLayout::new();
582 let id0 = mgr.create(TensorShape::new(vec![2, 3]), LayoutOrder::RowMajor, 4);
583 let id1 = mgr.create(TensorShape::new(vec![4]), LayoutOrder::ColMajor, 8);
584 assert_eq!(id0, 0);
585 assert_eq!(id1, 1);
586 }
587
588 #[test]
589 fn manager_get_retrieves_descriptor() {
590 let mut mgr = TensorMemoryLayout::new();
591 let id = mgr.create(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
592 let desc = mgr.get(id).expect("descriptor must exist");
593 assert_eq!(desc.shape.dims, vec![3, 4, 5]);
594 }
595
596 #[test]
597 fn manager_get_missing_id_returns_none() {
598 let mgr = TensorMemoryLayout::new();
599 assert!(mgr.get(99).is_none());
600 }
601
602 #[test]
603 fn manager_transpose_creates_new_entry() {
604 let mut mgr = TensorMemoryLayout::new();
605 let id = mgr.create(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
606 let tid = mgr.transpose(id).expect("transpose must succeed");
607 assert_ne!(id, tid);
608 let t = mgr.get(tid).expect("transposed descriptor must exist");
609 assert_eq!(t.shape.dims, vec![5, 4, 3]);
610 }
611
612 #[test]
613 fn manager_transpose_missing_id_returns_none() {
614 let mut mgr = TensorMemoryLayout::new();
615 assert!(mgr.transpose(42).is_none());
616 }
617
618 #[test]
619 fn manager_stats_total_layouts_created() {
620 let mut mgr = TensorMemoryLayout::new();
621 mgr.create(TensorShape::new(vec![2, 2]), LayoutOrder::RowMajor, 4);
622 mgr.create(TensorShape::new(vec![3]), LayoutOrder::RowMajor, 4);
623 assert_eq!(mgr.stats().total_layouts_created, 2);
624 }
625
626 #[test]
627 fn manager_stats_transpositions() {
628 let mut mgr = TensorMemoryLayout::new();
629 let id = mgr.create(TensorShape::new(vec![2, 3]), LayoutOrder::RowMajor, 4);
630 mgr.transpose(id);
631 mgr.transpose(id);
632 assert_eq!(mgr.stats().total_transpositions, 2);
633 }
634
635 #[test]
636 fn manager_stats_contiguous_count() {
637 let mut mgr = TensorMemoryLayout::new();
638 mgr.create(TensorShape::new(vec![2, 3]), LayoutOrder::RowMajor, 4);
640 assert_eq!(mgr.stats().contiguous_count, 1);
641 assert_eq!(mgr.stats().non_contiguous_count, 0);
642 }
643
644 #[test]
645 fn manager_stats_non_contiguous_count() {
646 let mut mgr = TensorMemoryLayout::new();
647 mgr.create(TensorShape::new(vec![2, 3]), LayoutOrder::ColMajor, 4);
649 assert_eq!(mgr.stats().non_contiguous_count, 1);
650 assert_eq!(mgr.stats().contiguous_count, 0);
651 }
652
653 #[test]
654 fn manager_default_is_empty() {
655 let mgr = TensorMemoryLayout::default();
656 assert_eq!(mgr.stats().total_layouts_created, 0);
657 assert!(mgr.get(0).is_none());
658 }
659
660 #[test]
663 fn memory_layout_shape_alias_works() {
664 let s: MemoryLayoutShape = MemoryLayoutShape::new(vec![2, 3, 4]);
665 assert_eq!(s.total_elements(), 24);
666 }
667}