Skip to main content

ipfrs_tensorlogic/
memory_layout.rs

1//! Tensor memory layout management for multi-dimensional arrays.
2//!
3//! Provides layout descriptors that capture shape, strides, byte offsets,
4//! and layout transformations (row-major / column-major, transposition, slicing).
5
6use std::collections::HashMap;
7
8// ---------------------------------------------------------------------------
9// LayoutOrder
10// ---------------------------------------------------------------------------
11
12/// Memory ordering for a multi-dimensional tensor.
13///
14/// - `RowMajor` (C-style): last dimension varies fastest.
15/// - `ColMajor` (Fortran-style): first dimension varies fastest.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum LayoutOrder {
18    /// C-style layout — last dimension changes fastest in memory.
19    RowMajor,
20    /// Fortran-style layout — first dimension changes fastest in memory.
21    ColMajor,
22}
23
24// ---------------------------------------------------------------------------
25// TensorShape
26// ---------------------------------------------------------------------------
27
28/// Describes the logical shape of a tensor.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct TensorShape {
31    /// Sizes of each dimension, e.g. `[3, 4, 5]` for a 3-D tensor.
32    pub dims: Vec<usize>,
33}
34
35impl TensorShape {
36    /// Creates a new `TensorShape` from the given dimension sizes.
37    pub fn new(dims: Vec<usize>) -> Self {
38        Self { dims }
39    }
40
41    /// Number of dimensions.
42    #[inline]
43    pub fn ndim(&self) -> usize {
44        self.dims.len()
45    }
46
47    /// Total number of elements (product of all dims).  Returns 1 for a
48    /// zero-dimensional (scalar) shape.
49    pub fn total_elements(&self) -> usize {
50        self.dims
51            .iter()
52            .copied()
53            .fold(1usize, usize::saturating_mul)
54    }
55
56    /// Returns `true` for scalar tensors (zero dims or `total_elements == 1`).
57    pub fn is_scalar(&self) -> bool {
58        self.dims.is_empty() || self.total_elements() == 1
59    }
60}
61
62// ---------------------------------------------------------------------------
63// LayoutDescriptor
64// ---------------------------------------------------------------------------
65
66/// Complete memory layout descriptor for a tensor.
67#[derive(Clone, Debug)]
68pub struct LayoutDescriptor {
69    /// Logical shape of the tensor.
70    pub shape: TensorShape,
71    /// Stride (in *elements*, not bytes) for each dimension.
72    pub strides: Vec<usize>,
73    /// Byte offset from the start of the backing buffer to element [0,…,0].
74    pub byte_offset: usize,
75    /// Size of a single element in bytes (e.g. 4 for `f32`, 8 for `f64`).
76    pub element_size_bytes: usize,
77    /// Memory ordering of this layout.
78    pub order: LayoutOrder,
79}
80
81impl LayoutDescriptor {
82    // -----------------------------------------------------------------------
83    // Stride computation helpers
84    // -----------------------------------------------------------------------
85
86    /// Compute row-major (C-style) strides for `dims`.
87    ///
88    /// The last dimension has stride 1; each preceding stride equals the
89    /// product of all following dimensions.  For `[3, 4, 5]` the result is
90    /// `[20, 5, 1]`.
91    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        // Walk backwards: stride[i] = stride[i+1] * dims[i+1]
99        for i in (0..n - 1).rev() {
100            strides[i] = strides[i + 1].saturating_mul(dims[i + 1]);
101        }
102        strides
103    }
104
105    /// Compute column-major (Fortran-style) strides for `dims`.
106    ///
107    /// The first dimension has stride 1; each following stride equals the
108    /// product of all preceding dimensions.  For `[3, 4, 5]` the result is
109    /// `[1, 3, 12]`.
110    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    // -----------------------------------------------------------------------
124    // Constructor
125    // -----------------------------------------------------------------------
126
127    /// Create a new layout descriptor.
128    ///
129    /// Strides are computed from `order`; `byte_offset` starts at 0.
130    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    // -----------------------------------------------------------------------
145    // Index computation
146    // -----------------------------------------------------------------------
147
148    /// Compute the flat (linear) element index for the given multi-dimensional
149    /// `indices`.
150    ///
151    /// Returns `None` when:
152    /// - `indices.len() != ndim`, or
153    /// - any `indices[i] >= shape.dims[i]`.
154    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    /// Compute the byte offset into the backing buffer for `indices`.
170    ///
171    /// Returns `None` under the same conditions as `linear_index`.
172    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    // -----------------------------------------------------------------------
181    // Layout properties
182    // -----------------------------------------------------------------------
183
184    /// Returns `true` if this layout has row-major (C-contiguous) strides.
185    pub fn is_contiguous(&self) -> bool {
186        self.strides == Self::row_major_strides(&self.shape.dims)
187    }
188
189    /// Total bytes occupied by all elements.
190    pub fn total_bytes(&self) -> usize {
191        self.shape
192            .total_elements()
193            .saturating_mul(self.element_size_bytes)
194    }
195
196    // -----------------------------------------------------------------------
197    // Transformations
198    // -----------------------------------------------------------------------
199
200    /// Return a new `LayoutDescriptor` representing the transpose of this
201    /// layout.
202    ///
203    /// Dimension and stride order are reversed; memory ordering is flipped
204    /// (`RowMajor` ↔ `ColMajor`).
205    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// ---------------------------------------------------------------------------
225// LayoutStats
226// ---------------------------------------------------------------------------
227
228/// Cumulative statistics gathered by [`TensorMemoryLayout`].
229#[derive(Clone, Debug, Default)]
230pub struct LayoutStats {
231    /// Total number of layout descriptors ever created.
232    pub total_layouts_created: u64,
233    /// Total number of transposition operations performed.
234    pub total_transpositions: u64,
235    /// Layouts that were contiguous at creation time.
236    pub contiguous_count: u64,
237    /// Layouts that were *not* contiguous at creation time.
238    pub non_contiguous_count: u64,
239}
240
241// ---------------------------------------------------------------------------
242// TensorMemoryLayout  (manager)
243// ---------------------------------------------------------------------------
244
245/// Manager for a collection of [`LayoutDescriptor`]s.
246///
247/// Each descriptor is assigned a unique `u64` identifier on creation.
248/// Provides statistics tracking for auditing and profiling purposes.
249#[derive(Debug)]
250pub struct TensorMemoryLayout {
251    layouts: HashMap<u64, LayoutDescriptor>,
252    next_id: u64,
253    stats: LayoutStats,
254}
255
256impl TensorMemoryLayout {
257    /// Create an empty layout manager.
258    pub fn new() -> Self {
259        Self {
260            layouts: HashMap::new(),
261            next_id: 0,
262            stats: LayoutStats::default(),
263        }
264    }
265
266    /// Create a new [`LayoutDescriptor`], store it, and return its id.
267    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    /// Transpose an existing layout and store the result as a new entry.
287    ///
288    /// Returns `None` if `layout_id` does not exist; otherwise returns the
289    /// id of the newly created transposed layout.
290    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    /// Retrieve a reference to a layout descriptor by id.
306    pub fn get(&self, layout_id: u64) -> Option<&LayoutDescriptor> {
307        self.layouts.get(&layout_id)
308    }
309
310    /// Cumulative statistics for this manager.
311    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
322// ---------------------------------------------------------------------------
323// Type alias (for callers that want the MemoryLayoutShape name)
324// ---------------------------------------------------------------------------
325
326/// Alias for [`TensorShape`] used when importing alongside other crates that
327/// define their own `TensorShape`.
328pub type MemoryLayoutShape = TensorShape;
329
330// ---------------------------------------------------------------------------
331// Tests
332// ---------------------------------------------------------------------------
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    // ── TensorShape ─────────────────────────────────────────────────────────
339
340    #[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    // ── Stride computation ───────────────────────────────────────────────────
383
384    #[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    // ── linear_index ────────────────────────────────────────────────────────
433
434    #[test]
435    fn linear_index_row_major_corner() {
436        let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::RowMajor, 4);
437        // [0,0,0] → 0
438        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        // [1, 2, 3] → 1*20 + 2*5 + 3*1 = 33
445        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        // strides = [1, 3, 12]; [1, 2, 3] → 1 + 6 + 36 = 43
452        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        // dim 0 has size 3, index 3 is out of bounds
465        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    // ── byte_offset_for ──────────────────────────────────────────────────────
475
476    #[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, // f32
482        );
483        // linear_index([1,2,3]) = 33 → byte offset = 33 * 4 = 132
484        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, // f64
493        );
494        // 33 * 8 = 264
495        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    // ── is_contiguous ────────────────────────────────────────────────────────
505
506    #[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        // strides are now [1, 5, 20]; row-major for [5,4,3] would be [12,3,1]
517        assert!(!t.is_contiguous());
518    }
519
520    #[test]
521    fn is_contiguous_col_major_fresh() {
522        // ColMajor is NOT row-major contiguous
523        let desc = LayoutDescriptor::new(TensorShape::new(vec![3, 4, 5]), LayoutOrder::ColMajor, 4);
524        // strides = [1,3,12]; row-major for [3,4,5] = [20,5,1] → not equal
525        assert!(!desc.is_contiguous());
526    }
527
528    // ── transposed ───────────────────────────────────────────────────────────
529
530    #[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        // strides before: [20, 5, 1]
541        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    // ── total_bytes ───────────────────────────────────────────────────────────
564
565    #[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    // ── TensorMemoryLayout manager ───────────────────────────────────────────
578
579    #[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        // Row-major is contiguous
639        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        // Col-major is not row-major contiguous
648        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    // ── MemoryLayoutShape alias ───────────────────────────────────────────────
661
662    #[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}