1use std::fmt;
10use std::fmt::{Display, Formatter};
11use std::ops::Index;
12
13use crate::enums::error::MinarrowError;
14use crate::enums::shape_dim::ShapeDim;
15use crate::structs::ndarray::{NdArray, NdArrayIter, NdDims, offset_of_impl};
16use crate::traits::print::print_ndarray_body;
17#[cfg(feature = "select")]
18use crate::structs::ndarray::gather_obs_impl;
19#[cfg(feature = "select")]
20use crate::traits::selection::{AxisSelection, DataSelector, RowSelection};
21#[cfg(feature = "select")]
22use std::ops::Range;
23#[cfg(feature = "dlpack")]
24use crate::ffi::dlpack::{
25 export_view_to_dlpack, export_view_to_dlpack_versioned, DLPackTensor, DLPackTensorVersioned,
26};
27use crate::traits::shape::Shape;
28use crate::traits::type_unions::Float;
29use crate::Vec64;
30
31#[cfg(feature = "matrix")]
32use crate::structs::matrix::Matrix;
33
34#[derive(Clone)]
41pub struct NdArrayV<T> {
42 pub(crate) source: NdArray<T>,
43 pub(crate) offset: usize,
44 pub(crate) dims: NdDims,
45}
46
47impl<T: Float> NdArrayV<T> {
48 pub fn new(source: NdArray<T>, offset: usize, shape: &[usize], strides: &[usize]) -> Self {
51 Self::try_new(source, offset, shape, strides)
52 .unwrap_or_else(|e| panic!("NdArrayV::new: {}", e))
53 }
54
55 pub fn try_new(
63 source: NdArray<T>,
64 offset: usize,
65 shape: &[usize],
66 strides: &[usize],
67 ) -> Result<Self, MinarrowError> {
68 if shape.len() != strides.len() {
69 return Err(MinarrowError::ShapeError {
70 message: format!(
71 "view shape rank {} does not match strides rank {}",
72 shape.len(), strides.len()
73 ),
74 });
75 }
76
77 let span = if shape.contains(&0) {
78 0
79 } else {
80 let max_offset = shape.iter().zip(strides.iter()).try_fold(
81 0usize,
82 |acc, (&dim, &stride)| {
83 (dim - 1)
84 .checked_mul(stride)
85 .and_then(|extent| acc.checked_add(extent))
86 },
87 ).ok_or_else(|| MinarrowError::ShapeError {
88 message: format!("view shape {:?} with strides {:?} overflows", shape, strides),
89 })?;
90 max_offset.checked_add(1).ok_or_else(|| MinarrowError::ShapeError {
91 message: format!("view shape {:?} with strides {:?} overflows", shape, strides),
92 })?
93 };
94 let end = offset.checked_add(span).ok_or_else(|| MinarrowError::ShapeError {
95 message: format!("view offset {} plus span {} overflows", offset, span),
96 })?;
97 let buffer_len = source.data.as_slice().len();
98 if end > buffer_len || (span == 0 && offset > buffer_len) {
99 return Err(MinarrowError::IndexError(format!(
100 "view span [{}, {}) exceeds source buffer length {}",
101 offset, end, buffer_len
102 )));
103 }
104
105 Ok(NdArrayV {
106 source,
107 offset,
108 dims: NdDims::from_shape_and_strides(shape, strides),
109 })
110 }
111
112 pub fn from_ndarray(source: NdArray<T>) -> Self {
114 let dims = source.dims.clone();
115 NdArrayV { source, offset: 0, dims }
116 }
117
118 #[inline]
120 pub fn ndim(&self) -> usize { self.dims.ndim() }
121
122 #[inline]
124 pub fn shape(&self) -> &[usize] { self.dims.shape() }
125
126 #[inline]
128 pub fn strides(&self) -> &[usize] { self.dims.strides() }
129
130 #[inline]
132 pub fn len(&self) -> usize { self.dims.len() }
133
134 #[inline]
136 pub fn is_empty(&self) -> bool { self.len() == 0 }
137
138 #[inline]
140 pub fn get(&self, indices: &[usize]) -> T {
141 let off = self.offset_of(indices);
142 self.source.data.as_slice()[off]
143 }
144
145 #[inline(always)]
150 pub unsafe fn get_unchecked(&self, indices: &[usize]) -> T {
151 let strides = self.dims.strides();
152 let mut off = self.offset;
153 for d in 0..indices.len() {
154 off += indices[d] * strides[d];
155 }
156 unsafe { *self.source.data.as_slice().get_unchecked(off) }
158 }
159
160 #[inline]
162 fn offset_of(&self, indices: &[usize]) -> usize {
163 self.offset + offset_of_impl(indices, self.dims.shape(), self.dims.strides())
164 }
165
166 #[inline]
172 pub fn col(&self, col: usize) -> &[T] {
173 let shape = self.dims.shape();
174 assert_eq!(shape.len(), 2, "col() requires a 2D view");
175 assert!(col < shape[1], "Column index out of bounds");
176 assert_eq!(
177 self.dims.strides()[0], 1,
178 "col() requires unit stride on axis 0; materialise with to_ndarray() first"
179 );
180 let stride = self.dims.strides()[1];
181 let start = self.offset + col * stride;
182 &self.source.data.as_slice()[start..start + shape[0]]
183 }
184
185 pub fn columns(&self) -> Vec<&[T]> {
188 let shape = self.dims.shape();
189 assert_eq!(shape.len(), 2, "columns() requires a 2D view");
190 assert_eq!(
191 self.dims.strides()[0], 1,
192 "columns() requires unit stride on axis 0; materialise with to_ndarray() first"
193 );
194 let stride = self.dims.strides()[1];
195 let n_rows = shape[0];
196 let buf = self.source.data.as_slice();
197 (0..shape[1])
198 .map(|c| &buf[self.offset + c * stride..self.offset + c * stride + n_rows])
199 .collect()
200 }
201
202 #[inline]
206 pub fn m(&self) -> i32 {
207 assert_eq!(self.ndim(), 2, "m() requires a 2D view");
208 self.dims.shape()[0] as i32
209 }
210
211 #[inline]
213 pub fn n(&self) -> i32 {
214 assert_eq!(self.ndim(), 2, "n() requires a 2D view");
215 self.dims.shape()[1] as i32
216 }
217
218 #[inline]
223 pub fn lda(&self) -> i32 {
224 assert_eq!(self.ndim(), 2, "lda() requires a 2D view");
225 assert_eq!(
226 self.dims.strides()[0], 1,
227 "lda() requires unit stride on axis 0; materialise with to_ndarray() first"
228 );
229 self.dims.strides()[1] as i32
230 }
231
232 pub fn obs(&self, idx: usize) -> NdArrayV<T> {
241 let shape = self.dims.shape();
242 let strides = self.dims.strides();
243 assert!(
244 shape.len() >= 2,
245 "obs() requires a 2D or higher view, use get(&[i]) for scalar access on 1D"
246 );
247 assert!(idx < shape[0], "obs: index {} out of bounds for axis 0 (size {})", idx, shape[0]);
248
249 let new_offset = self.offset + idx * strides[0];
250 NdArrayV::new(self.source.clone(), new_offset, &shape[1..], &strides[1..])
251 }
252
253 #[cfg(feature = "select")]
258 pub fn slice(&self, selection: &[&dyn DataSelector]) -> NdArrayV<T> {
259 let shape = self.dims.shape();
260 let strides = self.dims.strides();
261 assert_eq!(
262 selection.len(), shape.len(),
263 "slice(): expected {} axes, got {}", shape.len(), selection.len()
264 );
265
266 let mut new_offset = self.offset;
267 let mut new_shape = Vec::with_capacity(shape.len());
268 let mut new_strides = Vec::with_capacity(shape.len());
269
270 for (d, sel) in selection.iter().enumerate() {
271 let (start, end, collapse) = sel.resolve_axis(shape[d]);
272 assert!(
273 end <= shape[d],
274 "slice(): end {} out of bounds for axis {} (size {})", end, d, shape[d]
275 );
276 new_offset += start * strides[d];
277 if !collapse {
278 new_shape.push(end - start);
279 new_strides.push(strides[d]);
280 }
281 }
282
283 NdArrayV::new(self.source.clone(), new_offset, &new_shape, &new_strides)
284 }
285
286 pub fn transpose(&self) -> NdArrayV<T> {
292 let shape: Vec<usize> = self.dims.shape().iter().rev().copied().collect();
293 let strides: Vec<usize> = self.dims.strides().iter().rev().copied().collect();
294 NdArrayV::new(self.source.clone(), self.offset, &shape, &strides)
295 }
296
297 pub fn permute_axes(&self, perm: &[usize]) -> NdArrayV<T> {
302 let shape = self.dims.shape();
303 let strides = self.dims.strides();
304 let ndim = shape.len();
305 assert_eq!(
306 perm.len(), ndim,
307 "permute_axes: expected {} axes, got {}", ndim, perm.len()
308 );
309 let mut seen = vec![false; ndim];
310 for &ax in perm {
311 assert!(ax < ndim, "permute_axes: axis {} out of bounds for {}D view", ax, ndim);
312 assert!(!seen[ax], "permute_axes: axis {} repeated", ax);
313 seen[ax] = true;
314 }
315 let new_shape: Vec<usize> = perm.iter().map(|&ax| shape[ax]).collect();
316 let new_strides: Vec<usize> = perm.iter().map(|&ax| strides[ax]).collect();
317 NdArrayV::new(self.source.clone(), self.offset, &new_shape, &new_strides)
318 }
319
320 pub fn swap_axes(&self, a: usize, b: usize) -> NdArrayV<T> {
322 let ndim = self.dims.ndim();
323 assert!(
324 a < ndim && b < ndim,
325 "swap_axes: axes ({}, {}) out of bounds for {}D view", a, b, ndim
326 );
327 let mut shape = self.dims.shape().to_vec();
328 let mut strides = self.dims.strides().to_vec();
329 shape.swap(a, b);
330 strides.swap(a, b);
331 NdArrayV::new(self.source.clone(), self.offset, &shape, &strides)
332 }
333
334 #[cfg(feature = "dlpack")]
340 pub fn to_dlpack(self) -> DLPackTensor {
341 export_view_to_dlpack(self)
342 }
343
344 #[cfg(feature = "dlpack")]
348 pub fn to_dlpack_versioned(self) -> DLPackTensorVersioned {
349 export_view_to_dlpack_versioned(self)
350 }
351
352 pub fn to_ndarray(&self) -> NdArray<T> {
356 let flat: Vec64<T> = self.into_iter().collect();
357 let mut arr = NdArray::from_slice(&flat, self.dims.shape());
358 arr.name = self.source.name.clone();
359 arr
360 }
361
362 #[cfg(feature = "parallel_proc")]
367 pub fn par_iter_obs(&self) -> impl rayon::iter::ParallelIterator<Item = (usize, NdArrayV<T>)> + '_
368 where
369 T: Send + Sync,
370 {
371 use rayon::prelude::*;
372 assert!(self.ndim() >= 2, "par_iter_obs() requires a 2D or higher view");
373 let n_obs = self.dims.shape()[0];
374 (0..n_obs).into_par_iter().map(move |i| (i, self.obs(i)))
375 }
376
377 pub(crate) fn iter_axis0_run(&self, run_idx: usize) -> impl ExactSizeIterator<Item = T> + '_ {
381 assert!(self.ndim() > 0, "axis-0 iteration requires an axis 0");
382 let n_runs: usize = self.shape()[1..].iter().product();
383 assert!(run_idx < n_runs, "axis-0 run {} out of bounds ({})", run_idx, n_runs);
384
385 let mut rem = run_idx;
386 let mut offset = self.offset;
387 for d in 1..self.ndim() {
388 offset += (rem % self.shape()[d]) * self.strides()[d];
389 rem /= self.shape()[d];
390 }
391 let stride = self.strides()[0];
392 (0..self.shape()[0]).map(move |i| self.source.data.as_slice()[offset + i * stride])
393 }
394}
395
396#[cfg(feature = "matrix")]
398impl NdArrayV<f64> {
399 pub fn to_matrix(&self) -> Result<Matrix, MinarrowError> {
401 self.to_ndarray().to_matrix()
402 }
403}
404
405impl<T: Float> NdArrayV<T> {
406 pub fn apply(&self, f: impl Fn(T) -> T) -> NdArray<T> {
409 let flat: Vec64<T> = self.into_iter().map(f).collect();
410 let mut result = NdArray::from_slice(&flat, self.dims.shape());
411 result.name = self.source.name.clone();
412 result
413 }
414}
415
416#[cfg(feature = "select")]
422impl<T: Float> AxisSelection for NdArrayV<T> {
423 type View = NdArrayV<T>;
424
425 fn s(&self, selection: &[&dyn DataSelector]) -> NdArrayV<T> {
426 self.slice(selection)
427 }
428
429 fn get_axis_count(&self) -> usize {
430 self.ndim()
431 }
432}
433
434#[cfg(feature = "select")]
440impl<T: Float> RowSelection for NdArrayV<T> {
441 type View = NdArrayV<T>;
442
443 fn r<S: DataSelector>(&self, selection: S) -> NdArrayV<T> {
444 assert!(self.ndim() > 0, "row selection requires an axis 0");
445 let n_obs = self.dims.shape()[0];
446 let indices = selection.resolve_indices(n_obs);
447 if selection.is_contiguous() {
448 let start = indices.first().copied().unwrap_or(0);
449 let ranges: Vec<Range<usize>> = std::iter::once(start..start + indices.len())
450 .chain(self.dims.shape()[1..].iter().map(|&n| 0..n))
451 .collect();
452 let refs: Vec<&dyn DataSelector> = ranges.iter().map(|r| r as _).collect();
453 return self.slice(&refs);
454 }
455 NdArrayV::from_ndarray(gather_obs_impl(
456 &indices,
457 self.dims.shape(),
458 self.source.name.clone(),
459 |idx| self.get(idx),
460 ))
461 }
462
463 fn get_row_count(&self) -> usize {
464 assert!(self.ndim() > 0, "row count requires an axis 0");
465 self.dims.shape()[0]
466 }
467}
468
469impl<'a, T: Float> IntoIterator for &'a NdArrayV<T> {
474 type Item = T;
475 type IntoIter = NdArrayIter<'a, T>;
476
477 fn into_iter(self) -> NdArrayIter<'a, T> {
478 let shape = self.dims.shape();
479 let strides = self.dims.strides();
480 if shape.is_empty() {
481 return NdArrayIter {
482 buf: self.source.data.as_slice(),
483 n_inner: 1,
484 inner_stride: 1,
485 run_offsets: vec![self.offset],
486 run_idx: 0,
487 inner_idx: 0,
488 total: 1,
489 yielded: 0,
490 };
491 }
492 let n_inner = shape[0];
493 let n_runs: usize = shape[1..].iter().product();
494
495 let mut run_offsets = Vec::with_capacity(n_runs);
496 if shape.len() <= 1 {
497 run_offsets.push(self.offset);
498 } else {
499 let outer_shape = &shape[1..];
500 let outer_strides = &strides[1..];
501 let mut outer_indices = vec![0usize; outer_shape.len()];
502 for _ in 0..n_runs {
503 let off: usize = self.offset + outer_indices.iter()
504 .zip(outer_strides.iter())
505 .map(|(&i, &s)| i * s)
506 .sum::<usize>();
507 run_offsets.push(off);
508 let mut carry = true;
509 for d in 0..outer_shape.len() {
510 if carry {
511 outer_indices[d] += 1;
512 if outer_indices[d] < outer_shape[d] {
513 carry = false;
514 } else {
515 outer_indices[d] = 0;
516 }
517 }
518 }
519 }
520 }
521
522 NdArrayIter {
523 buf: self.source.data.as_slice(),
524 n_inner,
525 inner_stride: strides[0],
526 run_offsets,
527 run_idx: 0,
528 inner_idx: 0,
529 total: self.len(),
530 yielded: 0,
531 }
532 }
533}
534
535impl<T: Float> Shape for NdArrayV<T> {
538 fn shape(&self) -> ShapeDim {
539 match self.dims.ndim() {
540 0 => ShapeDim::Rank0(1),
541 1 => ShapeDim::Rank1(self.dims.shape()[0]),
542 2 => ShapeDim::Rank2 {
543 rows: self.dims.shape()[0],
544 cols: self.dims.shape()[1],
545 },
546 _ => ShapeDim::RankN(self.dims.shape().to_vec()),
547 }
548 }
549}
550
551impl<T: Float> PartialEq for NdArrayV<T> {
552 fn eq(&self, other: &Self) -> bool {
553 if self.dims.shape() != other.dims.shape() { return false; }
554 self.into_iter()
555 .zip(other.into_iter())
556 .all(|(a, b)| a == b)
557 }
558}
559
560impl<T: Float> Index<()> for NdArrayV<T> {
563 type Output = T;
564 #[inline]
565 fn index(&self, (): ()) -> &T {
566 &self.source.data.as_slice()[self.offset_of(&[])]
567 }
568}
569
570impl<T: Float> Index<(usize,)> for NdArrayV<T> {
571 type Output = T;
572 #[inline]
573 fn index(&self, (i,): (usize,)) -> &T {
574 &self.source.data.as_slice()[self.offset_of(&[i])]
575 }
576}
577
578impl<T: Float> Index<(usize, usize)> for NdArrayV<T> {
579 type Output = T;
580 #[inline]
581 fn index(&self, (i, j): (usize, usize)) -> &T {
582 &self.source.data.as_slice()[self.offset_of(&[i, j])]
583 }
584}
585
586impl<T: Float> Index<(usize, usize, usize)> for NdArrayV<T> {
587 type Output = T;
588 #[inline]
589 fn index(&self, (i, j, k): (usize, usize, usize)) -> &T {
590 &self.source.data.as_slice()[self.offset_of(&[i, j, k])]
591 }
592}
593
594impl<T: Float> Index<(usize, usize, usize, usize)> for NdArrayV<T> {
595 type Output = T;
596 #[inline]
597 fn index(&self, (i, j, k, l): (usize, usize, usize, usize)) -> &T {
598 &self.source.data.as_slice()[self.offset_of(&[i, j, k, l])]
599 }
600}
601
602impl<T: Float> Index<(usize, usize, usize, usize, usize)> for NdArrayV<T> {
603 type Output = T;
604 #[inline]
605 fn index(&self, (i, j, k, l, m): (usize, usize, usize, usize, usize)) -> &T {
606 &self.source.data.as_slice()[self.offset_of(&[i, j, k, l, m])]
607 }
608}
609
610impl<T: Float> fmt::Debug for NdArrayV<T> {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 write!(
615 f, "NdArrayV: {:?} [{}D, offset={}]",
616 self.dims.shape(), self.ndim(), self.offset,
617 )
618 }
619}
620
621impl<T: Float + Display> Display for NdArrayV<T> {
622 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
623 let shape = self.shape();
624 let elem = std::any::type_name::<T>();
625 let dims = if shape.is_empty() {
626 String::from("scalar")
627 } else {
628 shape.iter().map(|d| d.to_string()).collect::<Vec<_>>().join(" × ")
629 };
630 match &self.source.name {
631 Some(name) => writeln!(
632 f,
633 "NdArrayView \"{}\" [{}, {}] (offset: {})",
634 name, dims, elem, self.offset
635 )?,
636 None => writeln!(f, "NdArrayView [{}, {}] (offset: {})", dims, elem, self.offset)?,
637 }
638 print_ndarray_body(f, shape, |index| self.get(index))
639 }
640}
641
642impl<T: Float> From<NdArrayV<T>> for NdArray<T> {
643 fn from(value: NdArrayV<T>) -> Self {
645 value.to_ndarray()
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652 use crate::enums::shape_dim::ShapeDim;
653
654 #[test]
655 fn from_ndarray() {
656 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
657 let v = NdArrayV::from_ndarray(a);
658 assert_eq!(v.shape(), &[3, 2]);
659 assert_eq!(v.len(), 6);
660 assert_eq!(v[(0, 0)], 1.0);
661 assert_eq!(v[(2, 1)], 6.0);
662 }
663
664 #[test]
665 fn try_new_validates_and_constructs_view() {
666 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
667 let v = NdArrayV::try_new(a, 1, &[2], &[1]).unwrap();
668 assert_eq!(v.shape(), &[2]);
669 assert_eq!(v.get(&[0]), 2.0);
670 assert_eq!(v.get(&[1]), 3.0);
671 }
672
673 #[test]
674 fn try_new_accepts_rank_zero_scalar_view() {
675 let a = NdArray::from_slice(&[10.0, 20.0], &[2]);
676 let v = NdArrayV::try_new(a, 1, &[], &[]).unwrap();
677 assert!(v.shape().is_empty());
678 assert!(v.strides().is_empty());
679 assert_eq!(v.len(), 1);
680 assert_eq!(v[()], 20.0);
681 assert_eq!((&v).into_iter().collect::<Vec<_>>(), vec![20.0]);
682 assert_eq!(Shape::shape(&v), ShapeDim::Rank0(1));
683 }
684
685 #[test]
686 fn try_new_rejects_invalid_rank_and_span() {
687 let a = NdArray::<f64>::new(&[4]);
688 assert!(NdArrayV::try_new(a.clone(), 0, &[2, 2], &[1]).is_err());
689 assert!(NdArrayV::try_new(a.clone(), 3, &[2], &[1]).is_err());
690 assert!(NdArrayV::try_new(a, usize::MAX, &[2], &[1]).is_err());
691 }
692
693 #[test]
694 fn try_new_accepts_empty_view_at_buffer_end() {
695 let a = NdArray::<f64>::new(&[4]);
696 let v = NdArrayV::try_new(a, 4, &[0], &[1]).unwrap();
697 assert!(v.is_empty());
698 }
699
700 #[test]
701 #[should_panic(expected = "NdArrayV::new")]
702 fn new_panics_on_invalid_span() {
703 let a = NdArray::<f64>::new(&[4]);
704 let _ = NdArrayV::new(a, 3, &[2], &[1]);
705 }
706
707 #[test]
708 fn col_access() {
709 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
710 let v = NdArrayV::from_ndarray(a);
711 assert_eq!(v.col(0), &[1.0, 2.0, 3.0]);
712 assert_eq!(v.col(1), &[4.0, 5.0, 6.0]);
713 }
714
715 #[test]
716 fn columns() {
717 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
718 let v = NdArrayV::from_ndarray(a);
719 let cols = v.columns();
720 assert_eq!(cols.len(), 2);
721 assert_eq!(cols[0], &[1.0, 2.0, 3.0]);
722 assert_eq!(cols[1], &[4.0, 5.0, 6.0]);
723 }
724
725 #[test]
726 #[cfg(feature = "select")]
727 fn row_selection_on_view() {
728 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
729 let v = a.as_view();
730 let sub = v.r(1..3);
732 assert_eq!(sub.shape(), &[2, 2]);
733 assert_eq!(sub.get(&[0, 1]), 5.0);
734 let picked = v.r(&[2, 0]);
736 assert_eq!(picked.get(&[0, 0]), 3.0);
737 assert_eq!(picked.get(&[1, 1]), 4.0);
738 }
739
740 #[test]
741 fn apply_on_view() {
742 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
743 let out = a.as_view().apply(|x| x + 1.0);
744 assert_eq!(out.shape(), &[2, 2]);
745 assert_eq!(out.get(&[1, 1]), 5.0);
746 }
747
748 #[test]
749 fn obs_access() {
750 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
751 let v = NdArrayV::from_ndarray(a);
752 let obs0: Vec<f64> = (&v.obs(0)).into_iter().collect();
753 let obs2: Vec<f64> = (&v.obs(2)).into_iter().collect();
754 assert_eq!(obs0, vec![1.0, 4.0]);
755 assert_eq!(obs2, vec![3.0, 6.0]);
756 }
757
758 #[test]
759 fn iteration() {
760 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
761 let v = NdArrayV::from_ndarray(a);
762 let vals: Vec<f64> = (&v).into_iter().collect();
763 assert_eq!(vals, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
764 }
765
766 #[test]
767 fn iteration_2d() {
768 let data: Vec<f64> = (1..=20).map(|x| x as f64).collect();
769 let a = NdArray::from_slice(&data, &[10, 2]);
770 let v = NdArrayV::from_ndarray(a);
771 let vals: Vec<f64> = (&v).into_iter().collect();
772 assert_eq!(vals.len(), 20);
773 assert_eq!(&vals[..10], &data[..10]);
774 assert_eq!(&vals[10..], &data[10..]);
775 }
776
777 #[test]
778 fn with_offset() {
779 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
780 let stride = a.strides()[1];
781 let v = NdArrayV::new(a.clone(), stride, &[3], &[1]);
783 assert_eq!(v.shape(), &[3]);
784 assert_eq!(v[(0,)], 4.0);
785 assert_eq!(v[(1,)], 5.0);
786 assert_eq!(v[(2,)], 6.0);
787 }
788
789 #[test]
790 fn to_ndarray() {
791 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
792 let v = NdArrayV::from_ndarray(a);
793 let b = v.to_ndarray();
794 assert_eq!(b.shape(), &[3, 2]);
795 assert_eq!(b.col(0), &[1.0, 2.0, 3.0]);
796 }
797
798 #[test]
799 fn transpose_2d_view() {
800 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
801 let t = NdArrayV::from_ndarray(a).transpose();
802 assert_eq!(t.shape(), &[2, 3]);
803 assert_eq!(t[(0, 0)], 1.0);
804 assert_eq!(t[(1, 0)], 4.0);
805 assert_eq!(t[(0, 2)], 3.0);
806 assert_eq!(t[(1, 2)], 6.0);
807 let owned = t.to_ndarray();
809 assert_eq!(owned.shape(), &[2, 3]);
810 assert_eq!(owned.get(&[1, 1]), 5.0);
811 }
812
813 #[test]
814 fn transpose_3d_view_matches_materialised() {
815 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
816 let a = NdArray::from_slice(&data, &[2, 3, 4]);
817 let t = a.as_view().transpose();
818 assert_eq!(t.shape(), &[4, 3, 2]);
819 let materialised = a.transpose();
820 for i in 0..4 {
821 for j in 0..3 {
822 for k in 0..2 {
823 assert_eq!(t[(i, j, k)], materialised.get(&[i, j, k]));
824 }
825 }
826 }
827 }
828
829 #[test]
830 fn permute_axes_view() {
831 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
832 let a = NdArray::from_slice(&data, &[2, 3, 4]);
833 let p = a.as_view().permute_axes(&[2, 0, 1]);
834 assert_eq!(p.shape(), &[4, 2, 3]);
835 for i in 0..2 {
836 for j in 0..3 {
837 for k in 0..4 {
838 assert_eq!(p[(k, i, j)], a.get(&[i, j, k]));
839 }
840 }
841 }
842 }
843
844 #[test]
845 #[should_panic(expected = "permute_axes")]
846 fn permute_axes_rejects_repeat() {
847 let a = NdArray::<f64>::new(&[2, 3, 4]);
848 let _ = a.as_view().permute_axes(&[0, 0, 1]);
849 }
850
851 #[test]
852 fn swap_axes_view() {
853 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
854 let a = NdArray::from_slice(&data, &[2, 3, 4]);
855 let s = a.as_view().swap_axes(0, 2);
856 assert_eq!(s.shape(), &[4, 3, 2]);
857 assert_eq!(s[(3, 2, 1)], a.get(&[1, 2, 3]));
858 assert_eq!(s[(0, 0, 0)], a.get(&[0, 0, 0]));
859 }
860
861 #[test]
862 #[should_panic(expected = "obs()")]
863 fn obs_on_1d_panics() {
864 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
865 let _ = a.as_view().obs(1);
866 }
867
868 #[cfg(feature = "matrix")]
869 #[test]
870 fn to_matrix() {
871 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
872 let v = NdArrayV::from_ndarray(a);
873 let mat = v.to_matrix().unwrap();
874 assert_eq!(mat.n_rows, 3);
875 assert_eq!(mat.n_cols, 2);
876 }
877
878 #[test]
879 fn blas_params() {
880 let a = NdArray::<f64>::new(&[10, 5]);
881 let v = NdArrayV::from_ndarray(a);
882 assert_eq!(v.m(), 10);
883 assert_eq!(v.n(), 5);
884 assert_eq!(v.lda(), 10);
885 }
886
887 #[test]
888 fn eq() {
889 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
890 let v1 = NdArrayV::from_ndarray(a.clone());
891 let v2 = NdArrayV::from_ndarray(a);
892 assert_eq!(v1, v2);
893 }
894
895 #[test]
896 fn shape_trait() {
897 let a = NdArray::<f64>::new(&[3, 4]);
898 let v = NdArrayV::from_ndarray(a);
899 assert_eq!(Shape::shape(&v), ShapeDim::Rank2 { rows: 3, cols: 4 });
900 }
901
902 #[test]
903 #[should_panic(expected = "unit stride")]
904 fn col_on_transposed_view_panics() {
905 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
906 let t = a.as_view().transpose();
907 let _ = t.col(0);
908 }
909
910 #[test]
911 #[should_panic(expected = "unit stride")]
912 fn columns_on_transposed_view_panics() {
913 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
914 let t = a.as_view().transpose();
915 let _ = t.columns();
916 }
917
918 #[test]
919 #[should_panic(expected = "unit stride")]
920 fn lda_on_transposed_view_panics() {
921 let a = NdArray::<f64>::new(&[3, 2]);
922 let t = a.as_view().transpose();
923 let _ = t.lda();
924 }
925
926 #[test]
927 #[should_panic(expected = "index 3 out of bounds for axis 0")]
928 fn obs_out_of_bounds_panics() {
929 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
930 let _ = a.as_view().obs(3);
931 }
932
933 #[test]
934 #[should_panic(expected = "swap_axes: axes (0, 3) out of bounds")]
935 fn swap_axes_out_of_bounds_panics() {
936 let a = NdArray::<f64>::new(&[2, 3, 4]);
937 let _ = a.as_view().swap_axes(0, 3);
938 }
939
940 #[test]
941 #[should_panic(expected = "permute_axes: axis 3 out of bounds")]
942 fn permute_axes_out_of_bounds_axis_panics() {
943 let a = NdArray::<f64>::new(&[2, 3, 4]);
944 let _ = a.as_view().permute_axes(&[0, 1, 3]);
945 }
946
947 #[test]
948 fn get_unchecked_matches_get() {
949 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
950 let a = NdArray::from_slice(&data, &[2, 3, 4]);
951 let t = a.as_view().transpose();
952 for i in 0..4 {
953 for j in 0..3 {
954 for k in 0..2 {
955 let idx = [i, j, k];
956 assert_eq!(unsafe { t.get_unchecked(&idx) }, t.get(&idx));
958 }
959 }
960 }
961 }
962}