Skip to main content

strided_view/
view.rs

1//! Julia-like dynamic-rank strided view types.
2//!
3//! This module provides the canonical view types for strided operations,
4//! matching Julia's StridedViews.jl data model:
5//!
6//! - [`StridedView`]: Immutable dynamic-rank strided view with lazy element operations
7//! - [`StridedViewMut`]: Mutable dynamic-rank strided view (Identity op only)
8//! - [`StridedArray`]: Owned strided multidimensional array
9//!
10//! A view's shape and strides are `Cow<'a, [_]>`: [`StridedView::new`] borrows
11//! the caller's layout and allocates nothing, while [`StridedView::new_owned`]
12//! (and every derived view, such as [`StridedView::permute`]) owns the layout it
13//! computed. Neither touches a reference count.
14
15use std::borrow::Cow;
16use std::marker::PhantomData;
17use std::ops::{Index, IndexMut};
18
19use crate::element_op::{ComposableElementOp, ElementOp, ElementOpApply, Identity};
20use crate::{Result, StridedError};
21
22#[inline]
23fn empty_layout(dims: &[usize]) -> bool {
24    dims.iter().any(|&dim| dim == 0)
25}
26
27#[inline]
28fn empty_const_ptr<T>() -> *const T {
29    core::ptr::NonNull::<T>::dangling().as_ptr()
30}
31
32#[inline]
33fn empty_mut_ptr<T>() -> *mut T {
34    core::ptr::NonNull::<T>::dangling().as_ptr()
35}
36
37// ============================================================================
38// Validation helpers
39// ============================================================================
40
41/// Validate that all accessed offsets stay within `[0, len)`.
42pub(crate) fn validate_bounds(
43    len: usize,
44    dims: &[usize],
45    strides: &[isize],
46    offset: isize,
47) -> Result<()> {
48    if dims.len() != strides.len() {
49        return Err(StridedError::StrideLengthMismatch);
50    }
51    // Empty array - no access needed
52    if dims.iter().any(|&d| d == 0) {
53        return Ok(());
54    }
55    // Compute min and max offsets
56    let mut min_offset = offset;
57    let mut max_offset = offset;
58    for (&dim, &stride) in dims.iter().zip(strides.iter()) {
59        if dim > 1 {
60            let last = dim
61                .checked_sub(1)
62                .and_then(|value| isize::try_from(value).ok())
63                .ok_or(StridedError::OffsetOverflow)?;
64            let end = stride
65                .checked_mul(last)
66                .ok_or(StridedError::OffsetOverflow)?;
67            if end >= 0 {
68                max_offset = max_offset
69                    .checked_add(end)
70                    .ok_or(StridedError::OffsetOverflow)?;
71            } else {
72                min_offset = min_offset
73                    .checked_add(end)
74                    .ok_or(StridedError::OffsetOverflow)?;
75            }
76        }
77    }
78    if min_offset < 0 || max_offset < 0 {
79        return Err(StridedError::OffsetOverflow);
80    }
81    if max_offset as usize >= len {
82        return Err(StridedError::OffsetOverflow);
83    }
84    Ok(())
85}
86
87/// Compute column-major strides (Julia default: first index varies fastest).
88pub fn col_major_strides(dims: &[usize]) -> Vec<isize> {
89    let rank = dims.len();
90    if rank == 0 {
91        return vec![];
92    }
93    let mut strides = vec![1isize; rank];
94    for i in 1..rank {
95        strides[i] = strides[i - 1] * dims[i - 1] as isize;
96    }
97    strides
98}
99
100/// Compute row-major strides (C default: last index varies fastest).
101pub fn row_major_strides(dims: &[usize]) -> Vec<isize> {
102    let rank = dims.len();
103    if rank == 0 {
104        return vec![];
105    }
106    let mut strides = vec![1isize; rank];
107    for i in (0..rank - 1).rev() {
108        strides[i] = strides[i + 1] * dims[i + 1] as isize;
109    }
110    strides
111}
112
113// ============================================================================
114// StridedView
115// ============================================================================
116
117/// Dynamic-rank immutable strided view with lazy element operations.
118///
119/// This is the Julia-equivalent `StridedView` type with:
120/// - Dynamic rank, with metadata borrowed ([`StridedView::new`], no allocation)
121///   or owned ([`StridedView::new_owned`], for computed metadata)
122/// - Lazy element operations via the `Op` type parameter
123/// - Zero-copy transformations (permute, transpose, adjoint, conj)
124///
125/// # Type Parameters
126/// - `'a`: Lifetime of the underlying data and of borrowed metadata
127/// - `T`: Element type
128/// - `Op`: Element operation applied lazily on access (default: `Identity`)
129pub struct StridedView<'a, T, Op = Identity> {
130    ptr: *const T,
131    data: &'a [T],
132    dims: Cow<'a, [usize]>,
133    strides: Cow<'a, [isize]>,
134    offset: isize,
135    _op: PhantomData<Op>,
136}
137
138unsafe impl<T: Send, Op: Send> Send for StridedView<'_, T, Op> {}
139unsafe impl<T: Sync, Op: Sync> Sync for StridedView<'_, T, Op> {}
140
141impl<T, Op> Clone for StridedView<'_, T, Op> {
142    fn clone(&self) -> Self {
143        Self {
144            ptr: self.ptr,
145            data: self.data,
146            dims: self.dims.clone(),
147            strides: self.strides.clone(),
148            offset: self.offset,
149            _op: PhantomData,
150        }
151    }
152}
153
154impl<T: std::fmt::Debug, Op> std::fmt::Debug for StridedView<'_, T, Op> {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        f.debug_struct("StridedView")
157            .field("dims", &self.dims)
158            .field("strides", &self.strides)
159            .field("offset", &self.offset)
160            .finish()
161    }
162}
163
164/// The cached base pointer of a view over `data` at `offset`.
165///
166/// An empty layout gets the dangling pointer rather than `data + offset`: a
167/// zero-sized extent may sit at an offset no element of `data` reaches.
168#[inline]
169fn base_ptr<T>(data: &[T], dims: &[usize], offset: isize) -> *const T {
170    if empty_layout(dims) {
171        empty_const_ptr()
172    } else {
173        unsafe { data.as_ptr().offset(offset) }
174    }
175}
176
177/// A view from already-validated metadata, with the element op chosen here.
178#[inline]
179fn from_meta<'a, T, Op>(
180    data: &'a [T],
181    dims: Cow<'a, [usize]>,
182    strides: Cow<'a, [isize]>,
183    offset: isize,
184) -> StridedView<'a, T, Op> {
185    StridedView {
186        ptr: base_ptr(data, &dims, offset),
187        data,
188        dims,
189        strides,
190        offset,
191        _op: PhantomData,
192    }
193}
194
195impl<'a, T, Op> StridedView<'a, T, Op> {
196    /// Create an immutable strided view that borrows the caller's metadata.
197    ///
198    /// This is the allocation-free constructor: `dims` and `strides` must
199    /// outlive the view, which every caller that keeps its layouts does.
200    pub fn new(
201        data: &'a [T],
202        dims: &'a [usize],
203        strides: &'a [isize],
204        offset: isize,
205    ) -> Result<Self> {
206        validate_bounds(data.len(), dims, strides, offset)?;
207        Ok(from_meta(
208            data,
209            Cow::Borrowed(dims),
210            Cow::Borrowed(strides),
211            offset,
212        ))
213    }
214
215    /// Create an immutable strided view that owns computed metadata.
216    ///
217    /// For a caller that computes `dims`/`strides` next to the view, as a
218    /// helper returning a view must. Prefer [`StridedView::new`] otherwise.
219    pub fn new_owned(
220        data: &'a [T],
221        dims: Vec<usize>,
222        strides: Vec<isize>,
223        offset: isize,
224    ) -> Result<Self> {
225        validate_bounds(data.len(), &dims, &strides, offset)?;
226        Ok(from_meta(
227            data,
228            Cow::Owned(dims),
229            Cow::Owned(strides),
230            offset,
231        ))
232    }
233
234    /// Create a view without bounds checking, borrowing the caller's metadata.
235    ///
236    /// # Safety
237    /// The caller must ensure all index combinations stay within bounds.
238    pub unsafe fn new_unchecked(
239        data: &'a [T],
240        dims: &'a [usize],
241        strides: &'a [isize],
242        offset: isize,
243    ) -> Self {
244        from_meta(data, Cow::Borrowed(dims), Cow::Borrowed(strides), offset)
245    }
246
247    /// Create a view without bounds checking, owning the metadata.
248    ///
249    /// # Safety
250    /// As [`StridedView::new_unchecked`].
251    pub unsafe fn new_owned_unchecked(
252        data: &'a [T],
253        dims: Vec<usize>,
254        strides: Vec<isize>,
255        offset: isize,
256    ) -> Self {
257        from_meta(data, Cow::Owned(dims), Cow::Owned(strides), offset)
258    }
259
260    /// Returns the shape (dimension sizes) of this view.
261    #[inline]
262    pub fn dims(&self) -> &[usize] {
263        &self.dims
264    }
265
266    /// Returns the strides (in units of `T`) for each dimension.
267    #[inline]
268    pub fn strides(&self) -> &[isize] {
269        &self.strides
270    }
271
272    /// Returns the byte offset into the backing data.
273    #[inline]
274    pub fn offset(&self) -> isize {
275        self.offset
276    }
277
278    /// Returns the number of dimensions (rank).
279    #[inline]
280    pub fn ndim(&self) -> usize {
281        self.dims.len()
282    }
283
284    /// Returns the total number of elements.
285    #[inline]
286    pub fn len(&self) -> usize {
287        self.dims.iter().product()
288    }
289
290    /// Returns `true` if any dimension is zero.
291    #[inline]
292    pub fn is_empty(&self) -> bool {
293        self.dims.iter().any(|&d| d == 0)
294    }
295
296    /// Returns a reference to the backing data slice.
297    #[inline]
298    pub fn data(&self) -> &'a [T] {
299        self.data
300    }
301
302    /// Raw const pointer to element at the view's base offset.
303    #[inline]
304    pub fn ptr(&self) -> *const T {
305        self.ptr
306    }
307
308    /// Permute dimensions.
309    pub fn permute(&self, perm: &[usize]) -> Result<StridedView<'a, T, Op>> {
310        let rank = self.dims.len();
311        if perm.len() != rank {
312            return Err(StridedError::RankMismatch(perm.len(), rank));
313        }
314        let mut seen = vec![false; rank];
315        for &p in perm {
316            if p >= rank {
317                return Err(StridedError::InvalidAxis { axis: p, rank });
318            }
319            if seen[p] {
320                return Err(StridedError::InvalidAxis { axis: p, rank });
321            }
322            seen[p] = true;
323        }
324        let new_dims: Vec<usize> = perm.iter().map(|&p| self.dims[p]).collect();
325        let new_strides: Vec<isize> = perm.iter().map(|&p| self.strides[p]).collect();
326        Ok(from_meta(
327            self.data,
328            Cow::Owned(new_dims),
329            Cow::Owned(new_strides),
330            self.offset,
331        ))
332    }
333
334    /// Create a diagonal view by fusing repeated axis pairs via stride trick (zero-copy).
335    ///
336    /// For each pair `(a, b)`:
337    /// - New stride = `strides[a] + strides[b]`
338    /// - New dim = `min(dims[a], dims[b])`
339    /// - The higher-numbered axis is removed
340    /// - Pairs use **original** axis numbering
341    ///
342    /// # Example
343    /// `A[i,i,j]` shape=`[n,n,m]` strides=`[s0,s1,s2]` -> shape=`[n,m]` strides=`[s0+s1, s2]`
344    pub fn diagonal_view(&self, axis_pairs: &[(usize, usize)]) -> Result<StridedView<'a, T, Op>> {
345        let ndim = self.ndim();
346        let mut dims: Vec<usize> = self.dims().to_vec();
347        let mut strides: Vec<isize> = self.strides().to_vec();
348
349        let mut axes_to_remove = Vec::new();
350        for &(a, b) in axis_pairs {
351            let (lo, hi) = if a < b { (a, b) } else { (b, a) };
352            if lo >= ndim || hi >= ndim {
353                return Err(StridedError::InvalidAxis {
354                    axis: hi,
355                    rank: ndim,
356                });
357            }
358            if lo == hi {
359                return Err(StridedError::InvalidAxis {
360                    axis: lo,
361                    rank: ndim,
362                });
363            }
364            strides[lo] += strides[hi];
365            dims[lo] = dims[lo].min(dims[hi]);
366            axes_to_remove.push(hi);
367        }
368
369        axes_to_remove.sort_unstable();
370        axes_to_remove.dedup();
371        for &ax in axes_to_remove.iter().rev() {
372            dims.remove(ax);
373            strides.remove(ax);
374        }
375
376        Ok(from_meta(
377            self.data(),
378            Cow::Owned(dims),
379            Cow::Owned(strides),
380            self.offset(),
381        ))
382    }
383
384    /// Broadcast this view to a target shape.
385    ///
386    /// Size-1 dimensions are expanded (stride set to 0) to match target.
387    pub fn broadcast(&self, target_dims: &[usize]) -> Result<StridedView<'a, T, Op>> {
388        if self.dims.len() != target_dims.len() {
389            return Err(StridedError::RankMismatch(
390                self.dims.len(),
391                target_dims.len(),
392            ));
393        }
394        let mut new_strides = Vec::with_capacity(self.dims.len());
395        for i in 0..self.dims.len() {
396            if self.dims[i] == target_dims[i] {
397                new_strides.push(self.strides[i]);
398            } else if self.dims[i] == 1 {
399                new_strides.push(0);
400            } else {
401                return Err(StridedError::ShapeMismatch(
402                    self.dims.to_vec(),
403                    target_dims.to_vec(),
404                ));
405            }
406        }
407        Ok(from_meta(
408            self.data,
409            Cow::Owned(target_dims.to_vec()),
410            Cow::Owned(new_strides),
411            self.offset,
412        ))
413    }
414}
415
416/// Composition methods: require `T: ElementOpApply` and `Op: ComposableElementOp<T>`.
417impl<'a, T: Copy + ElementOpApply, Op: ComposableElementOp<T>> StridedView<'a, T, Op> {
418    /// Transpose a 2D view: reverses dimensions and composes the Transpose element op.
419    ///
420    /// Julia equivalent: `Base.transpose(a::AbstractStridedView{<:Any, 2})`
421    pub fn transpose_2d(&self) -> Result<StridedView<'a, T, Op::ComposeTranspose>> {
422        if self.dims.len() != 2 {
423            return Err(StridedError::RankMismatch(self.dims.len(), 2));
424        }
425        Ok(from_meta(
426            self.data,
427            Cow::Owned(vec![self.dims[1], self.dims[0]]),
428            Cow::Owned(vec![self.strides[1], self.strides[0]]),
429            self.offset,
430        ))
431    }
432
433    /// Adjoint (conjugate transpose) of a 2D view.
434    ///
435    /// Julia equivalent: `Base.adjoint(a::AbstractStridedView{<:Any, 2})`
436    pub fn adjoint_2d(&self) -> Result<StridedView<'a, T, Op::ComposeAdjoint>> {
437        if self.dims.len() != 2 {
438            return Err(StridedError::RankMismatch(self.dims.len(), 2));
439        }
440        Ok(from_meta(
441            self.data,
442            Cow::Owned(vec![self.dims[1], self.dims[0]]),
443            Cow::Owned(vec![self.strides[1], self.strides[0]]),
444            self.offset,
445        ))
446    }
447
448    /// Complex conjugate (compose Conj without changing dims/strides).
449    ///
450    /// Julia equivalent: `Base.conj(a::AbstractStridedView)`
451    pub fn conj(&self) -> StridedView<'a, T, Op::ComposeConj> {
452        StridedView {
453            ptr: self.ptr,
454            data: self.data,
455            dims: self.dims.clone(),
456            strides: self.strides.clone(),
457            offset: self.offset,
458            _op: PhantomData,
459        }
460    }
461}
462
463/// Element access: requires `Op: ElementOp<T>`.
464impl<'a, T: Copy, Op: ElementOp<T>> StridedView<'a, T, Op> {
465    /// Get an element with the element operation applied.
466    pub fn get(&self, indices: &[usize]) -> T {
467        assert_eq!(indices.len(), self.dims.len(), "wrong number of indices");
468        let mut idx = 0isize;
469        for (i, &index) in indices.iter().enumerate() {
470            assert!(
471                index < self.dims[i],
472                "index {} out of bounds for dim {}",
473                index,
474                self.dims[i]
475            );
476            idx += index as isize * self.strides[i];
477        }
478        Op::apply(unsafe { *self.ptr.offset(idx) })
479    }
480
481    /// Get an element without bounds checking.
482    ///
483    /// # Safety
484    /// Caller must ensure indices are within bounds.
485    #[inline]
486    pub unsafe fn get_unchecked(&self, indices: &[usize]) -> T {
487        let mut idx = 0isize;
488        for (i, &index) in indices.iter().enumerate() {
489            idx += index as isize * self.strides[i];
490        }
491        Op::apply(*self.ptr.offset(idx))
492    }
493}
494
495// ============================================================================
496// StridedViewMut
497// ============================================================================
498
499/// Dynamic-rank mutable strided view.
500///
501/// Always uses `Identity` element operation for write simplicity.
502/// Julia typically applies ops on the read side.
503pub struct StridedViewMut<'a, T> {
504    ptr: *mut T,
505    data: &'a mut [T],
506    dims: Cow<'a, [usize]>,
507    strides: Cow<'a, [isize]>,
508    offset: isize,
509}
510
511unsafe impl<T: Send> Send for StridedViewMut<'_, T> {}
512
513impl<T: std::fmt::Debug> std::fmt::Debug for StridedViewMut<'_, T> {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        f.debug_struct("StridedViewMut")
516            .field("dims", &self.dims)
517            .field("strides", &self.strides)
518            .field("offset", &self.offset)
519            .finish()
520    }
521}
522
523impl<'a, T> StridedViewMut<'a, T> {
524    /// A view from already-validated metadata.
525    #[inline]
526    fn from_meta(
527        data: &'a mut [T],
528        dims: Cow<'a, [usize]>,
529        strides: Cow<'a, [isize]>,
530        offset: isize,
531    ) -> Self {
532        let ptr = if empty_layout(&dims) {
533            empty_mut_ptr()
534        } else {
535            unsafe { data.as_mut_ptr().offset(offset) }
536        };
537        Self {
538            ptr,
539            data,
540            dims,
541            strides,
542            offset,
543        }
544    }
545
546    /// Create a mutable view that borrows the caller's metadata: no allocation.
547    pub fn new(
548        data: &'a mut [T],
549        dims: &'a [usize],
550        strides: &'a [isize],
551        offset: isize,
552    ) -> Result<Self> {
553        validate_bounds(data.len(), dims, strides, offset)?;
554        Ok(Self::from_meta(
555            data,
556            Cow::Borrowed(dims),
557            Cow::Borrowed(strides),
558            offset,
559        ))
560    }
561
562    /// Create without bounds checking, borrowing the caller's metadata.
563    ///
564    /// # Safety
565    /// Caller must ensure all index combinations stay within bounds.
566    pub unsafe fn new_unchecked(
567        data: &'a mut [T],
568        dims: &'a [usize],
569        strides: &'a [isize],
570        offset: isize,
571    ) -> Self {
572        Self::from_meta(data, Cow::Borrowed(dims), Cow::Borrowed(strides), offset)
573    }
574
575    /// Returns the shape (dimension sizes) of this view.
576    #[inline]
577    pub fn dims(&self) -> &[usize] {
578        &self.dims
579    }
580
581    /// Returns the strides (in units of `T`) for each dimension.
582    #[inline]
583    pub fn strides(&self) -> &[isize] {
584        &self.strides
585    }
586
587    /// Returns the byte offset into the backing data.
588    #[inline]
589    pub fn offset(&self) -> isize {
590        self.offset
591    }
592
593    /// Returns the number of dimensions (rank).
594    #[inline]
595    pub fn ndim(&self) -> usize {
596        self.dims.len()
597    }
598
599    /// Returns the total number of elements.
600    #[inline]
601    pub fn len(&self) -> usize {
602        self.dims.iter().product()
603    }
604
605    /// Returns `true` if any dimension is zero.
606    #[inline]
607    pub fn is_empty(&self) -> bool {
608        self.dims.iter().any(|&d| d == 0)
609    }
610
611    /// Raw const pointer to element at the view's base offset.
612    #[inline]
613    pub fn ptr(&self) -> *const T {
614        self.ptr as *const T
615    }
616
617    /// Raw mutable pointer to element at the view's base offset.
618    #[inline]
619    pub fn as_mut_ptr(&self) -> *mut T {
620        self.ptr
621    }
622
623    /// Returns a reference to the backing data slice.
624    #[inline]
625    pub fn data(&self) -> &[T] {
626        &self.data
627    }
628
629    /// Returns a mutable reference to the backing data slice.
630    #[inline]
631    pub fn data_mut(&mut self) -> &mut [T] {
632        &mut self.data
633    }
634
635    /// Permute dimensions, consuming the mutable view.
636    ///
637    /// Returns a new mutable view with reordered dimensions and strides.
638    /// Takes `self` by value to prevent aliasing of mutable views.
639    pub fn permute(self, perm: &[usize]) -> Result<StridedViewMut<'a, T>> {
640        let rank = self.dims.len();
641        if perm.len() != rank {
642            return Err(StridedError::RankMismatch(perm.len(), rank));
643        }
644        let mut seen = vec![false; rank];
645        for &p in perm {
646            if p >= rank {
647                return Err(StridedError::InvalidAxis { axis: p, rank });
648            }
649            if seen[p] {
650                return Err(StridedError::InvalidAxis { axis: p, rank });
651            }
652            seen[p] = true;
653        }
654        let new_dims: Vec<usize> = perm.iter().map(|&p| self.dims[p]).collect();
655        let new_strides: Vec<isize> = perm.iter().map(|&p| self.strides[p]).collect();
656        Ok(Self::from_meta(
657            self.data,
658            Cow::Owned(new_dims),
659            Cow::Owned(new_strides),
660            self.offset,
661        ))
662    }
663
664    /// Reborrow as an immutable view.
665    ///
666    /// The reborrow shares this view's metadata by borrowing it, so it allocates
667    /// nothing even when the metadata is owned.
668    pub fn as_view(&self) -> StridedView<'_, T, Identity> {
669        StridedView {
670            ptr: self.ptr as *const T,
671            data: unsafe { std::slice::from_raw_parts(self.data.as_ptr(), self.data.len()) },
672            dims: Cow::Borrowed(self.dims.as_ref()),
673            strides: Cow::Borrowed(self.strides.as_ref()),
674            offset: self.offset,
675            _op: PhantomData,
676        }
677    }
678}
679
680impl<'a, T: Copy> StridedViewMut<'a, T> {
681    /// Get an element.
682    pub fn get(&self, indices: &[usize]) -> T {
683        assert_eq!(indices.len(), self.dims.len());
684        let mut idx = 0isize;
685        for (i, &index) in indices.iter().enumerate() {
686            assert!(index < self.dims[i]);
687            idx += index as isize * self.strides[i];
688        }
689        unsafe { *self.ptr.offset(idx) }
690    }
691
692    /// Set an element.
693    pub fn set(&mut self, indices: &[usize], value: T) {
694        assert_eq!(indices.len(), self.dims.len());
695        let mut idx = 0isize;
696        for (i, &index) in indices.iter().enumerate() {
697            assert!(index < self.dims[i]);
698            idx += index as isize * self.strides[i];
699        }
700        unsafe {
701            *self.ptr.offset(idx) = value;
702        }
703    }
704}
705
706// ============================================================================
707// StridedArray
708// ============================================================================
709
710/// Owned strided multidimensional array.
711///
712/// Supports both column-major (Julia default) and row-major (C default) layouts.
713pub struct StridedArray<T> {
714    data: Vec<T>,
715    dims: Vec<usize>,
716    strides: Vec<isize>,
717    offset: isize,
718}
719
720impl<T: std::fmt::Debug> std::fmt::Debug for StridedArray<T> {
721    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722        f.debug_struct("StridedArray")
723            .field("dims", &self.dims)
724            .field("strides", &self.strides)
725            .field("offset", &self.offset)
726            .finish()
727    }
728}
729
730impl<T: Clone> Clone for StridedArray<T> {
731    fn clone(&self) -> Self {
732        Self {
733            data: self.data.clone(),
734            dims: self.dims.clone(),
735            strides: self.strides.clone(),
736            offset: self.offset,
737        }
738    }
739}
740
741impl<T: Clone + Default> StridedArray<T> {
742    /// Create a column-major (Julia default) tensor filled with Default values.
743    pub fn col_major(dims: &[usize]) -> Self {
744        let total: usize = dims.iter().product();
745        let data = vec![T::default(); total];
746        let strides = col_major_strides(dims);
747        Self {
748            data,
749            dims: dims.to_vec(),
750            strides,
751            offset: 0,
752        }
753    }
754
755    /// Create a row-major (C default) tensor filled with Default values.
756    pub fn row_major(dims: &[usize]) -> Self {
757        let total: usize = dims.iter().product();
758        let data = vec![T::default(); total];
759        let strides = row_major_strides(dims);
760        Self {
761            data,
762            dims: dims.to_vec(),
763            strides,
764            offset: 0,
765        }
766    }
767
768    /// Create a column-major tensor with values produced by a function.
769    ///
770    /// The function is called with indices in column-major iteration order.
771    pub fn from_fn_col_major(dims: &[usize], mut f: impl FnMut(&[usize]) -> T) -> Self {
772        let total: usize = dims.iter().product();
773        let strides = col_major_strides(dims);
774        let rank = dims.len();
775        let mut data = Vec::with_capacity(total);
776        let mut idx = vec![0usize; rank];
777        for _ in 0..total {
778            data.push(f(&idx));
779            for d in 0..rank {
780                idx[d] += 1;
781                if idx[d] < dims[d] {
782                    break;
783                }
784                idx[d] = 0;
785            }
786        }
787        Self {
788            data,
789            dims: dims.to_vec(),
790            strides,
791            offset: 0,
792        }
793    }
794
795    /// Create a row-major tensor with values produced by a function.
796    ///
797    /// The function is called with indices in row-major iteration order.
798    pub fn from_fn_row_major(dims: &[usize], mut f: impl FnMut(&[usize]) -> T) -> Self {
799        let total: usize = dims.iter().product();
800        let strides = row_major_strides(dims);
801        let rank = dims.len();
802        let mut data = Vec::with_capacity(total);
803        let mut idx = vec![0usize; rank];
804        for _ in 0..total {
805            data.push(f(&idx));
806            for d in (0..rank).rev() {
807                idx[d] += 1;
808                if idx[d] < dims[d] {
809                    break;
810                }
811                idx[d] = 0;
812            }
813        }
814        Self {
815            data,
816            dims: dims.to_vec(),
817            strides,
818            offset: 0,
819        }
820    }
821}
822
823impl<T> StridedArray<T> {
824    /// Create from raw parts.
825    pub fn from_parts(
826        data: Vec<T>,
827        dims: &[usize],
828        strides: &[isize],
829        offset: isize,
830    ) -> Result<Self> {
831        validate_bounds(data.len(), dims, strides, offset)?;
832        Ok(Self {
833            data,
834            dims: dims.to_vec(),
835            strides: strides.to_vec(),
836            offset,
837        })
838    }
839
840    /// Returns the shape (dimension sizes) of this array.
841    #[inline]
842    pub fn dims(&self) -> &[usize] {
843        &self.dims
844    }
845
846    /// Returns the strides (in units of `T`) for each dimension.
847    #[inline]
848    pub fn strides(&self) -> &[isize] {
849        &self.strides
850    }
851
852    /// Returns the number of dimensions (rank).
853    #[inline]
854    pub fn ndim(&self) -> usize {
855        self.dims.len()
856    }
857
858    /// Returns the total number of elements.
859    #[inline]
860    pub fn len(&self) -> usize {
861        self.dims.iter().product()
862    }
863
864    /// Returns `true` if any dimension is zero.
865    #[inline]
866    pub fn is_empty(&self) -> bool {
867        self.dims.iter().any(|&d| d == 0)
868    }
869
870    /// Returns a reference to the backing data slice.
871    #[inline]
872    pub fn data(&self) -> &[T] {
873        &self.data
874    }
875
876    /// Returns a mutable reference to the backing data slice.
877    #[inline]
878    pub fn data_mut(&mut self) -> &mut [T] {
879        &mut self.data
880    }
881
882    /// Create an immutable view over this tensor.
883    pub fn view(&self) -> StridedView<'_, T> {
884        let ptr = if self.is_empty() {
885            empty_const_ptr()
886        } else {
887            unsafe { self.data.as_ptr().offset(self.offset) }
888        };
889        StridedView {
890            ptr,
891            data: &self.data,
892            dims: Cow::Borrowed(&self.dims),
893            strides: Cow::Borrowed(&self.strides),
894            offset: self.offset,
895            _op: PhantomData,
896        }
897    }
898
899    /// Create a mutable view over this tensor.
900    pub fn view_mut(&mut self) -> StridedViewMut<'_, T> {
901        let ptr = if self.is_empty() {
902            empty_mut_ptr()
903        } else {
904            unsafe { self.data.as_mut_ptr().offset(self.offset) }
905        };
906        StridedViewMut {
907            ptr,
908            data: &mut self.data,
909            dims: Cow::Borrowed(&self.dims),
910            strides: Cow::Borrowed(&self.strides),
911            offset: self.offset,
912        }
913    }
914
915    /// Permute dimensions (metadata-only reorder, no data copy).
916    ///
917    /// Returns a new array with reordered dims and strides.
918    /// The underlying data buffer is not touched.
919    pub fn permuted(self, perm: &[usize]) -> Result<Self> {
920        let rank = self.dims.len();
921        if perm.len() != rank {
922            return Err(StridedError::RankMismatch(perm.len(), rank));
923        }
924        let mut seen = vec![false; rank];
925        for &p in perm {
926            if p >= rank {
927                return Err(StridedError::InvalidAxis { axis: p, rank });
928            }
929            if seen[p] {
930                return Err(StridedError::InvalidAxis { axis: p, rank });
931            }
932            seen[p] = true;
933        }
934        let new_dims: Vec<usize> = perm.iter().map(|&p| self.dims[p]).collect();
935        let new_strides: Vec<isize> = perm.iter().map(|&p| self.strides[p]).collect();
936        Ok(Self {
937            data: self.data,
938            dims: new_dims,
939            strides: new_strides,
940            offset: self.offset,
941        })
942    }
943
944    /// Consume the array and return the backing `Vec<T>`.
945    pub fn into_data(self) -> Vec<T> {
946        self.data
947    }
948
949    /// Iterate over all elements in memory order.
950    pub fn iter(&self) -> std::slice::Iter<'_, T> {
951        self.data.iter()
952    }
953
954    /// Mutable iteration over all elements in memory order.
955    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
956        self.data.iter_mut()
957    }
958}
959
960impl<T: Default> StridedArray<T> {
961    /// Create a column-major tensor reusing an existing buffer.
962    ///
963    /// If `buf` has at least `product(dims)` elements, it is truncated and
964    /// zeroed.  Otherwise a fresh buffer is allocated.
965    pub fn col_major_from_buffer(mut buf: Vec<T>, dims: &[usize]) -> Self {
966        let total: usize = dims.iter().product();
967        if buf.len() >= total {
968            buf.truncate(total);
969        } else {
970            buf.resize_with(total, T::default);
971        }
972        // Zero the reused region
973        for v in buf.iter_mut() {
974            *v = T::default();
975        }
976        let strides = col_major_strides(dims);
977        Self {
978            data: buf,
979            dims: dims.to_vec(),
980            strides,
981            offset: 0,
982        }
983    }
984}
985
986impl<T: Copy> StridedArray<T> {
987    /// Create a column-major tensor with **uninitialized** data.
988    ///
989    /// # Safety
990    /// Caller must write every element before reading.
991    pub unsafe fn col_major_uninit(dims: &[usize]) -> Self {
992        let total: usize = dims.iter().product();
993        let mut data = Vec::with_capacity(total);
994        data.set_len(total);
995        let strides = col_major_strides(dims);
996        Self {
997            data,
998            dims: dims.to_vec(),
999            strides,
1000            offset: 0,
1001        }
1002    }
1003
1004    /// Reuse an existing buffer as a column-major tensor **without zeroing**.
1005    ///
1006    /// # Safety
1007    /// Caller must write every element before reading.
1008    pub unsafe fn col_major_from_buffer_uninit(mut buf: Vec<T>, dims: &[usize]) -> Self {
1009        let total: usize = dims.iter().product();
1010        if buf.capacity() < total {
1011            buf.reserve(total - buf.len());
1012        }
1013        buf.set_len(total);
1014        let strides = col_major_strides(dims);
1015        Self {
1016            data: buf,
1017            dims: dims.to_vec(),
1018            strides,
1019            offset: 0,
1020        }
1021    }
1022}
1023
1024impl<T: Copy> StridedArray<T> {
1025    /// Get an element by multi-dimensional index.
1026    pub fn get(&self, indices: &[usize]) -> T {
1027        self.view().get(indices)
1028    }
1029
1030    /// Set an element by multi-dimensional index.
1031    pub fn set(&mut self, indices: &[usize], value: T) {
1032        assert_eq!(indices.len(), self.dims.len());
1033        let mut idx = self.offset;
1034        for (i, &index) in indices.iter().enumerate() {
1035            assert!(index < self.dims[i]);
1036            idx += index as isize * self.strides[i];
1037        }
1038        self.data[idx as usize] = value;
1039    }
1040}
1041
1042impl<T: Copy> Index<&[usize]> for StridedArray<T> {
1043    type Output = T;
1044
1045    fn index(&self, indices: &[usize]) -> &T {
1046        let mut idx = self.offset;
1047        for (i, &index) in indices.iter().enumerate() {
1048            assert!(index < self.dims[i]);
1049            idx += index as isize * self.strides[i];
1050        }
1051        &self.data[idx as usize]
1052    }
1053}
1054
1055impl<T: Copy> IndexMut<&[usize]> for StridedArray<T> {
1056    fn index_mut(&mut self, indices: &[usize]) -> &mut T {
1057        let mut idx = self.offset;
1058        for (i, &index) in indices.iter().enumerate() {
1059            assert!(index < self.dims[i]);
1060            idx += index as isize * self.strides[i];
1061        }
1062        &mut self.data[idx as usize]
1063    }
1064}
1065
1066// ============================================================================
1067// Tests
1068// ============================================================================
1069
1070#[cfg(test)]
1071mod tests {
1072    use super::*;
1073
1074    #[test]
1075    fn empty_views_use_dangling_base_pointers_for_extreme_offsets() {
1076        let dims = [0usize];
1077        let strides = [1isize];
1078        let data: [f64; 0] = [];
1079        let view = StridedView::<f64>::new(&data, &dims, &strides, isize::MAX).unwrap();
1080        assert_eq!(view.ptr(), empty_const_ptr());
1081
1082        let mut data: [f64; 0] = [];
1083        let view = StridedViewMut::new(&mut data, &dims, &strides, isize::MAX).unwrap();
1084        assert_eq!(view.ptr(), empty_const_ptr());
1085        assert_eq!(view.as_mut_ptr(), empty_mut_ptr());
1086
1087        let array =
1088            StridedArray::<f64>::from_parts(Vec::new(), &dims, &strides, isize::MAX).unwrap();
1089        assert_eq!(array.view().ptr(), empty_const_ptr());
1090
1091        let mut array =
1092            StridedArray::<f64>::from_parts(Vec::new(), &dims, &strides, isize::MAX).unwrap();
1093        let view = array.view_mut();
1094        assert_eq!(view.ptr(), empty_const_ptr());
1095        assert_eq!(view.as_mut_ptr(), empty_mut_ptr());
1096    }
1097    use num_complex::Complex64;
1098
1099    #[test]
1100    fn test_col_major_strides() {
1101        assert_eq!(col_major_strides(&[3, 4]), vec![1, 3]);
1102        assert_eq!(col_major_strides(&[2, 3, 4]), vec![1, 2, 6]);
1103    }
1104
1105    #[test]
1106    fn test_row_major_strides() {
1107        assert_eq!(row_major_strides(&[3, 4]), vec![4, 1]);
1108        assert_eq!(row_major_strides(&[2, 3, 4]), vec![12, 4, 1]);
1109    }
1110
1111    #[test]
1112    fn test_strided_view_new() {
1113        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1114        let view = StridedView::<f64>::new(&data, &[2, 3], &[3, 1], 0).unwrap();
1115        assert_eq!(view.ndim(), 2);
1116        assert_eq!(view.dims(), &[2, 3]);
1117        assert_eq!(view.strides(), &[3, 1]);
1118        assert_eq!(view.len(), 6);
1119    }
1120
1121    #[test]
1122    fn test_strided_view_get() {
1123        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1124        let view = StridedView::<f64>::new(&data, &[2, 3], &[3, 1], 0).unwrap();
1125        assert_eq!(view.get(&[0, 0]), 1.0);
1126        assert_eq!(view.get(&[0, 1]), 2.0);
1127        assert_eq!(view.get(&[0, 2]), 3.0);
1128        assert_eq!(view.get(&[1, 0]), 4.0);
1129        assert_eq!(view.get(&[1, 2]), 6.0);
1130    }
1131
1132    #[test]
1133    fn test_strided_view_col_major() {
1134        // Column-major: strides [1, 2] for 2x3
1135        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1136        let view = StridedView::<f64>::new(&data, &[2, 3], &[1, 2], 0).unwrap();
1137        assert_eq!(view.get(&[0, 0]), 1.0); // data[0]
1138        assert_eq!(view.get(&[1, 0]), 2.0); // data[1]
1139        assert_eq!(view.get(&[0, 1]), 3.0); // data[2]
1140        assert_eq!(view.get(&[1, 1]), 4.0); // data[3]
1141        assert_eq!(view.get(&[0, 2]), 5.0); // data[4]
1142        assert_eq!(view.get(&[1, 2]), 6.0); // data[5]
1143    }
1144
1145    #[test]
1146    fn test_strided_view_permute() {
1147        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1148        let view = StridedView::<f64>::new(&data, &[2, 3], &[3, 1], 0).unwrap();
1149        let perm = view.permute(&[1, 0]).unwrap();
1150        assert_eq!(perm.dims(), &[3, 2]);
1151        assert_eq!(perm.strides(), &[1, 3]);
1152        assert_eq!(perm.get(&[0, 0]), 1.0);
1153        assert_eq!(perm.get(&[1, 0]), 2.0);
1154        assert_eq!(perm.get(&[0, 1]), 4.0);
1155    }
1156
1157    #[test]
1158    fn test_strided_view_transpose_2d() {
1159        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1160        let view = StridedView::<f64>::new(&data, &[2, 3], &[3, 1], 0).unwrap();
1161        let t = view.transpose_2d().unwrap();
1162        assert_eq!(t.dims(), &[3, 2]);
1163        assert_eq!(t.get(&[0, 0]), 1.0);
1164        assert_eq!(t.get(&[1, 0]), 2.0);
1165        assert_eq!(t.get(&[0, 1]), 4.0);
1166    }
1167
1168    #[test]
1169    fn test_strided_view_conj() {
1170        let data = vec![Complex64::new(1.0, 2.0), Complex64::new(3.0, 4.0)];
1171        let view = StridedView::<Complex64>::new(&data, &[2], &[1], 0).unwrap();
1172        let c = view.conj();
1173        assert_eq!(c.get(&[0]), Complex64::new(1.0, -2.0));
1174        assert_eq!(c.get(&[1]), Complex64::new(3.0, -4.0));
1175    }
1176
1177    #[test]
1178    fn test_strided_view_adjoint_2d() {
1179        let data = vec![
1180            Complex64::new(1.0, 2.0),
1181            Complex64::new(3.0, 4.0),
1182            Complex64::new(5.0, 6.0),
1183            Complex64::new(7.0, 8.0),
1184        ];
1185        // 2x2 row-major
1186        let view = StridedView::<Complex64>::new(&data, &[2, 2], &[2, 1], 0).unwrap();
1187        let adj = view.adjoint_2d().unwrap();
1188        assert_eq!(adj.dims(), &[2, 2]);
1189        // Adjoint: conj + transpose
1190        assert_eq!(adj.get(&[0, 0]), Complex64::new(1.0, -2.0));
1191        assert_eq!(adj.get(&[1, 0]), Complex64::new(3.0, -4.0));
1192        assert_eq!(adj.get(&[0, 1]), Complex64::new(5.0, -6.0));
1193    }
1194
1195    #[test]
1196    fn test_strided_view_broadcast() {
1197        let data = vec![1.0, 2.0, 3.0];
1198        let view = StridedView::<f64>::new(&data, &[1, 3], &[3, 1], 0).unwrap();
1199        let broad = view.broadcast(&[4, 3]).unwrap();
1200        assert_eq!(broad.dims(), &[4, 3]);
1201        for i in 0..4 {
1202            assert_eq!(broad.get(&[i, 0]), 1.0);
1203            assert_eq!(broad.get(&[i, 1]), 2.0);
1204            assert_eq!(broad.get(&[i, 2]), 3.0);
1205        }
1206    }
1207
1208    #[test]
1209    fn test_strided_view_mut() {
1210        let mut data = vec![0.0; 6];
1211        {
1212            let mut view = StridedViewMut::<f64>::new(&mut data, &[2, 3], &[3, 1], 0).unwrap();
1213            view.set(&[0, 0], 1.0);
1214            view.set(&[1, 2], 6.0);
1215        }
1216        assert_eq!(data[0], 1.0);
1217        assert_eq!(data[5], 6.0);
1218    }
1219
1220    #[test]
1221    fn test_strided_view_mut_as_view() {
1222        let mut data = vec![1.0, 2.0, 3.0];
1223        let vm = StridedViewMut::<f64>::new(&mut data, &[3], &[1], 0).unwrap();
1224        let v = vm.as_view();
1225        assert_eq!(v.get(&[0]), 1.0);
1226        assert_eq!(v.get(&[2]), 3.0);
1227    }
1228
1229    #[test]
1230    fn test_strided_tensor_col_major() {
1231        let t = StridedArray::<f64>::from_fn_col_major(&[2, 3], |idx| (idx[0] * 3 + idx[1]) as f64);
1232        assert_eq!(t.dims(), &[2, 3]);
1233        assert_eq!(t.strides(), &[1, 2]); // column-major
1234        assert_eq!(t.get(&[0, 0]), 0.0);
1235        assert_eq!(t.get(&[1, 0]), 3.0);
1236        assert_eq!(t.get(&[0, 1]), 1.0);
1237        assert_eq!(t.get(&[1, 2]), 5.0);
1238    }
1239
1240    #[test]
1241    fn test_strided_tensor_row_major() {
1242        let t = StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1]) as f64);
1243        assert_eq!(t.dims(), &[2, 3]);
1244        assert_eq!(t.strides(), &[3, 1]); // row-major
1245        assert_eq!(t.get(&[0, 0]), 0.0);
1246        assert_eq!(t.get(&[0, 1]), 1.0);
1247        assert_eq!(t.get(&[1, 0]), 3.0);
1248        assert_eq!(t.get(&[1, 2]), 5.0);
1249    }
1250
1251    #[test]
1252    fn test_strided_tensor_view() {
1253        let t =
1254            StridedArray::<f64>::from_fn_col_major(&[2, 3], |idx| (idx[0] * 10 + idx[1]) as f64);
1255        let v = t.view();
1256        assert_eq!(v.get(&[0, 0]), 0.0);
1257        assert_eq!(v.get(&[1, 0]), 10.0);
1258        assert_eq!(v.get(&[0, 2]), 2.0);
1259    }
1260
1261    #[test]
1262    fn test_strided_tensor_view_mut() {
1263        let mut t = StridedArray::<f64>::col_major(&[2, 3]);
1264        {
1265            let mut vm = t.view_mut();
1266            vm.set(&[1, 2], 42.0);
1267        }
1268        assert_eq!(t.get(&[1, 2]), 42.0);
1269    }
1270
1271    #[test]
1272    fn test_strided_tensor_index() {
1273        let t =
1274            StridedArray::<f64>::from_fn_row_major(&[3, 4], |idx| (idx[0] * 10 + idx[1]) as f64);
1275        assert_eq!(t[&[0usize, 0] as &[usize]], 0.0);
1276        assert_eq!(t[&[2usize, 3] as &[usize]], 23.0);
1277    }
1278
1279    #[test]
1280    fn test_strided_tensor_index_mut() {
1281        let mut t = StridedArray::<f64>::row_major(&[2, 3]);
1282        t[&[1usize, 2] as &[usize]] = 99.0;
1283        assert_eq!(t.get(&[1, 2]), 99.0);
1284    }
1285
1286    #[test]
1287    fn test_validate_bounds_ok() {
1288        assert!(validate_bounds(6, &[2, 3], &[3, 1], 0).is_ok());
1289        assert!(validate_bounds(6, &[2, 3], &[1, 2], 0).is_ok());
1290    }
1291
1292    #[test]
1293    fn test_validate_bounds_out_of_range() {
1294        assert!(validate_bounds(5, &[2, 3], &[3, 1], 0).is_err());
1295    }
1296
1297    #[test]
1298    fn test_validate_bounds_empty() {
1299        assert!(validate_bounds(0, &[0, 3], &[3, 1], 0).is_ok());
1300    }
1301
1302    #[test]
1303    fn test_validate_bounds_with_offset() {
1304        assert!(validate_bounds(7, &[2, 3], &[3, 1], 1).is_ok());
1305        assert!(validate_bounds(6, &[2, 3], &[3, 1], 1).is_err());
1306    }
1307
1308    #[test]
1309    fn test_strided_tensor_3d() {
1310        let t = StridedArray::<f64>::from_fn_col_major(&[2, 3, 4], |idx| {
1311            (idx[0] * 100 + idx[1] * 10 + idx[2]) as f64
1312        });
1313        assert_eq!(t.ndim(), 3);
1314        assert_eq!(t.strides(), &[1, 2, 6]); // column-major
1315        assert_eq!(t.get(&[0, 0, 0]), 0.0);
1316        assert_eq!(t.get(&[1, 0, 0]), 100.0);
1317        assert_eq!(t.get(&[0, 1, 0]), 10.0);
1318        assert_eq!(t.get(&[0, 0, 1]), 1.0);
1319        assert_eq!(t.get(&[1, 2, 3]), 123.0);
1320    }
1321
1322    #[test]
1323    fn test_diagonal_view_2d() {
1324        // A[i,i] shape=[3,3] row-major strides=[3,1]
1325        // diagonal: shape=[3] strides=[4] (3+1)
1326        let data: Vec<f64> = (0..9).map(|x| x as f64).collect();
1327        let view = StridedView::<f64>::new(&data, &[3, 3], &[3, 1], 0).unwrap();
1328        let diag = view.diagonal_view(&[(0, 1)]).unwrap();
1329        assert_eq!(diag.dims(), &[3]);
1330        assert_eq!(diag.strides(), &[4]);
1331        assert_eq!(diag.get(&[0]), 0.0); // A[0,0]
1332        assert_eq!(diag.get(&[1]), 4.0); // A[1,1]
1333        assert_eq!(diag.get(&[2]), 8.0); // A[2,2]
1334    }
1335
1336    #[test]
1337    fn test_diagonal_view_3d_adjacent() {
1338        // A[i,i,j] shape=[2,2,3] row-major strides=[6,3,1]
1339        // diagonal over (0,1): shape=[2,3] strides=[9,1] (6+3)
1340        let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
1341        let view = StridedView::<f64>::new(&data, &[2, 2, 3], &[6, 3, 1], 0).unwrap();
1342        let diag = view.diagonal_view(&[(0, 1)]).unwrap();
1343        assert_eq!(diag.dims(), &[2, 3]);
1344        assert_eq!(diag.strides(), &[9, 1]);
1345        assert_eq!(diag.get(&[0, 0]), 0.0);
1346        assert_eq!(diag.get(&[0, 2]), 2.0);
1347        assert_eq!(diag.get(&[1, 0]), 9.0);
1348    }
1349
1350    #[test]
1351    fn test_diagonal_view_3d_non_adjacent() {
1352        // A[i,j,i] shape=[2,3,2] row-major strides=[6,2,1]
1353        // diagonal over (0,2): shape=[2,3] strides=[7,2] (6+1)
1354        let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
1355        let view = StridedView::<f64>::new(&data, &[2, 3, 2], &[6, 2, 1], 0).unwrap();
1356        let diag = view.diagonal_view(&[(0, 2)]).unwrap();
1357        assert_eq!(diag.dims(), &[2, 3]);
1358        assert_eq!(diag.strides(), &[7, 2]);
1359        assert_eq!(diag.get(&[0, 0]), 0.0);
1360        assert_eq!(diag.get(&[0, 1]), 2.0);
1361        assert_eq!(diag.get(&[1, 0]), 7.0);
1362        assert_eq!(diag.get(&[1, 2]), 11.0);
1363    }
1364
1365    #[test]
1366    fn test_diagonal_view_two_pairs() {
1367        // A[i,j,i,j] shape=[2,3,2,3] -> A_diag[i,j] shape=[2,3]
1368        let data: Vec<f64> = (0..36).map(|x| x as f64).collect();
1369        let view = StridedView::<f64>::new(&data, &[2, 3, 2, 3], &[18, 6, 3, 1], 0).unwrap();
1370        let diag = view.diagonal_view(&[(0, 2), (1, 3)]).unwrap();
1371        assert_eq!(diag.dims(), &[2, 3]);
1372        assert_eq!(diag.strides(), &[21, 7]);
1373        assert_eq!(diag.get(&[0, 0]), 0.0);
1374        assert_eq!(diag.get(&[1, 1]), 28.0);
1375    }
1376
1377    #[test]
1378    fn test_custom_type_without_element_op_apply() {
1379        // Custom Copy type that does NOT implement ElementOpApply.
1380        // Should work with Identity views (StridedView<T> default).
1381        #[derive(Debug, Clone, Copy, PartialEq)]
1382        struct MyScalar(f64);
1383
1384        impl Default for MyScalar {
1385            fn default() -> Self {
1386                MyScalar(0.0)
1387            }
1388        }
1389
1390        // Create array with custom type
1391        let data = vec![MyScalar(1.0), MyScalar(2.0), MyScalar(3.0), MyScalar(4.0)];
1392        let arr = StridedArray::from_parts(data, &[2, 2], &[2, 1], 0).unwrap();
1393
1394        // Access via Identity view (default)
1395        assert_eq!(arr.get(&[0, 0]), MyScalar(1.0));
1396        assert_eq!(arr.get(&[1, 0]), MyScalar(3.0));
1397
1398        // Create view and access elements
1399        let view: StridedView<MyScalar> = arr.view();
1400        assert_eq!(view.get(&[0, 1]), MyScalar(2.0));
1401
1402        // Permute view (metadata-only, no ElementOpApply needed)
1403        let perm = view.permute(&[1, 0]).unwrap();
1404        assert_eq!(perm.get(&[0, 0]), MyScalar(1.0));
1405        assert_eq!(perm.get(&[0, 1]), MyScalar(3.0)); // transposed
1406    }
1407}