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