Skip to main content

tensor4all_core/
col_major_array.rs

1//! N-dimensional column-major array types.
2//!
3//! Column-major layout: the element at multi-index `[i0, i1, i2, ...]` is stored
4//! at flat offset `i0 + shape[0] * (i1 + shape[1] * (i2 + ...))`.
5//!
6//! Three flavors are provided:
7//! - [`ColMajorArrayRef`] — borrowed data and shape (read-only)
8//! - [`ColMajorArrayMut`] — mutably borrowed data, borrowed shape
9//! - [`ColMajorArray`] — fully owned data and shape
10
11/// Errors that can occur when constructing or modifying a column-major array.
12#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
13pub enum ColMajorArrayError {
14    /// The length of the data does not match the product of the shape dimensions.
15    #[error("Shape mismatch: shape {shape:?} requires {expected} elements, but got {actual}")]
16    ShapeMismatch {
17        /// The requested shape.
18        shape: Vec<usize>,
19        /// Number of elements implied by the shape.
20        expected: usize,
21        /// Number of elements actually provided.
22        actual: usize,
23    },
24
25    /// The column length does not match `nrows`.
26    #[error("Column length mismatch: expected {expected} elements, but got {actual}")]
27    ColumnLengthMismatch {
28        /// Expected number of rows.
29        expected: usize,
30        /// Actual number of elements in the column.
31        actual: usize,
32    },
33
34    /// A 2D operation was called on an array that is not 2-dimensional.
35    #[error("Expected a 2D array, but ndim = {ndim}")]
36    Not2D {
37        /// The actual number of dimensions.
38        ndim: usize,
39    },
40
41    /// The product of shape dimensions overflows `usize`.
42    #[error("Shape product overflow: shape {shape:?} overflows usize")]
43    ShapeOverflow {
44        /// The shape that caused the overflow.
45        shape: Vec<usize>,
46    },
47
48    /// Incrementing the column count would overflow `usize`.
49    #[error("Column count overflow")]
50    ColumnCountOverflow,
51}
52
53// ---------------------------------------------------------------------------
54// Helper: compute the total number of elements from a shape
55// ---------------------------------------------------------------------------
56
57fn checked_shape_numel(shape: &[usize]) -> Option<usize> {
58    shape
59        .iter()
60        .copied()
61        .try_fold(1usize, |acc, d| acc.checked_mul(d))
62}
63
64/// Compute the flat offset for a column-major multi-index, using checked
65/// arithmetic. Returns `None` if any index is out of bounds or on overflow.
66fn flat_offset(shape: &[usize], index: &[usize]) -> Option<usize> {
67    if index.len() != shape.len() {
68        return None;
69    }
70    // Traverse from the last axis to the first:
71    //   offset = i_{n-1}
72    //   offset = i_{n-2} + shape[n-2] * offset  -- but we build from the back
73    // Actually, column-major: offset = i0 + s0*(i1 + s1*(i2 + ...))
74    // Evaluate right-to-left (Horner-like):
75    let mut offset: usize = 0;
76    for (idx, dim) in index.iter().zip(shape.iter()).rev() {
77        if *idx >= *dim {
78            return None;
79        }
80        offset = offset.checked_mul(*dim)?.checked_add(*idx)?;
81    }
82    Some(offset)
83}
84
85// ===========================================================================
86// ColMajorArrayRef
87// ===========================================================================
88
89/// A borrowed, read-only view of an N-dimensional column-major array.
90#[derive(Debug, Clone, Copy)]
91pub struct ColMajorArrayRef<'a, T> {
92    data: &'a [T],
93    shape: &'a [usize],
94}
95
96impl<'a, T> ColMajorArrayRef<'a, T> {
97    /// Create a new borrowed array view.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error when the construction or conversion fails (a shape or
102    /// /// index mismatch, or a backend failure).
103    ///
104    pub fn new(data: &'a [T], shape: &'a [usize]) -> Result<Self, ColMajorArrayError> {
105        let expected =
106            checked_shape_numel(shape).ok_or_else(|| ColMajorArrayError::ShapeOverflow {
107                shape: shape.to_vec(),
108            })?;
109        if data.len() != expected {
110            return Err(ColMajorArrayError::ShapeMismatch {
111                shape: shape.to_vec(),
112                expected,
113                actual: data.len(),
114            });
115        }
116        Ok(Self { data, shape })
117    }
118
119    /// Number of dimensions.
120    pub fn ndim(&self) -> usize {
121        self.shape.len()
122    }
123
124    /// Shape of the array.
125    pub fn shape(&self) -> &[usize] {
126        self.shape
127    }
128
129    /// Total number of elements.
130    pub fn len(&self) -> usize {
131        self.data.len()
132    }
133
134    /// Whether the array is empty (zero elements).
135    pub fn is_empty(&self) -> bool {
136        self.data.is_empty()
137    }
138
139    /// Flat (contiguous) data slice.
140    pub fn data(&self) -> &[T] {
141        self.data
142    }
143
144    /// Get a reference to the element at the given multi-index, or `None` if
145    /// out of bounds.
146    pub fn get(&self, index: &[usize]) -> Option<&T> {
147        let off = flat_offset(self.shape, index)?;
148        self.data.get(off)
149    }
150}
151
152// ===========================================================================
153// ColMajorArrayMut
154// ===========================================================================
155
156/// A mutably borrowed view of an N-dimensional column-major array.
157#[derive(Debug)]
158pub struct ColMajorArrayMut<'a, T> {
159    data: &'a mut [T],
160    shape: &'a [usize],
161}
162
163impl<'a, T> ColMajorArrayMut<'a, T> {
164    /// Create a new mutable borrowed array view.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error when the construction or conversion fails (a shape or
169    /// /// index mismatch, or a backend failure).
170    ///
171    pub fn new(data: &'a mut [T], shape: &'a [usize]) -> Result<Self, ColMajorArrayError> {
172        let expected =
173            checked_shape_numel(shape).ok_or_else(|| ColMajorArrayError::ShapeOverflow {
174                shape: shape.to_vec(),
175            })?;
176        if data.len() != expected {
177            return Err(ColMajorArrayError::ShapeMismatch {
178                shape: shape.to_vec(),
179                expected,
180                actual: data.len(),
181            });
182        }
183        Ok(Self { data, shape })
184    }
185
186    /// Number of dimensions.
187    pub fn ndim(&self) -> usize {
188        self.shape.len()
189    }
190
191    /// Shape of the array.
192    pub fn shape(&self) -> &[usize] {
193        self.shape
194    }
195
196    /// Total number of elements.
197    pub fn len(&self) -> usize {
198        self.data.len()
199    }
200
201    /// Whether the array is empty (zero elements).
202    pub fn is_empty(&self) -> bool {
203        self.data.is_empty()
204    }
205
206    /// Flat (contiguous) data slice (read-only).
207    pub fn data(&self) -> &[T] {
208        self.data
209    }
210
211    /// Flat (contiguous) data slice (mutable).
212    pub fn data_mut(&mut self) -> &mut [T] {
213        self.data
214    }
215
216    /// Get a reference to the element at the given multi-index, or `None` if
217    /// out of bounds.
218    pub fn get(&self, index: &[usize]) -> Option<&T> {
219        let off = flat_offset(self.shape, index)?;
220        self.data.get(off)
221    }
222
223    /// Get a mutable reference to the element at the given multi-index, or
224    /// `None` if out of bounds.
225    pub fn get_mut(&mut self, index: &[usize]) -> Option<&mut T> {
226        let off = flat_offset(self.shape, index)?;
227        self.data.get_mut(off)
228    }
229}
230
231// ===========================================================================
232// ColMajorArray (owned)
233// ===========================================================================
234
235/// A fully owned N-dimensional column-major array.
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct ColMajorArray<T> {
238    data: Vec<T>,
239    shape: Vec<usize>,
240}
241
242impl<T> ColMajorArray<T> {
243    /// Create a new owned array from data and shape.
244    ///
245    /// Returns an error if `data.len()` does not equal the product of the
246    /// shape dimensions.
247    /// # Errors
248    ///
249    /// Returns an error when the construction or conversion fails (a shape or
250    /// /// index mismatch, or a backend failure).
251    ///
252    pub fn new(data: Vec<T>, shape: Vec<usize>) -> Result<Self, ColMajorArrayError> {
253        let expected =
254            checked_shape_numel(&shape).ok_or_else(|| ColMajorArrayError::ShapeOverflow {
255                shape: shape.clone(),
256            })?;
257        if data.len() != expected {
258            return Err(ColMajorArrayError::ShapeMismatch {
259                shape,
260                expected,
261                actual: data.len(),
262            });
263        }
264        Ok(Self { data, shape })
265    }
266
267    /// Number of dimensions.
268    pub fn ndim(&self) -> usize {
269        self.shape.len()
270    }
271
272    /// Shape of the array.
273    pub fn shape(&self) -> &[usize] {
274        &self.shape
275    }
276
277    /// Total number of elements.
278    pub fn len(&self) -> usize {
279        self.data.len()
280    }
281
282    /// Whether the array is empty (zero elements).
283    pub fn is_empty(&self) -> bool {
284        self.data.is_empty()
285    }
286
287    /// Flat (contiguous) data slice (read-only).
288    pub fn data(&self) -> &[T] {
289        &self.data
290    }
291
292    /// Flat (contiguous) data slice (mutable).
293    pub fn data_mut(&mut self) -> &mut [T] {
294        &mut self.data
295    }
296
297    /// Get a reference to the element at the given multi-index, or `None` if
298    /// out of bounds.
299    pub fn get(&self, index: &[usize]) -> Option<&T> {
300        let off = flat_offset(&self.shape, index)?;
301        self.data.get(off)
302    }
303
304    /// Get a mutable reference to the element at the given multi-index, or
305    /// `None` if out of bounds.
306    pub fn get_mut(&mut self, index: &[usize]) -> Option<&mut T> {
307        let off = flat_offset(&self.shape, index)?;
308        self.data.get_mut(off)
309    }
310
311    /// Consume the array and return the underlying data vector.
312    pub fn into_data(self) -> Vec<T> {
313        self.data
314    }
315
316    /// Borrow as a [`ColMajorArrayRef`].
317    pub fn as_ref(&self) -> ColMajorArrayRef<'_, T> {
318        ColMajorArrayRef {
319            data: &self.data,
320            shape: &self.shape,
321        }
322    }
323
324    /// Borrow as a [`ColMajorArrayMut`].
325    pub fn as_mut(&mut self) -> ColMajorArrayMut<'_, T> {
326        ColMajorArrayMut {
327            data: &mut self.data,
328            shape: &self.shape,
329        }
330    }
331
332    // -- 2D helpers ---------------------------------------------------------
333
334    /// Number of rows, or `None` when the array is not 2D.
335    pub fn nrows(&self) -> Option<usize> {
336        if self.ndim() == 2 {
337            Some(self.shape[0])
338        } else {
339            None
340        }
341    }
342
343    /// Number of columns, or `None` when the array is not 2D.
344    pub fn ncols(&self) -> Option<usize> {
345        if self.ndim() == 2 {
346            Some(self.shape[1])
347        } else {
348            None
349        }
350    }
351
352    /// Return a slice for column `j` of a 2D array, or `None` if `j` is out
353    /// of range or if the array is not 2D.
354    pub fn column(&self, j: usize) -> Option<&[T]> {
355        if self.ndim() != 2 {
356            return None;
357        }
358        let nrows = self.shape[0];
359        if j >= self.shape[1] {
360            return None;
361        }
362        let start = nrows.checked_mul(j)?;
363        let end = start.checked_add(nrows)?;
364        Some(&self.data[start..end])
365    }
366
367    /// Append a column to a 2D array.
368    ///
369    /// The `col` slice must have length equal to `nrows()`. This extends
370    /// the internal data and increments `shape[1]`.
371    ///
372    /// Returns an error if the array is not 2D or if the column length does
373    /// not match `nrows()`.
374    /// # Errors
375    ///
376    /// Returns an error when the column length does not match the row count (a
377    /// /// shape mismatch).
378    ///
379    pub fn push_column(&mut self, col: &[T]) -> Result<(), ColMajorArrayError>
380    where
381        T: Clone,
382    {
383        if self.ndim() != 2 {
384            return Err(ColMajorArrayError::Not2D { ndim: self.ndim() });
385        }
386        let nrows = self.shape[0];
387        if col.len() != nrows {
388            return Err(ColMajorArrayError::ColumnLengthMismatch {
389                expected: nrows,
390                actual: col.len(),
391            });
392        }
393        self.data.extend_from_slice(col);
394        self.shape[1] = self.shape[1]
395            .checked_add(1)
396            .ok_or(ColMajorArrayError::ColumnCountOverflow)?;
397        Ok(())
398    }
399}
400
401// -- Factories (require trait bounds on T) ----------------------------------
402
403impl<T: Clone> ColMajorArray<T> {
404    /// Create an array filled with a given value.
405    ///
406    /// Returns an error if the product of shape dimensions overflows `usize`.
407    /// # Errors
408    ///
409    /// Returns an error when the dimensions are invalid (a shape mismatch) or the
410    /// /// fill fails.
411    ///
412    pub fn filled(shape: Vec<usize>, value: T) -> Result<Self, ColMajorArrayError> {
413        let n = checked_shape_numel(&shape).ok_or_else(|| ColMajorArrayError::ShapeOverflow {
414            shape: shape.clone(),
415        })?;
416        Ok(Self {
417            data: vec![value; n],
418            shape,
419        })
420    }
421}
422
423impl<T: Default + Clone> ColMajorArray<T> {
424    /// Create an array filled with [`Default::default()`] (e.g., zeros for
425    /// numeric types).
426    ///
427    /// Returns an error if the product of shape dimensions overflows `usize`.
428    /// # Errors
429    ///
430    /// Returns an error when the dimensions are invalid (a shape mismatch).
431    ///
432    pub fn zeros(shape: Vec<usize>) -> Result<Self, ColMajorArrayError> {
433        let n = checked_shape_numel(&shape).ok_or_else(|| ColMajorArrayError::ShapeOverflow {
434            shape: shape.clone(),
435        })?;
436        Ok(Self {
437            data: vec![T::default(); n],
438            shape,
439        })
440    }
441}
442
443// ===========================================================================
444// Tests
445// ===========================================================================
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    // -- 1D creation + get --------------------------------------------------
452
453    #[test]
454    fn test_1d_creation_and_get() {
455        let arr = ColMajorArray::new(vec![10, 20, 30], vec![3]).unwrap();
456        assert_eq!(arr.ndim(), 1);
457        assert_eq!(arr.shape(), &[3]);
458        assert_eq!(arr.len(), 3);
459        assert!(!arr.is_empty());
460
461        assert_eq!(arr.get(&[0]), Some(&10));
462        assert_eq!(arr.get(&[1]), Some(&20));
463        assert_eq!(arr.get(&[2]), Some(&30));
464    }
465
466    // -- 2D creation + get --------------------------------------------------
467
468    #[test]
469    fn test_2d_creation_and_get() {
470        // 2x3 matrix in column-major:
471        // Column 0: [1, 2], Column 1: [3, 4], Column 2: [5, 6]
472        // Flat: [1, 2, 3, 4, 5, 6]
473        let arr = ColMajorArray::new(vec![1, 2, 3, 4, 5, 6], vec![2, 3]).unwrap();
474        assert_eq!(arr.ndim(), 2);
475        assert_eq!(arr.shape(), &[2, 3]);
476        assert_eq!(arr.len(), 6);
477
478        // (row, col)
479        assert_eq!(arr.get(&[0, 0]), Some(&1));
480        assert_eq!(arr.get(&[1, 0]), Some(&2));
481        assert_eq!(arr.get(&[0, 1]), Some(&3));
482        assert_eq!(arr.get(&[1, 1]), Some(&4));
483        assert_eq!(arr.get(&[0, 2]), Some(&5));
484        assert_eq!(arr.get(&[1, 2]), Some(&6));
485    }
486
487    // -- 3D creation + get --------------------------------------------------
488
489    #[test]
490    fn test_3d_creation_and_get() {
491        // Shape [2, 3, 2]: total 12 elements
492        let data: Vec<i32> = (0..12).collect();
493        let arr = ColMajorArray::new(data.clone(), vec![2, 3, 2]).unwrap();
494        assert_eq!(arr.ndim(), 3);
495        assert_eq!(arr.len(), 12);
496
497        // Verify column-major offset: i0 + 2*(i1 + 3*i2)
498        for i2 in 0..2 {
499            for i1 in 0..3 {
500                for i0 in 0..2 {
501                    let expected_offset = i0 + 2 * (i1 + 3 * i2);
502                    assert_eq!(
503                        arr.get(&[i0, i1, i2]),
504                        Some(&(expected_offset as i32)),
505                        "Mismatch at [{i0}, {i1}, {i2}]"
506                    );
507                }
508            }
509        }
510    }
511
512    // -- Column-major order verification (2D) -------------------------------
513
514    #[test]
515    fn test_column_major_order_2d() {
516        let nrows = 3;
517        let ncols = 4;
518        let data: Vec<i32> = (0..(nrows * ncols) as i32).collect();
519        let arr = ColMajorArray::new(data.clone(), vec![nrows, ncols]).unwrap();
520
521        // In column-major, data[i + nrows * j] == arr[(i, j)]
522        for j in 0..ncols {
523            for i in 0..nrows {
524                assert_eq!(arr.get(&[i, j]), Some(&data[i + nrows * j]));
525            }
526        }
527    }
528
529    // -- get_mut ------------------------------------------------------------
530
531    #[test]
532    fn test_get_mut() {
533        let mut arr = ColMajorArray::new(vec![1, 2, 3, 4], vec![2, 2]).unwrap();
534        if let Some(v) = arr.get_mut(&[1, 0]) {
535            *v = 42;
536        }
537        assert_eq!(arr.get(&[1, 0]), Some(&42));
538        // Other elements unchanged
539        assert_eq!(arr.get(&[0, 0]), Some(&1));
540        assert_eq!(arr.get(&[0, 1]), Some(&3));
541        assert_eq!(arr.get(&[1, 1]), Some(&4));
542    }
543
544    // -- push_column --------------------------------------------------------
545
546    #[test]
547    fn test_push_column() {
548        let mut arr = ColMajorArray::new(vec![1, 2, 3, 4], vec![2, 2]).unwrap();
549        assert_eq!(arr.ncols(), Some(2));
550
551        arr.push_column(&[5, 6]).unwrap();
552        assert_eq!(arr.ncols(), Some(3));
553        assert_eq!(arr.shape(), &[2, 3]);
554        assert_eq!(arr.len(), 6);
555        assert_eq!(arr.get(&[0, 2]), Some(&5));
556        assert_eq!(arr.get(&[1, 2]), Some(&6));
557    }
558
559    #[test]
560    fn test_push_column_wrong_length() {
561        let mut arr = ColMajorArray::new(vec![1, 2, 3, 4], vec![2, 2]).unwrap();
562        let err = arr.push_column(&[5, 6, 7]).unwrap_err();
563        assert_eq!(
564            err,
565            ColMajorArrayError::ColumnLengthMismatch {
566                expected: 2,
567                actual: 3,
568            }
569        );
570    }
571
572    #[test]
573    fn test_push_column_not_2d() {
574        let mut arr = ColMajorArray::new(vec![1, 2, 3], vec![3]).unwrap();
575        let err = arr.push_column(&[4]).unwrap_err();
576        assert_eq!(err, ColMajorArrayError::Not2D { ndim: 1 });
577    }
578
579    // -- column() slice access ----------------------------------------------
580
581    #[test]
582    fn test_column_access() {
583        let arr = ColMajorArray::new(vec![1, 2, 3, 4, 5, 6], vec![2, 3]).unwrap();
584        assert_eq!(arr.column(0), Some([1, 2].as_slice()));
585        assert_eq!(arr.column(1), Some([3, 4].as_slice()));
586        assert_eq!(arr.column(2), Some([5, 6].as_slice()));
587        assert_eq!(arr.column(3), None); // out of bounds
588    }
589
590    // -- zeros, filled ------------------------------------------------------
591
592    #[test]
593    fn test_zeros() {
594        let arr: ColMajorArray<f64> = ColMajorArray::zeros(vec![3, 2]).unwrap();
595        assert_eq!(arr.len(), 6);
596        assert!(arr.data().iter().all(|&v| v == 0.0));
597    }
598
599    #[test]
600    fn test_filled() {
601        let arr = ColMajorArray::filled(vec![2, 3], 7i32).unwrap();
602        assert_eq!(arr.len(), 6);
603        assert!(arr.data().iter().all(|&v| v == 7));
604    }
605
606    // -- Shape mismatch error -----------------------------------------------
607
608    #[test]
609    fn test_shape_mismatch() {
610        let result = ColMajorArray::new(vec![1, 2, 3], vec![2, 2]);
611        assert_eq!(
612            result.unwrap_err(),
613            ColMajorArrayError::ShapeMismatch {
614                shape: vec![2, 2],
615                expected: 4,
616                actual: 3,
617            }
618        );
619    }
620
621    // -- Out-of-bounds -> None ----------------------------------------------
622
623    #[test]
624    fn test_out_of_bounds() {
625        let arr = ColMajorArray::new(vec![1, 2, 3, 4], vec![2, 2]).unwrap();
626        // Index out of range
627        assert_eq!(arr.get(&[2, 0]), None);
628        assert_eq!(arr.get(&[0, 2]), None);
629        // Wrong number of indices
630        assert_eq!(arr.get(&[0]), None);
631        assert_eq!(arr.get(&[0, 0, 0]), None);
632    }
633
634    // -- Ref and Mut views --------------------------------------------------
635
636    #[test]
637    fn test_as_ref() {
638        let arr = ColMajorArray::new(vec![10, 20, 30, 40], vec![2, 2]).unwrap();
639        let view = arr.as_ref();
640        assert_eq!(view.ndim(), 2);
641        assert_eq!(view.shape(), &[2, 2]);
642        assert_eq!(view.get(&[1, 1]), Some(&40));
643        assert_eq!(view.data(), arr.data());
644    }
645
646    #[test]
647    fn test_as_mut() {
648        let mut arr = ColMajorArray::new(vec![10, 20, 30, 40], vec![2, 2]).unwrap();
649        {
650            let mut view = arr.as_mut();
651            if let Some(v) = view.get_mut(&[0, 1]) {
652                *v = 99;
653            }
654        }
655        assert_eq!(arr.get(&[0, 1]), Some(&99));
656    }
657
658    // -- into_data ----------------------------------------------------------
659
660    #[test]
661    fn test_into_data() {
662        let arr = ColMajorArray::new(vec![1, 2, 3], vec![3]).unwrap();
663        let data = arr.into_data();
664        assert_eq!(data, vec![1, 2, 3]);
665    }
666
667    // -- Empty arrays -------------------------------------------------------
668
669    #[test]
670    fn test_empty_array() {
671        let arr: ColMajorArray<i32> = ColMajorArray::new(vec![], vec![0]).unwrap();
672        assert!(arr.is_empty());
673        assert_eq!(arr.len(), 0);
674        assert_eq!(arr.ndim(), 1);
675        assert_eq!(arr.nrows(), None);
676        assert_eq!(arr.ncols(), None);
677    }
678
679    #[test]
680    fn test_empty_2d_array() {
681        let arr: ColMajorArray<i32> = ColMajorArray::new(vec![], vec![3, 0]).unwrap();
682        assert!(arr.is_empty());
683        assert_eq!(arr.len(), 0);
684        assert_eq!(arr.nrows(), Some(3));
685        assert_eq!(arr.ncols(), Some(0));
686    }
687
688    // -- ColMajorArrayRef construction --------------------------------------
689
690    #[test]
691    fn test_ref_new() {
692        let data = [1, 2, 3, 4, 5, 6];
693        let shape = [2, 3];
694        let view = ColMajorArrayRef::new(&data, &shape).unwrap();
695        assert_eq!(view.ndim(), 2);
696        assert_eq!(view.len(), 6);
697        assert_eq!(view.get(&[1, 2]), Some(&6));
698    }
699
700    // -- ColMajorArrayMut construction --------------------------------------
701
702    #[test]
703    fn test_mut_new() {
704        let mut data = [1, 2, 3, 4, 5, 6];
705        let shape = [2, 3];
706        let mut view = ColMajorArrayMut::new(&mut data, &shape).unwrap();
707        assert_eq!(view.ndim(), 2);
708        assert_eq!(view.len(), 6);
709        *view.get_mut(&[0, 0]).unwrap() = 100;
710        assert_eq!(view.get(&[0, 0]), Some(&100));
711    }
712
713    // -- Overflow detection ---------------------------------------------------
714
715    #[test]
716    fn test_new_rejects_overflow_shape() {
717        let result = ColMajorArray::<u8>::new(vec![], vec![usize::MAX, 2]);
718        assert!(
719            matches!(result, Err(ColMajorArrayError::ShapeOverflow { .. })),
720            "expected ShapeOverflow, got {:?}",
721            result
722        );
723    }
724
725    #[test]
726    fn test_filled_rejects_overflow_shape() {
727        let result = ColMajorArray::filled(vec![usize::MAX, 2], 0u8);
728        assert!(
729            matches!(result, Err(ColMajorArrayError::ShapeOverflow { .. })),
730            "expected ShapeOverflow, got {:?}",
731            result
732        );
733    }
734
735    #[test]
736    fn test_zeros_rejects_overflow_shape() {
737        let result = ColMajorArray::<u8>::zeros(vec![usize::MAX, 2]);
738        assert!(
739            matches!(result, Err(ColMajorArrayError::ShapeOverflow { .. })),
740            "expected ShapeOverflow, got {:?}",
741            result
742        );
743    }
744}