1use std::fmt;
68use std::fmt::{Display, Formatter};
69use std::ops::{Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
70use std::sync::Arc;
71
72use crate::enums::error::MinarrowError;
73use crate::enums::shape_dim::ShapeDim;
74use crate::structs::buffer::Buffer;
75use crate::traits::print::print_ndarray_body;
76#[cfg(all(feature = "views", feature = "select"))]
77use crate::traits::selection::{AxisSelection, DataSelector, RowSelection};
78use crate::traits::type_unions::Float;
79use crate::traits::{concatenate::Concatenate, shape::Shape};
80use crate::{Array, ArrowType, Field, FieldArray, FloatArray, NumericArray, Table, Vec64};
81
82#[cfg(feature = "matrix")]
83use crate::structs::matrix::{Matrix, aligned_stride};
84#[cfg(feature = "views")]
85use crate::structs::views::ndarray_view::NdArrayV;
86#[cfg(feature = "dlpack")]
87use crate::ffi::dlpack::{
88 export_to_dlpack, export_to_dlpack_versioned, DLPackTensor, DLPackTensorVersioned,
89};
90
91#[derive(Clone)]
102pub struct NdArray<T> {
103 pub(crate) data: Arc<Buffer<T>>,
104 pub(crate) dims: NdDims,
105 pub name: Option<String>,
106}
107
108impl<T: Float> PartialEq for NdArray<T> {
111 fn eq(&self, other: &Self) -> bool {
112 if self.dims.shape() != other.dims.shape() {
113 return false;
114 }
115 self.into_iter()
116 .zip(other.into_iter())
117 .all(|(a, b)| a == b)
118 }
119}
120
121impl<T: Float> NdArray<T> {
124 pub fn new(shape: &[usize]) -> Self {
128 let dims = NdDims::from_shape(shape);
129 let total = buffer_len(dims.shape(), dims.strides());
130 let mut v = Vec64::with_capacity(total);
131 v.0.resize(total, T::default());
132 NdArray { data: Arc::new(Buffer::from_vec64(v)), dims, name: None }
133 }
134
135 pub fn new_named(shape: &[usize], name: impl Into<String>) -> Self {
137 let mut arr = Self::new(shape);
138 arr.name = Some(name.into());
139 arr
140 }
141
142 pub fn from_slice(data: &[T], shape: &[usize]) -> Self {
150 let logical_len: usize = shape.iter().product();
151 assert_eq!(
152 data.len(), logical_len,
153 "NdArray::from_slice: data length {} does not match shape product {}",
154 data.len(), logical_len
155 );
156 let dims = NdDims::from_shape(shape);
157 NdArray {
158 data: Arc::new(Buffer::from_slice(data)),
159 dims,
160 name: None,
161 }
162 }
163
164 pub fn from_vec64(data: Vec64<T>, shape: &[usize]) -> Self {
169 let logical_len: usize = shape.iter().product();
170 assert_eq!(
171 data.len(), logical_len,
172 "NdArray::from_vec64: data length {} does not match shape product {}",
173 data.len(), logical_len
174 );
175 let dims = NdDims::from_shape(shape);
176 NdArray {
177 data: Arc::new(Buffer::from_vec64(data)),
178 dims,
179 name: None,
180 }
181 }
182
183 pub fn from_buffer(data: Buffer<T>, shape: &[usize], strides: &[usize]) -> Self {
188 let required = buffer_len(shape, strides);
189 assert!(
190 data.len() >= required,
191 "NdArray::from_buffer: buffer has {} elements but shape requires {}",
192 data.len(), required
193 );
194 let dims = NdDims::from_shape_and_strides(shape, strides);
195 NdArray { data: Arc::new(data), dims, name: None }
196 }
197
198 pub fn fill(shape: &[usize], value: T) -> Self {
200 let dims = NdDims::from_shape(shape);
201 let total = buffer_len(dims.shape(), dims.strides());
202 let mut v = Vec64::with_capacity(total);
203 v.0.resize(total, value);
204 NdArray { data: Arc::new(Buffer::from_vec64(v)), dims, name: None }
205 }
206
207 pub fn ones(shape: &[usize]) -> Self {
209 Self::fill(shape, T::one())
210 }
211
212 pub fn eye(n: usize) -> Self {
214 let mut arr = Self::new(&[n, n]);
215 let stride = arr.dims.strides()[1];
216 let buf = Arc::make_mut(&mut arr.data).as_mut_slice();
217 for i in 0..n {
218 buf[i * stride + i] = T::one();
219 }
220 arr
221 }
222
223 pub fn linspace(start: T, end: T, n: usize) -> Self {
225 assert!(n >= 2, "linspace requires at least 2 points");
226 let step = (end - start) / T::from(n - 1).unwrap();
227 let v: Vec64<T> = (0..n).map(|i| start + step * T::from(i).unwrap()).collect();
228 NdArray {
229 data: Arc::new(Buffer::from_vec64(v)),
230 dims: NdDims::from_shape(&[n]),
231 name: None,
232 }
233 }
234
235 pub fn arange(start: T, step: T, n: usize) -> Self {
238 let v: Vec64<T> = (0..n).map(|i| start + step * T::from(i).unwrap()).collect();
239 NdArray {
240 data: Arc::new(Buffer::from_vec64(v)),
241 dims: NdDims::from_shape(&[n]),
242 name: None,
243 }
244 }
245
246 #[inline]
250 pub fn ndim(&self) -> usize { self.dims.ndim() }
251
252 #[inline]
254 pub fn shape(&self) -> &[usize] { self.dims.shape() }
255
256 #[inline]
258 pub fn strides(&self) -> &[usize] { self.dims.strides() }
259
260 #[inline]
262 pub fn len(&self) -> usize { self.dims.len() }
263
264 #[inline]
268 pub fn n_obs(&self) -> usize {
269 assert!(self.ndim() > 0, "n_obs() requires an axis 0");
270 self.dims.shape()[0]
271 }
272
273 #[inline]
275 pub fn is_empty(&self) -> bool { self.len() == 0 }
276
277 pub fn is_contiguous(&self) -> bool {
280 let shape = self.dims.shape();
281 let strides = self.dims.strides();
282 let mut expected = 1;
283 for d in 0..shape.len() {
284 if strides[d] != expected {
285 return false;
286 }
287 expected *= shape[d];
288 }
289 true
290 }
291
292 pub fn has_nan(&self) -> bool {
294 self.into_iter().any(|v| v.is_nan())
295 }
296
297 pub fn nan_count(&self) -> usize {
299 self.into_iter().filter(|v| v.is_nan()).count()
300 }
301
302 #[inline]
306 pub fn get(&self, indices: &[usize]) -> T {
307 self.data.as_slice()[self.offset_of(indices)]
308 }
309
310 #[inline(always)]
315 pub unsafe fn get_unchecked(&self, indices: &[usize]) -> T {
316 let strides = self.dims.strides();
317 let mut off = 0;
318 for d in 0..indices.len() {
319 off += indices[d] * strides[d];
320 }
321 unsafe { *self.data.as_slice().get_unchecked(off) }
323 }
324
325 #[inline]
338 pub fn set(&mut self, indices: &[usize], value: T) {
339 let off = self.offset_of(indices);
340 Arc::make_mut(&mut self.data).as_mut_slice()[off] = value;
341 }
342
343 #[inline]
345 pub(crate) fn offset_of(&self, indices: &[usize]) -> usize {
346 offset_of_impl(indices, self.dims.shape(), self.dims.strides())
347 }
348
349 #[inline]
353 pub fn set_name(&mut self, name: impl Into<String>) {
354 self.name = Some(name.into());
355 }
356
357 #[inline]
359 pub fn as_slice(&self) -> &[T] {
360 self.data.as_slice()
361 }
362
363 #[inline]
366 pub fn as_mut_slice(&mut self) -> &mut [T] {
367 Arc::make_mut(&mut self.data).as_mut_slice()
368 }
369
370 pub fn fill_with(&mut self, value: T) {
372 if self.is_contiguous() {
374 Arc::make_mut(&mut self.data).as_mut_slice().fill(value);
375 return;
376 }
377 let offsets: Vec<usize> = {
379 let shape = self.dims.shape();
380 let strides = self.dims.strides();
381 let mut result = Vec::with_capacity(self.len());
382 let mut indices = vec![0usize; shape.len()];
383 for _ in 0..self.len() {
384 result.push(indices.iter().zip(strides).map(|(&i, &s)| i * s).sum());
385 let mut carry = true;
386 for d in 0..shape.len() {
387 if carry {
388 indices[d] += 1;
389 if indices[d] < shape[d] { carry = false; } else { indices[d] = 0; }
390 }
391 }
392 }
393 result
394 };
395 let buf = Arc::make_mut(&mut self.data).as_mut_slice();
396 for off in offsets { buf[off] = value; }
397 }
398
399 #[inline]
405 pub fn col(&self, col: usize) -> &[T] {
406 let shape = self.dims.shape();
407 assert_eq!(shape.len(), 2, "col() requires a 2D array");
408 assert!(col < shape[1], "Column index out of bounds");
409 assert_eq!(
410 self.dims.strides()[0], 1,
411 "col() requires unit stride on axis 0; call to_contiguous() first"
412 );
413 let stride = self.dims.strides()[1];
414 let start = col * stride;
415 &self.data.as_slice()[start..start + shape[0]]
416 }
417
418 #[inline]
423 pub fn col_mut(&mut self, col: usize) -> &mut [T] {
424 let shape = self.dims.shape();
425 assert_eq!(shape.len(), 2, "col_mut() requires a 2D array");
426 assert!(col < shape[1], "Column index out of bounds");
427 assert_eq!(
428 self.dims.strides()[0], 1,
429 "col_mut() requires unit stride on axis 0; call to_contiguous() first"
430 );
431 let stride = self.dims.strides()[1];
432 let n_rows = shape[0];
433 let start = col * stride;
434 &mut Arc::make_mut(&mut self.data).as_mut_slice()[start..start + n_rows]
435 }
436
437 pub fn columns(&self) -> Vec<&[T]> {
440 let shape = self.dims.shape();
441 assert_eq!(shape.len(), 2, "columns() requires a 2D array");
442 assert_eq!(
443 self.dims.strides()[0], 1,
444 "columns() requires unit stride on axis 0; call to_contiguous() first"
445 );
446 let stride = self.dims.strides()[1];
447 let n_rows = shape[0];
448 let buf = self.data.as_slice();
449 (0..shape[1])
450 .map(|c| &buf[c * stride..c * stride + n_rows])
451 .collect()
452 }
453
454 pub fn columns_mut(&mut self) -> Vec<&mut [T]> {
457 let shape = self.dims.shape();
458 assert_eq!(shape.len(), 2, "columns_mut() requires a 2D array");
459 assert_eq!(
460 self.dims.strides()[0], 1,
461 "columns_mut() requires unit stride on axis 0; call to_contiguous() first"
462 );
463 let n_rows = shape[0];
464 let n_cols = shape[1];
465 let stride = self.dims.strides()[1];
466 let ptr = Arc::make_mut(&mut self.data).as_mut_slice().as_mut_ptr();
467 let mut result = Vec::with_capacity(n_cols);
468 for c in 0..n_cols {
469 let start = c * stride;
470 unsafe {
473 let col_ptr = ptr.add(start);
474 result.push(std::slice::from_raw_parts_mut(col_ptr, n_rows));
475 }
476 }
477 result
478 }
479
480 #[cfg(feature = "views")]
486 pub fn as_view(&self) -> NdArrayV<T> {
487 NdArrayV::from_ndarray(self.clone())
488 }
489
490 #[cfg(feature = "views")]
500 pub fn obs(&self, idx: usize) -> NdArrayV<T> {
501 self.as_view().obs(idx)
502 }
503
504 #[inline]
508 pub fn m(&self) -> i32 {
509 assert_eq!(self.ndim(), 2, "m() requires a 2D array");
510 self.dims.shape()[0] as i32
511 }
512
513 #[inline]
515 pub fn n(&self) -> i32 {
516 assert_eq!(self.ndim(), 2, "n() requires a 2D array");
517 self.dims.shape()[1] as i32
518 }
519
520 #[inline]
522 pub fn lda(&self) -> i32 {
523 assert_eq!(self.ndim(), 2, "lda() requires a 2D array");
524 self.dims.strides()[1] as i32
525 }
526
527 pub fn reshape(&self, new_shape: &[usize]) -> Result<NdArray<T>, MinarrowError> {
534 let new_len: usize = new_shape.iter().product();
535 if new_len != self.len() {
536 return Err(MinarrowError::ShapeError {
537 message: format!(
538 "reshape: cannot reshape array of size {} into shape {:?}",
539 self.len(), new_shape
540 ),
541 });
542 }
543 let flat: Vec64<T> = self.into_iter().collect();
544 let mut result = NdArray::from_slice(&flat, new_shape);
545 result.name = self.name.clone();
546 Ok(result)
547 }
548
549 pub fn transpose(&self) -> NdArray<T> {
555 let shape = self.dims.shape();
556 let ndim = shape.len();
557 if ndim <= 1 {
558 let mut result = self.to_contiguous();
559 result.name = self.name.clone();
560 return result;
561 }
562 if ndim == 2 {
563 let (n_rows, n_cols) = (shape[0], shape[1]);
564 let new_shape = [n_cols, n_rows];
565 let mut result = NdArray::new(&new_shape);
566 let src_stride = self.dims.strides()[1];
567 let dst_stride = result.dims.strides()[1];
568 let src = self.data.as_slice();
569 let dst = Arc::make_mut(&mut result.data).as_mut_slice();
570 for r in 0..n_rows {
571 for c in 0..n_cols {
572 dst[r * dst_stride + c] = src[c * src_stride + r];
573 }
574 }
575 result.name = self.name.clone();
576 return result;
577 }
578
579 let new_shape: Vec<usize> = shape.iter().rev().copied().collect();
583 let rev_strides: Vec<usize> = self.dims.strides().iter().rev().copied().collect();
584 let src = self.data.as_slice();
585 let total = self.len();
586 let mut buf = Vec64::with_capacity(total);
587 let mut indices = vec![0usize; ndim];
588 for _ in 0..total {
589 let offset: usize = indices.iter()
590 .zip(rev_strides.iter())
591 .map(|(&i, &s)| i * s)
592 .sum();
593 buf.push(src[offset]);
594 let mut carry = true;
595 for d in 0..ndim {
596 if carry {
597 indices[d] += 1;
598 if indices[d] < new_shape[d] {
599 carry = false;
600 } else {
601 indices[d] = 0;
602 }
603 }
604 }
605 }
606 NdArray {
607 data: Arc::new(Buffer::from_vec64(buf)),
608 dims: NdDims::from_shape(&new_shape),
609 name: self.name.clone(),
610 }
611 }
612
613 pub fn flatten(&self) -> NdArray<T> {
615 let flat: Vec64<T> = self.into_iter().collect();
616 let n = flat.len();
617 NdArray {
618 data: Arc::new(Buffer::from_vec64(flat)),
619 dims: NdDims::from_shape(&[n]),
620 name: self.name.clone(),
621 }
622 }
623
624 pub fn to_contiguous(&self) -> NdArray<T> {
627 if self.is_contiguous() {
628 return self.clone();
629 }
630 let mut result = NdArray::from_slice(
631 &self.into_iter().collect::<Vec64<T>>(),
632 self.shape(),
633 );
634 result.name = self.name.clone();
635 result
636 }
637
638 #[cfg(all(feature = "views", feature = "select"))]
657 pub fn slice(&self, selection: &[&dyn DataSelector]) -> NdArrayV<T> {
658 assert_eq!(
659 selection.len(), self.ndim(),
660 "slice(): expected {} axes, got {}", self.ndim(), selection.len()
661 );
662
663 let shape = self.dims.shape();
664 let strides = self.dims.strides();
665
666 let mut new_offset: usize = 0;
668 let mut new_shape = Vec::with_capacity(self.ndim());
669 let mut new_strides = Vec::with_capacity(self.ndim());
670
671 for (d, sel) in selection.iter().enumerate() {
672 let (start, end, collapse) = sel.resolve_axis(shape[d]);
673 assert!(
674 end <= shape[d],
675 "slice(): end {} out of bounds for axis {} (size {})", end, d, shape[d]
676 );
677 new_offset += start * strides[d];
678 if !collapse {
679 new_shape.push(end - start);
680 new_strides.push(strides[d]);
681 }
682 }
683
684 NdArrayV::new(self.clone(), new_offset, &new_shape, &new_strides)
685 }
686
687 pub fn apply(&self, f: impl Fn(T) -> T) -> NdArray<T> {
693 let flat: Vec64<T> = self.into_iter().map(f).collect();
694 let mut result = NdArray::from_slice(&flat, self.shape());
695 result.name = self.name.clone();
696 result
697 }
698
699 pub fn apply_mut(&mut self, f: impl Fn(T) -> T) {
703 if self.is_contiguous() {
704 for v in Arc::make_mut(&mut self.data).as_mut_slice() {
705 *v = f(*v);
706 }
707 return;
708 }
709 let shape = self.dims.shape().to_vec();
712 let strides = self.dims.strides().to_vec();
713 let total = self.len();
714 let buf = Arc::make_mut(&mut self.data).as_mut_slice();
715 let mut indices = vec![0usize; shape.len()];
716 for _ in 0..total {
717 let offset: usize = indices.iter().zip(strides.iter()).map(|(&i, &s)| i * s).sum();
718 buf[offset] = f(buf[offset]);
719 let mut carry = true;
720 for d in 0..shape.len() {
721 if carry {
722 indices[d] += 1;
723 if indices[d] < shape[d] {
724 carry = false;
725 } else {
726 indices[d] = 0;
727 }
728 }
729 }
730 }
731 }
732
733 #[cfg(feature = "views")]
738 pub fn apply_axis(&self, axis: usize, mut f: impl FnMut(NdArrayV<T>) -> T) -> NdArray<T> {
739 let shape = self.dims.shape();
740 let strides = self.dims.strides();
741 let ndim = shape.len();
742 assert!(ndim >= 2, "apply_axis requires a 2D or higher array");
743 assert!(axis < ndim, "apply_axis: axis {} out of bounds for {}D array", axis, ndim);
744
745 let out_shape: Vec<usize> = shape
746 .iter()
747 .enumerate()
748 .filter(|(d, _)| *d != axis)
749 .map(|(_, &s)| s)
750 .collect();
751 let out_dims: Vec<usize> = (0..ndim).filter(|&d| d != axis).collect();
752 let total: usize = out_shape.iter().product();
753
754 let lane_shape = [shape[axis]];
755 let lane_strides = [strides[axis]];
756
757 let mut flat = Vec64::with_capacity(total);
760 let mut indices = vec![0usize; out_shape.len()];
761 for _ in 0..total {
762 let offset: usize = indices
763 .iter()
764 .zip(out_dims.iter())
765 .map(|(&i, &d)| i * strides[d])
766 .sum();
767 let lane = NdArrayV::new(self.clone(), offset, &lane_shape, &lane_strides);
768 flat.push(f(lane));
769 let mut carry = true;
770 for d in 0..out_shape.len() {
771 if carry {
772 indices[d] += 1;
773 if indices[d] < out_shape[d] {
774 carry = false;
775 } else {
776 indices[d] = 0;
777 }
778 }
779 }
780 }
781 let mut result = NdArray::from_slice(&flat, &out_shape);
782 result.name = self.name.clone();
783 result
784 }
785
786 #[cfg(feature = "dlpack")]
796 pub fn to_dlpack(self) -> DLPackTensor {
797 export_to_dlpack(self)
798 }
799
800 #[cfg(feature = "dlpack")]
804 pub fn to_dlpack_versioned(self) -> DLPackTensorVersioned {
805 export_to_dlpack_versioned(self)
806 }
807
808 #[cfg(feature = "parallel_proc")]
813 pub fn par_iter(&self) -> rayon::slice::Iter<'_, T>
814 where
815 T: Send + Sync,
816 {
817 use rayon::prelude::*;
818 self.data.as_slice().par_iter()
819 }
820
821 #[cfg(feature = "parallel_proc")]
824 pub fn par_chunks(&self, chunk_size: usize) -> rayon::slice::Chunks<'_, T>
825 where
826 T: Send + Sync,
827 {
828 use rayon::prelude::*;
829 self.data.as_slice().par_chunks(chunk_size)
830 }
831
832 #[cfg(feature = "views")]
835 pub fn iter_obs(&self) -> impl Iterator<Item = (usize, NdArrayV<T>)> + '_ {
836 assert!(self.ndim() >= 2, "iter_obs() requires a 2D or higher array");
837 let n_obs = self.shape()[0];
838 (0..n_obs).map(move |i| (i, self.obs(i)))
839 }
840
841 #[cfg(all(feature = "parallel_proc", feature = "views"))]
844 pub fn par_iter_obs(&self) -> impl rayon::iter::ParallelIterator<Item = (usize, NdArrayV<T>)> + '_
845 where
846 T: Send + Sync,
847 {
848 use rayon::prelude::*;
849 assert!(self.ndim() >= 2, "par_iter_obs() requires a 2D or higher array");
850 let n_obs = self.dims.shape()[0];
851 (0..n_obs).into_par_iter().map(move |i| (i, self.obs(i)))
852 }
853
854 pub(crate) fn iter_axis0_run(&self, run_idx: usize) -> impl ExactSizeIterator<Item = T> + '_ {
858 assert!(self.ndim() > 0, "axis-0 iteration requires an axis 0");
859 let n_runs: usize = self.shape()[1..].iter().product();
860 assert!(run_idx < n_runs, "axis-0 run {} out of bounds ({})", run_idx, n_runs);
861
862 let mut rem = run_idx;
863 let mut offset = 0;
864 for d in 1..self.ndim() {
865 offset += (rem % self.shape()[d]) * self.strides()[d];
866 rem /= self.shape()[d];
867 }
868 let stride = self.strides()[0];
869 (0..self.shape()[0]).map(move |i| self.data.as_slice()[offset + i * stride])
870 }
871}
872
873impl NdArray<f64> {
876 pub fn to_table(self, fields: Option<Vec<Field>>) -> Result<Table, MinarrowError> {
885 let shape = self.dims.shape();
886 if shape.len() != 2 {
887 return Err(MinarrowError::ShapeError {
888 message: format!("to_table requires a 2D array, got {}D", shape.len()),
889 });
890 }
891 let n_cols = shape[1];
892 let fields = match fields {
893 Some(fields) => {
894 if fields.len() != n_cols {
895 return Err(MinarrowError::ShapeError {
896 message: format!(
897 "to_table: expected {} fields for {} columns, got {}",
898 n_cols, n_cols, fields.len()
899 ),
900 });
901 }
902 for (index, field) in fields.iter().enumerate() {
903 if field.dtype != ArrowType::Float64 {
904 return Err(MinarrowError::TypeError {
905 from: "Field",
906 to: "Float64 NdArray column",
907 message: Some(format!(
908 "to_table: field {} ('{}') has dtype {:?}, expected Float64",
909 index, field.name, field.dtype
910 )),
911 });
912 }
913 if field.nullable {
914 return Err(MinarrowError::NullError {
915 message: Some(format!(
916 "to_table: field {} ('{}') is nullable, but NdArray columns are non-nullable",
917 index, field.name
918 )),
919 });
920 }
921 }
922 fields
923 }
924 None => (0..n_cols)
925 .map(|i| Field::new(format!("col_{}", i), ArrowType::Float64, false, None))
926 .collect(),
927 };
928 let this = if self.dims.strides()[0] == 1 { self } else { self.to_contiguous() };
931 let n_rows = this.dims.shape()[0];
932 let stride = this.dims.strides()[1];
933 let name = this.name;
934 let buf = this.data.as_slice();
935
936 let mut cols = Vec::with_capacity(n_cols);
937 for (i, field) in fields.into_iter().enumerate() {
938 let col_start = i * stride;
939 let col: Buffer<f64> = Buffer::from_slice(&buf[col_start..col_start + n_rows]);
940 let float_arr = FloatArray::new(col, None);
941 let array = Array::NumericArray(NumericArray::Float64(Arc::new(float_arr)));
942 cols.push(FieldArray::new(field, array));
943 }
944
945 Ok(Table::new(name.unwrap_or_default(), Some(cols)))
946 }
947
948 pub fn to_array(self) -> Result<Array, MinarrowError> {
952 if self.ndim() != 1 {
953 return Err(MinarrowError::ShapeError {
954 message: format!("to_array requires a 1D array, got {}D", self.ndim()),
955 });
956 }
957 let buffer = if self.dims.strides()[0] == 1 && self.data.len() == self.len() {
958 Arc::try_unwrap(self.data).unwrap_or_else(|arc| (*arc).clone())
959 } else {
960 Buffer::from_vec64((&self).into_iter().collect())
961 };
962 let float_arr = FloatArray::new(buffer, None);
963 Ok(Array::NumericArray(NumericArray::Float64(Arc::new(float_arr))))
964 }
965
966 #[cfg(feature = "matrix")]
973 pub fn to_matrix(self) -> Result<Matrix, MinarrowError> {
974 let shape = self.dims.shape();
975 if shape.len() != 2 {
976 return Err(MinarrowError::ShapeError {
977 message: format!("to_matrix requires a 2D array, got {}D", shape.len()),
978 });
979 }
980 let n_rows = shape[0];
981 let n_cols = shape[1];
982 let strides = self.dims.strides();
983
984 if strides[0] == 1 && strides[1] == aligned_stride(n_rows) {
985 return Ok(Matrix {
986 n_rows,
987 n_cols,
988 stride: strides[1],
989 data: Arc::try_unwrap(self.data).unwrap_or_else(|arc| (*arc).clone()),
990 name: self.name,
991 });
992 }
993
994 let name = self.name.clone();
995 let compact: Vec64<f64> = self.into_iter().collect();
996 Ok(Matrix::from_f64_unaligned(&compact, n_rows, n_cols, name))
997 }
998}
999
1000#[derive(Clone, PartialEq)]
1009pub(crate) enum NdDims {
1010 D1 { shape: [usize; 1], strides: [usize; 1] },
1011 D2 { shape: [usize; 2], strides: [usize; 2] },
1012 D3 { shape: [usize; 3], strides: [usize; 3] },
1013 D4 { shape: [usize; 4], strides: [usize; 4] },
1014 D5 { shape: [usize; 5], strides: [usize; 5] },
1015 Dn { shape: Box<[usize]>, strides: Box<[usize]> },
1016}
1017
1018impl NdDims {
1019 pub(crate) fn from_shape(shape: &[usize]) -> Self {
1022 let strides = col_major_strides(shape);
1023 Self::from_shape_and_strides(shape, &strides)
1024 }
1025
1026 pub(crate) fn from_shape_and_strides(shape: &[usize], strides: &[usize]) -> Self {
1028 assert_eq!(
1029 shape.len(),
1030 strides.len(),
1031 "NdArray: shape rank {} does not match strides rank {}",
1032 shape.len(),
1033 strides.len()
1034 );
1035 match shape.len() {
1036 1 => NdDims::D1 {
1037 shape: [shape[0]],
1038 strides: [strides[0]],
1039 },
1040 2 => NdDims::D2 {
1041 shape: [shape[0], shape[1]],
1042 strides: [strides[0], strides[1]],
1043 },
1044 3 => NdDims::D3 {
1045 shape: [shape[0], shape[1], shape[2]],
1046 strides: [strides[0], strides[1], strides[2]],
1047 },
1048 4 => NdDims::D4 {
1049 shape: [shape[0], shape[1], shape[2], shape[3]],
1050 strides: [strides[0], strides[1], strides[2], strides[3]],
1051 },
1052 5 => NdDims::D5 {
1053 shape: [shape[0], shape[1], shape[2], shape[3], shape[4]],
1054 strides: [strides[0], strides[1], strides[2], strides[3], strides[4]],
1055 },
1056 _ => NdDims::Dn {
1057 shape: shape.into(),
1058 strides: strides.into(),
1059 },
1060 }
1061 }
1062
1063 #[inline]
1065 pub(crate) fn ndim(&self) -> usize {
1066 match self {
1067 NdDims::D1 { .. } => 1,
1068 NdDims::D2 { .. } => 2,
1069 NdDims::D3 { .. } => 3,
1070 NdDims::D4 { .. } => 4,
1071 NdDims::D5 { .. } => 5,
1072 NdDims::Dn { shape, .. } => shape.len(),
1073 }
1074 }
1075
1076 #[inline]
1078 pub(crate) fn shape(&self) -> &[usize] {
1079 match self {
1080 NdDims::D1 { shape, .. } => shape,
1081 NdDims::D2 { shape, .. } => shape,
1082 NdDims::D3 { shape, .. } => shape,
1083 NdDims::D4 { shape, .. } => shape,
1084 NdDims::D5 { shape, .. } => shape,
1085 NdDims::Dn { shape, .. } => shape,
1086 }
1087 }
1088
1089 #[inline]
1091 pub(crate) fn strides(&self) -> &[usize] {
1092 match self {
1093 NdDims::D1 { strides, .. } => strides,
1094 NdDims::D2 { strides, .. } => strides,
1095 NdDims::D3 { strides, .. } => strides,
1096 NdDims::D4 { strides, .. } => strides,
1097 NdDims::D5 { strides, .. } => strides,
1098 NdDims::Dn { strides, .. } => strides,
1099 }
1100 }
1101
1102 #[inline]
1104 pub(crate) fn len(&self) -> usize {
1105 self.shape().iter().product()
1106 }
1107}
1108
1109#[macro_export]
1123macro_rules! nd {
1124 ($($sel:expr),+ $(,)?) => {
1125 &[$(&$sel as &dyn $crate::traits::selection::DataSelector),+]
1126 };
1127}
1128
1129pub(crate) fn col_major_strides(shape: &[usize]) -> Vec<usize> {
1140 let mut strides = Vec::with_capacity(shape.len());
1141 if shape.is_empty() {
1142 return strides;
1143 }
1144 strides.push(1);
1145 for d in 1..shape.len() {
1146 strides.push(strides[d - 1] * shape[d - 1]);
1147 }
1148 strides
1149}
1150
1151pub(crate) fn buffer_len(shape: &[usize], strides: &[usize]) -> usize {
1153 if shape.iter().any(|&d| d == 0) {
1154 return 0;
1155 }
1156 let max_offset: usize = shape.iter()
1159 .zip(strides.iter())
1160 .map(|(&s, &st)| (s - 1) * st)
1161 .sum();
1162 max_offset + 1
1163}
1164
1165impl<'a, T: Float> IntoIterator for &'a NdArray<T> {
1174 type Item = T;
1175 type IntoIter = NdArrayIter<'a, T>;
1176
1177 #[inline]
1178 fn into_iter(self) -> NdArrayIter<'a, T> {
1179 let shape = self.dims.shape();
1180 let strides = self.dims.strides();
1181 if shape.is_empty() {
1182 return NdArrayIter {
1183 buf: self.data.as_slice(),
1184 n_inner: 1,
1185 inner_stride: 1,
1186 run_offsets: vec![0],
1187 run_idx: 0,
1188 inner_idx: 0,
1189 total: 1,
1190 yielded: 0,
1191 };
1192 }
1193 let n_inner = shape[0];
1194
1195 let n_runs: usize = shape[1..].iter().product();
1197
1198 let mut run_offsets = Vec::with_capacity(n_runs);
1203 if shape.len() <= 1 {
1204 run_offsets.push(0);
1205 } else {
1206 let outer_shape = &shape[1..];
1207 let outer_strides = &strides[1..];
1208 let mut outer_indices = vec![0usize; outer_shape.len()];
1209 for _ in 0..n_runs {
1210 let off: usize = outer_indices.iter()
1211 .zip(outer_strides.iter())
1212 .map(|(&i, &s)| i * s)
1213 .sum();
1214 run_offsets.push(off);
1215 let mut carry = true;
1217 for d in 0..outer_shape.len() {
1218 if carry {
1219 outer_indices[d] += 1;
1220 if outer_indices[d] < outer_shape[d] {
1221 carry = false;
1222 } else {
1223 outer_indices[d] = 0;
1224 }
1225 }
1226 }
1227 }
1228 }
1229
1230 NdArrayIter {
1231 buf: self.data.as_slice(),
1232 n_inner,
1233 inner_stride: strides[0],
1234 run_offsets,
1235 run_idx: 0,
1236 inner_idx: 0,
1237 total: self.len(),
1238 yielded: 0,
1239 }
1240 }
1241}
1242
1243impl<T: Float> IntoIterator for NdArray<T> {
1245 type Item = T;
1246 type IntoIter = std::vec::IntoIter<T>;
1247
1248 #[inline]
1249 fn into_iter(self) -> std::vec::IntoIter<T> {
1250 let v: Vec<T> = (&self).into_iter().collect();
1251 v.into_iter()
1252 }
1253}
1254
1255pub struct NdArrayIter<'a, T> {
1262 pub(crate) buf: &'a [T],
1263 pub(crate) n_inner: usize,
1264 pub(crate) inner_stride: usize,
1265 pub(crate) run_offsets: Vec<usize>,
1266 pub(crate) run_idx: usize,
1267 pub(crate) inner_idx: usize,
1268 pub(crate) total: usize,
1269 pub(crate) yielded: usize,
1270}
1271
1272impl<'a, T: Float> Iterator for NdArrayIter<'a, T> {
1273 type Item = T;
1274
1275 #[inline]
1276 fn next(&mut self) -> Option<T> {
1277 if self.yielded >= self.total { return None; }
1278
1279 let val = self.buf[self.run_offsets[self.run_idx] + self.inner_idx * self.inner_stride];
1280 self.yielded += 1;
1281 self.inner_idx += 1;
1282 if self.inner_idx >= self.n_inner {
1283 self.inner_idx = 0;
1284 self.run_idx += 1;
1285 }
1286 Some(val)
1287 }
1288
1289 #[inline]
1290 fn size_hint(&self) -> (usize, Option<usize>) {
1291 let r = self.total - self.yielded;
1292 (r, Some(r))
1293 }
1294}
1295
1296impl<'a, T: Float> ExactSizeIterator for NdArrayIter<'a, T> {}
1297
1298#[inline]
1307pub(crate) fn offset_of_impl(indices: &[usize], shape: &[usize], strides: &[usize]) -> usize {
1308 assert_eq!(
1309 indices.len(),
1310 shape.len(),
1311 "NdArray: {} indices for a {}D array",
1312 indices.len(),
1313 shape.len()
1314 );
1315 let mut offset = 0;
1316 for d in 0..shape.len() {
1317 assert!(
1318 indices[d] < shape[d],
1319 "NdArray: index {} out of bounds for dim {} (size {})",
1320 indices[d], d, shape[d]
1321 );
1322 offset += indices[d] * strides[d];
1323 }
1324 offset
1325}
1326
1327impl<T: Float> Shape for NdArray<T> {
1332 fn shape(&self) -> ShapeDim {
1333 match self.dims.ndim() {
1334 0 => ShapeDim::Rank0(1),
1335 1 => ShapeDim::Rank1(self.dims.shape()[0]),
1336 2 => ShapeDim::Rank2 {
1337 rows: self.dims.shape()[0],
1338 cols: self.dims.shape()[1],
1339 },
1340 _ => ShapeDim::RankN(self.dims.shape().to_vec()),
1341 }
1342 }
1343}
1344
1345impl<T: Float> Concatenate for NdArray<T> {
1346 fn concat(self, other: Self) -> Result<Self, MinarrowError> {
1348 let s1 = self.dims.shape();
1349 let s2 = other.dims.shape();
1350 if s1.len() != s2.len() {
1351 return Err(MinarrowError::IncompatibleTypeError {
1352 from: "NdArray",
1353 to: "NdArray",
1354 message: Some(format!(
1355 "Cannot concatenate {}D and {}D arrays", s1.len(), s2.len()
1356 )),
1357 });
1358 }
1359 if s1.is_empty() {
1360 return Err(MinarrowError::ShapeError {
1361 message: "Cannot concatenate rank-zero arrays along axis 0".to_string(),
1362 });
1363 }
1364 for d in 1..s1.len() {
1365 if s1[d] != s2[d] {
1366 return Err(MinarrowError::IncompatibleTypeError {
1367 from: "NdArray",
1368 to: "NdArray",
1369 message: Some(format!(
1370 "Dimension {} mismatch: {} vs {}", d, s1[d], s2[d]
1371 )),
1372 });
1373 }
1374 }
1375
1376 let mut new_shape: Vec<usize> = s1.to_vec();
1377 new_shape[0] += s2[0];
1378
1379 let name = match (&self.name, &other.name) {
1381 (Some(a), Some(b)) => Some(format!("{}+{}", a, b)),
1382 (Some(a), None) => Some(a.clone()),
1383 (None, Some(b)) => Some(b.clone()),
1384 (None, None) => None,
1385 };
1386
1387 if s1.len() == 1 {
1389 let mut flat = Vec64::with_capacity(self.len() + other.len());
1390 flat.extend(&self);
1391 flat.extend(&other);
1392 let mut result = NdArray::from_slice(&flat, &new_shape);
1393 result.name = name;
1394 return Ok(result);
1395 }
1396
1397 if s1.len() == 2 && self.dims.strides()[0] == 1 && other.dims.strides()[0] == 1 {
1399 let new_dims = NdDims::from_shape(&new_shape);
1400 let new_stride = new_dims.strides()[1];
1401 let total = buffer_len(&new_shape, new_dims.strides());
1402 let mut buf = Vec64::with_capacity(total);
1403 buf.0.resize(total, T::default());
1404 let dst = buf.as_mut_slice();
1405 for c in 0..s1[1] {
1406 let dst_start = c * new_stride;
1407 dst[dst_start..dst_start + s1[0]].copy_from_slice(self.col(c));
1408 dst[dst_start + s1[0]..dst_start + s1[0] + s2[0]].copy_from_slice(other.col(c));
1409 }
1410 return Ok(NdArray {
1411 data: Arc::new(Buffer::from_vec64(buf)),
1412 dims: new_dims,
1413 name,
1414 });
1415 }
1416
1417 let n1 = s1[0];
1420 let n2 = s2[0];
1421 let run_len = n1 + n2;
1422 let n_runs: usize = s1[1..].iter().product();
1423 let new_dims = NdDims::from_shape(&new_shape);
1424 let total = buffer_len(&new_shape, new_dims.strides());
1425 let mut buf = Vec64::with_capacity(total);
1426 buf.0.resize(total, T::default());
1427 let dst = buf.as_mut_slice();
1428 let mut it_a = (&self).into_iter();
1429 let mut it_b = (&other).into_iter();
1430 for r in 0..n_runs {
1431 let base = r * run_len;
1432 for i in 0..n1 {
1433 dst[base + i] = it_a.next().unwrap();
1434 }
1435 for i in 0..n2 {
1436 dst[base + n1 + i] = it_b.next().unwrap();
1437 }
1438 }
1439 Ok(NdArray {
1440 data: Arc::new(Buffer::from_vec64(buf)),
1441 dims: new_dims,
1442 name,
1443 })
1444 }
1445}
1446
1447#[cfg(all(feature = "views", feature = "select"))]
1453impl<T: Float> AxisSelection for NdArray<T> {
1454 type View = NdArrayV<T>;
1455
1456 fn s(&self, selection: &[&dyn DataSelector]) -> NdArrayV<T> {
1457 self.slice(selection)
1458 }
1459
1460 fn get_axis_count(&self) -> usize {
1461 self.ndim()
1462 }
1463}
1464
1465#[cfg(all(feature = "views", feature = "select"))]
1471impl<T: Float> RowSelection for NdArray<T> {
1472 type View = NdArrayV<T>;
1473
1474 fn r<S: DataSelector>(&self, selection: S) -> NdArrayV<T> {
1475 assert!(self.ndim() > 0, "row selection requires an axis 0");
1476 let n_obs = self.shape()[0];
1477 let indices = selection.resolve_indices(n_obs);
1478 if selection.is_contiguous() {
1479 let start = indices.first().copied().unwrap_or(0);
1480 let ranges: Vec<Range<usize>> = std::iter::once(start..start + indices.len())
1481 .chain(self.shape()[1..].iter().map(|&n| 0..n))
1482 .collect();
1483 let refs: Vec<&dyn DataSelector> = ranges.iter().map(|r| r as _).collect();
1484 return self.slice(&refs);
1485 }
1486 NdArrayV::from_ndarray(gather_obs_impl(
1487 &indices,
1488 self.shape(),
1489 self.name.clone(),
1490 |idx| self.get(idx),
1491 ))
1492 }
1493
1494 fn get_row_count(&self) -> usize {
1495 self.n_obs()
1496 }
1497}
1498
1499#[cfg(all(feature = "views", feature = "select"))]
1504pub(crate) fn gather_obs_impl<T: Float>(
1505 indices: &[usize],
1506 shape: &[usize],
1507 name: Option<String>,
1508 get: impl Fn(&[usize]) -> T,
1509) -> NdArray<T> {
1510 let mut out_shape = shape.to_vec();
1511 out_shape[0] = indices.len();
1512 let total: usize = out_shape.iter().product();
1513
1514 let mut flat = Vec64::with_capacity(total);
1515 let ndim = shape.len();
1516 let mut idx = vec![0usize; ndim];
1517 let inner_runs: usize = shape[1..].iter().product::<usize>().max(1);
1518 for _ in 0..inner_runs {
1519 for &obs in indices {
1520 idx[0] = obs;
1521 flat.push(get(&idx));
1522 }
1523 let mut carry = true;
1525 for d in 1..ndim {
1526 if carry {
1527 idx[d] += 1;
1528 if idx[d] < shape[d] {
1529 carry = false;
1530 } else {
1531 idx[d] = 0;
1532 }
1533 }
1534 }
1535 }
1536 let mut result = NdArray::from_slice(&flat, &out_shape);
1537 result.name = name;
1538 result
1539}
1540
1541impl<T: Float> Index<usize> for NdArray<T> {
1549 type Output = [T];
1550
1551 #[inline]
1552 fn index(&self, idx: usize) -> &[T] {
1553 let shape = self.dims.shape();
1554 let strides = self.dims.strides();
1555 match shape.len() {
1556 0 => panic!("NdArray: a rank-zero array has no axis to index with usize"),
1557 1 => {
1558 assert!(idx < shape[0], "NdArray: index {} out of bounds (size {})", idx, shape[0]);
1559 &self.data.as_slice()[idx..idx + 1]
1560 }
1561 2 => {
1562 assert!(idx < shape[1], "NdArray: column {} out of bounds (n_cols {})", idx, shape[1]);
1563 let start = idx * strides[1];
1564 &self.data.as_slice()[start..start + shape[0]]
1565 }
1566 n => {
1567 assert!(
1569 self.is_contiguous(),
1570 "outermost-axis indexing on 3D+ requires a contiguous layout, use slice() for strided access"
1571 );
1572 let last = n - 1;
1573 assert!(idx < shape[last], "index out of bounds for axis {}", last);
1574 let start = idx * strides[last];
1575 &self.data.as_slice()[start..start + strides[last]]
1576 }
1577 }
1578 }
1579}
1580
1581impl<T: Float> IndexMut<usize> for NdArray<T> {
1582 #[inline]
1583 fn index_mut(&mut self, idx: usize) -> &mut [T] {
1584 let shape = self.dims.shape().to_vec();
1585 let strides = self.dims.strides().to_vec();
1586 match shape.len() {
1587 0 => panic!("NdArray: a rank-zero array has no axis to index with usize"),
1588 1 => {
1589 assert!(idx < shape[0], "NdArray: index {} out of bounds (size {})", idx, shape[0]);
1590 &mut Arc::make_mut(&mut self.data).as_mut_slice()[idx..idx + 1]
1591 }
1592 2 => {
1593 assert!(idx < shape[1], "NdArray: column {} out of bounds (n_cols {})", idx, shape[1]);
1594 let start = idx * strides[1];
1595 let n_rows = shape[0];
1596 &mut Arc::make_mut(&mut self.data).as_mut_slice()[start..start + n_rows]
1597 }
1598 n => {
1599 assert!(
1600 self.is_contiguous(),
1601 "outermost-axis indexing on 3D+ requires a contiguous layout, use slice() for strided access"
1602 );
1603 let last = n - 1;
1604 assert!(idx < shape[last], "index out of bounds for axis {}", last);
1605 let start = idx * strides[last];
1606 &mut Arc::make_mut(&mut self.data).as_mut_slice()[start..start + strides[last]]
1607 }
1608 }
1609 }
1610}
1611
1612impl<T: Float> Index<Range<usize>> for NdArray<T> {
1622 type Output = [T];
1623
1624 #[inline]
1625 fn index(&self, range: Range<usize>) -> &[T] {
1626 let shape = self.dims.shape();
1627 let strides = self.dims.strides();
1628 match shape.len() {
1629 0 => panic!("NdArray: a rank-zero array has no axis to range-index"),
1630 1 => &self.data.as_slice()[range],
1631 _ => {
1632 assert!(
1633 self.is_contiguous(),
1634 "range indexing requires a contiguous layout, use slice() for strided access"
1635 );
1636 let last = shape.len() - 1;
1637 assert!(range.end <= shape[last], "NdArray: range end {} out of bounds (size {})", range.end, shape[last]);
1638 let start = range.start * strides[last];
1639 let end = range.end * strides[last];
1640 &self.data.as_slice()[start..end]
1641 }
1642 }
1643 }
1644}
1645
1646impl<T: Float> IndexMut<Range<usize>> for NdArray<T> {
1647 #[inline]
1648 fn index_mut(&mut self, range: Range<usize>) -> &mut [T] {
1649 let shape = self.dims.shape().to_vec();
1650 let strides = self.dims.strides().to_vec();
1651 match shape.len() {
1652 0 => panic!("NdArray: a rank-zero array has no axis to range-index"),
1653 1 => &mut Arc::make_mut(&mut self.data).as_mut_slice()[range],
1654 _ => {
1655 assert!(
1656 self.is_contiguous(),
1657 "range indexing requires a contiguous layout, use slice() for strided access"
1658 );
1659 let last = shape.len() - 1;
1660 assert!(range.end <= shape[last], "NdArray: range end {} out of bounds (size {})", range.end, shape[last]);
1661 let start = range.start * strides[last];
1662 let end = range.end * strides[last];
1663 &mut Arc::make_mut(&mut self.data).as_mut_slice()[start..end]
1664 }
1665 }
1666 }
1667}
1668
1669impl<T: Float> Index<RangeFrom<usize>> for NdArray<T> {
1670 type Output = [T];
1671
1672 #[inline]
1673 fn index(&self, range: RangeFrom<usize>) -> &[T] {
1674 assert!(self.ndim() > 0, "NdArray: a rank-zero array has no axis to range-index");
1675 let last_dim = self.dims.shape().len() - 1;
1676 let end = self.dims.shape()[last_dim];
1677 &self[range.start..end]
1678 }
1679}
1680
1681impl<T: Float> Index<RangeTo<usize>> for NdArray<T> {
1682 type Output = [T];
1683
1684 #[inline]
1685 fn index(&self, range: RangeTo<usize>) -> &[T] {
1686 &self[0..range.end]
1687 }
1688}
1689
1690impl<T: Float> Index<RangeFull> for NdArray<T> {
1691 type Output = [T];
1692
1693 #[inline]
1694 fn index(&self, _: RangeFull) -> &[T] {
1695 assert!(self.ndim() > 0, "NdArray: a rank-zero array has no axis to range-index");
1696 let last_dim = self.dims.shape().len() - 1;
1697 let end = self.dims.shape()[last_dim];
1698 &self[0..end]
1699 }
1700}
1701
1702impl<T: Float> Index<()> for NdArray<T> {
1705 type Output = T;
1706 #[inline]
1707 fn index(&self, (): ()) -> &T {
1708 &self.data.as_slice()[self.offset_of(&[])]
1709 }
1710}
1711
1712impl<T: Float> Index<(usize,)> for NdArray<T> {
1713 type Output = T;
1714 #[inline]
1715 fn index(&self, (i,): (usize,)) -> &T {
1716 &self.data.as_slice()[self.offset_of(&[i])]
1717 }
1718}
1719
1720impl<T: Float> Index<(usize, usize)> for NdArray<T> {
1721 type Output = T;
1722 #[inline]
1723 fn index(&self, (i, j): (usize, usize)) -> &T {
1724 &self.data.as_slice()[self.offset_of(&[i, j])]
1725 }
1726}
1727
1728impl<T: Float> Index<(usize, usize, usize)> for NdArray<T> {
1729 type Output = T;
1730 #[inline]
1731 fn index(&self, (i, j, k): (usize, usize, usize)) -> &T {
1732 &self.data.as_slice()[self.offset_of(&[i, j, k])]
1733 }
1734}
1735
1736impl<T: Float> Index<(usize, usize, usize, usize)> for NdArray<T> {
1737 type Output = T;
1738 #[inline]
1739 fn index(&self, (i, j, k, l): (usize, usize, usize, usize)) -> &T {
1740 &self.data.as_slice()[self.offset_of(&[i, j, k, l])]
1741 }
1742}
1743
1744impl<T: Float> Index<(usize, usize, usize, usize, usize)> for NdArray<T> {
1745 type Output = T;
1746 #[inline]
1747 fn index(&self, (i, j, k, l, m): (usize, usize, usize, usize, usize)) -> &T {
1748 &self.data.as_slice()[self.offset_of(&[i, j, k, l, m])]
1749 }
1750}
1751
1752impl<T: Float> IndexMut<(usize,)> for NdArray<T> {
1753 #[inline]
1754 fn index_mut(&mut self, (i,): (usize,)) -> &mut T {
1755 let off = self.offset_of(&[i]);
1756 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1757 }
1758}
1759
1760impl<T: Float> IndexMut<()> for NdArray<T> {
1761 #[inline]
1762 fn index_mut(&mut self, (): ()) -> &mut T {
1763 let off = self.offset_of(&[]);
1764 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1765 }
1766}
1767
1768impl<T: Float> IndexMut<(usize, usize)> for NdArray<T> {
1769 #[inline]
1770 fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut T {
1771 let off = self.offset_of(&[i, j]);
1772 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1773 }
1774}
1775
1776impl<T: Float> IndexMut<(usize, usize, usize)> for NdArray<T> {
1777 #[inline]
1778 fn index_mut(&mut self, (i, j, k): (usize, usize, usize)) -> &mut T {
1779 let off = self.offset_of(&[i, j, k]);
1780 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1781 }
1782}
1783
1784impl<T: Float> IndexMut<(usize, usize, usize, usize)> for NdArray<T> {
1785 #[inline]
1786 fn index_mut(&mut self, (i, j, k, l): (usize, usize, usize, usize)) -> &mut T {
1787 let off = self.offset_of(&[i, j, k, l]);
1788 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1789 }
1790}
1791
1792impl<T: Float> IndexMut<(usize, usize, usize, usize, usize)> for NdArray<T> {
1793 #[inline]
1794 fn index_mut(&mut self, (i, j, k, l, m): (usize, usize, usize, usize, usize)) -> &mut T {
1795 let off = self.offset_of(&[i, j, k, l, m]);
1796 &mut Arc::make_mut(&mut self.data).as_mut_slice()[off]
1797 }
1798}
1799
1800impl<T: Float> From<&[T]> for NdArray<T> {
1804 fn from(data: &[T]) -> Self {
1805 NdArray {
1806 data: Arc::new(Buffer::from_slice(data)),
1807 dims: NdDims::from_shape(&[data.len()]),
1808 name: None,
1809 }
1810 }
1811}
1812
1813impl<T: Float> From<Vec64<T>> for NdArray<T> {
1815 fn from(v: Vec64<T>) -> Self {
1816 let n = v.len();
1817 NdArray {
1818 data: Arc::new(Buffer::from_vec64(v)),
1819 dims: NdDims::from_shape(&[n]),
1820 name: None,
1821 }
1822 }
1823}
1824
1825impl<T: Float> From<&[Vec<T>]> for NdArray<T> {
1827 fn from(columns: &[Vec<T>]) -> Self {
1828 let n_cols = columns.len();
1829 if n_cols == 0 {
1830 return NdArray::new(&[0, 0]);
1831 }
1832 let n_rows = columns[0].len();
1833 for col in columns {
1834 assert_eq!(col.len(), n_rows, "Column length mismatch");
1835 }
1836 let shape = [n_rows, n_cols];
1837 let dims = NdDims::from_shape(&shape);
1838 let stride = dims.strides()[1];
1839 let total = buffer_len(&shape, dims.strides());
1840 let mut buf = Vec64::with_capacity(total);
1841 buf.0.resize(total, T::default());
1842 for (c, col) in columns.iter().enumerate() {
1843 let start = c * stride;
1844 buf.as_mut_slice()[start..start + n_rows].copy_from_slice(col);
1845 }
1846 NdArray { data: Arc::new(Buffer::from_vec64(buf)), dims, name: None }
1847 }
1848}
1849
1850impl<T: Float> From<&[FloatArray<T>]> for NdArray<T> {
1852 fn from(columns: &[FloatArray<T>]) -> Self {
1853 let n_cols = columns.len();
1854 if n_cols == 0 {
1855 return NdArray::new(&[0, 0]);
1856 }
1857 let n_rows = columns[0].data.len();
1858 for col in columns {
1859 assert_eq!(col.data.len(), n_rows, "Column length mismatch");
1860 }
1861 let shape = [n_rows, n_cols];
1862 let dims = NdDims::from_shape(&shape);
1863 let stride = dims.strides()[1];
1864 let total = buffer_len(&shape, dims.strides());
1865 let mut buf = Vec64::with_capacity(total);
1866 buf.0.resize(total, T::default());
1867 for (c, col) in columns.iter().enumerate() {
1868 let start = c * stride;
1869 buf.as_mut_slice()[start..start + n_rows].copy_from_slice(col.data.as_slice());
1870 }
1871 NdArray { data: Arc::new(Buffer::from_vec64(buf)), dims, name: None }
1872 }
1873}
1874
1875#[cfg(feature = "matrix")]
1879impl From<Matrix> for NdArray<f64> {
1880 fn from(mat: Matrix) -> Self {
1881 let shape = [mat.n_rows, mat.n_cols];
1882 let strides = [1, mat.stride];
1883 NdArray {
1884 data: Arc::new(mat.data),
1885 dims: NdDims::from_shape_and_strides(&shape, &strides),
1886 name: mat.name,
1887 }
1888 }
1889}
1890
1891impl TryFrom<&Table> for NdArray<f64> {
1893 type Error = MinarrowError;
1894
1895 fn try_from(table: &Table) -> Result<Self, Self::Error> {
1896 let n_cols = table.n_cols();
1897 let n_rows = table.n_rows;
1898 if n_cols == 0 {
1899 return Ok(NdArray::new(&[0, 0]));
1900 }
1901
1902 let shape = [n_rows, n_cols];
1903 let dims = NdDims::from_shape(&shape);
1904 let stride = dims.strides()[1];
1905 let total = buffer_len(&shape, dims.strides());
1906 let mut buf = Vec64::with_capacity(total);
1907 buf.0.resize(total, 0.0);
1908
1909 for (col_idx, fa) in table.cols.iter().enumerate() {
1910 let numeric = fa.array.try_num().map_err(|_| MinarrowError::TypeError {
1911 from: "non-numeric",
1912 to: "Float64",
1913 message: Some(format!("column {} is not numeric", col_idx)),
1914 })?;
1915 let f64_arr = numeric.try_f64()?;
1916 if f64_arr.data.len() != n_rows {
1917 return Err(MinarrowError::ColumnLengthMismatch {
1918 col: col_idx,
1919 expected: n_rows,
1920 found: f64_arr.data.len(),
1921 });
1922 }
1923
1924 let start = col_idx * stride;
1925 let src = f64_arr.data.as_slice();
1926 let dst = &mut buf.as_mut_slice()[start..start + n_rows];
1927
1928 match f64_arr.null_mask.as_ref() {
1930 Some(mask) => {
1931 for i in 0..n_rows {
1932 dst[i] = if mask.get(i) { src[i] } else { f64::NAN };
1933 }
1934 }
1935 None => dst.copy_from_slice(src),
1936 }
1937 }
1938
1939 let name = if table.name.is_empty() { None } else { Some(table.name.clone()) };
1940 Ok(NdArray { data: Arc::new(Buffer::from_vec64(buf)), dims, name })
1941 }
1942}
1943
1944impl TryFrom<Table> for NdArray<f64> {
1945 type Error = MinarrowError;
1946 fn try_from(table: Table) -> Result<Self, Self::Error> {
1947 NdArray::try_from(&table)
1948 }
1949}
1950
1951impl<T: Float> fmt::Debug for NdArray<T> {
1954 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1955 write!(
1956 f, "NdArray{}: {:?} [{}D, col-major]",
1957 self.name.as_deref().map_or(String::new(), |n| format!(" '{}'", n)),
1958 self.dims.shape(),
1959 self.ndim(),
1960 )?;
1961 if self.ndim() == 0 {
1962 write!(f, "\n{:8.4}", self.get(&[]).to_f64().unwrap_or(f64::NAN))?;
1963 } else if self.ndim() == 2 {
1964 let shape = self.dims.shape();
1965 let max_rows = shape[0].min(6);
1966 let max_cols = shape[1].min(8);
1967 for r in 0..max_rows {
1968 write!(f, "\n[")?;
1969 for c in 0..max_cols {
1970 write!(f, " {:8.4}", self.get(&[r, c]).to_f64().unwrap_or(f64::NAN))?;
1971 if c < max_cols - 1 { write!(f, ",")?; }
1972 }
1973 if shape[1] > 8 { write!(f, " ...")?; }
1974 write!(f, " ]")?;
1975 }
1976 if shape[0] > 6 { write!(f, "\n...")?; }
1977 } else if self.ndim() == 1 {
1978 let n = self.dims.shape()[0].min(10);
1979 write!(f, "\n[")?;
1980 for i in 0..n {
1981 write!(f, " {:8.4}", self.get(&[i]).to_f64().unwrap_or(f64::NAN))?;
1982 if i < n - 1 { write!(f, ",")?; }
1983 }
1984 if self.dims.shape()[0] > 10 { write!(f, " ...")?; }
1985 write!(f, " ]")?;
1986 }
1987 Ok(())
1988 }
1989}
1990
1991impl<T: Float + Display> Display for NdArray<T> {
1992 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1993 let shape = self.shape();
1994 let elem = std::any::type_name::<T>();
1995 let dims = if shape.is_empty() {
1996 String::from("scalar")
1997 } else {
1998 shape.iter().map(|d| d.to_string()).collect::<Vec<_>>().join(" × ")
1999 };
2000 match &self.name {
2001 Some(name) => writeln!(f, "NdArray \"{}\" [{}, {}]", name, dims, elem)?,
2002 None => writeln!(f, "NdArray [{}, {}]", dims, elem)?,
2003 }
2004 print_ndarray_body(f, shape, |index| self.get(index))
2005 }
2006}
2007
2008impl TryFrom<NdArray<f64>> for Table {
2009 type Error = MinarrowError;
2010
2011 fn try_from(value: NdArray<f64>) -> Result<Self, Self::Error> {
2015 value.to_table(None)
2016 }
2017}
2018
2019impl TryFrom<NdArray<f64>> for Array {
2020 type Error = MinarrowError;
2021
2022 fn try_from(value: NdArray<f64>) -> Result<Self, Self::Error> {
2024 value.to_array()
2025 }
2026}
2027
2028#[cfg(feature = "matrix")]
2029impl TryFrom<NdArray<f64>> for Matrix {
2030 type Error = MinarrowError;
2031
2032 fn try_from(value: NdArray<f64>) -> Result<Self, Self::Error> {
2034 value.to_matrix()
2035 }
2036}
2037
2038#[cfg(test)]
2039mod tests {
2040 use super::*;
2041 use crate::StringArray;
2042 use crate::structs::bitmask::Bitmask;
2043
2044 #[cfg(all(feature = "views", feature = "select"))]
2047 #[test]
2048 fn axis_selection_rank10() {
2049 let shape = [3, 4, 2, 5, 3, 2, 4, 3, 2, 5];
2050 let len: usize = shape.iter().product();
2051 let data: Vec<f64> = (0..len).map(|i| i as f64).collect();
2052 let a = NdArray::from_slice(&data, &shape);
2053
2054 let v = a.s(nd![1..3, 0..2, 1, 2..5, .., 0..1, 1..3, 2, 0..2, 3..5]);
2056 assert_eq!(v.shape(), &[2, 2, 3, 3, 1, 2, 2, 2]);
2057 assert_eq!(
2058 v.get(&[0, 0, 0, 0, 0, 0, 0, 0]),
2059 a.get(&[1, 0, 1, 2, 0, 0, 1, 2, 0, 3])
2060 );
2061 assert_eq!(
2062 v.get(&[1, 1, 2, 2, 0, 1, 1, 1]),
2063 a.get(&[2, 1, 1, 4, 2, 0, 2, 2, 1, 4])
2064 );
2065 }
2066
2067 #[cfg(all(feature = "views", feature = "select"))]
2068 #[test]
2069 fn axis_selection_runtime_rank() {
2070 let mut shape = vec![1usize; 100];
2072 shape[0] = 3;
2073 shape[10] = 4;
2074 shape[50] = 5;
2075 shape[99] = 2;
2076 let len: usize = shape.iter().product();
2077 let data: Vec<f64> = (0..len).map(|i| i as f64).collect();
2078 let a = NdArray::from_slice(&data, &shape);
2079
2080 let mut sels: Vec<Box<dyn DataSelector>> =
2082 shape.iter().map(|&n| Box::new(0..n) as Box<dyn DataSelector>).collect();
2083 sels[0] = Box::new(1..3);
2084 sels[10] = Box::new(2usize);
2085 sels[50] = Box::new(1..4);
2086 let refs: Vec<&dyn DataSelector> = sels.iter().map(|s| s.as_ref()).collect();
2087
2088 let v = a.s(&refs);
2089 assert_eq!(v.ndim(), 99);
2090 assert_eq!(v.shape()[0], 2);
2091 assert_eq!(v.shape()[49], 3);
2092 assert_eq!(v.shape()[98], 2);
2093
2094 let mut view_idx = vec![0usize; 99];
2095 view_idx[0] = 1;
2096 view_idx[49] = 2;
2097 view_idx[98] = 1;
2098 let mut source_idx = vec![0usize; 100];
2099 source_idx[0] = 2;
2100 source_idx[10] = 2;
2101 source_idx[50] = 3;
2102 source_idx[99] = 1;
2103 assert_eq!(v.get(&view_idx), a.get(&source_idx));
2104 }
2105
2106 #[cfg(all(feature = "views", feature = "select"))]
2107 #[test]
2108 fn axis_selection_trait() {
2109 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2110 let v = a.s(nd![1..3, 1]);
2112 assert_eq!(v.shape(), &[2]);
2113 assert_eq!(v.get(&[0]), 5.0);
2114 assert_eq!(v.get(&[1]), 6.0);
2115 let sub = a.s(nd![0..3, 0..2]).s(nd![2, 0..2]);
2117 assert_eq!(sub.shape(), &[2]);
2118 assert_eq!(sub.get(&[0]), 3.0);
2119 assert_eq!(sub.get(&[1]), 6.0);
2120 assert_eq!(a.get_axis_count(), 2);
2121 assert_eq!(sub.get_axis_count(), 1);
2122 }
2123
2124 #[cfg(all(feature = "views", feature = "select"))]
2125 #[test]
2126 fn row_selection_contiguous() {
2127 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2128 let v = a.r(1..3);
2129 assert_eq!(v.shape(), &[2, 2]);
2130 assert_eq!(v.get(&[0, 0]), 2.0);
2131 assert_eq!(v.get(&[1, 1]), 6.0);
2132 let single = a.row(2);
2134 assert_eq!(single.shape(), &[1, 2]);
2135 assert_eq!(single.get(&[0, 1]), 6.0);
2136 }
2137
2138 #[cfg(all(feature = "views", feature = "select"))]
2139 #[test]
2140 fn row_selection_gathers_indices() {
2141 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2142 let v = a.r(&[2, 0]);
2143 assert_eq!(v.shape(), &[2, 2]);
2144 assert_eq!(v.get(&[0, 0]), 3.0);
2146 assert_eq!(v.get(&[0, 1]), 6.0);
2147 assert_eq!(v.get(&[1, 0]), 1.0);
2148 assert_eq!(v.get(&[1, 1]), 4.0);
2149 }
2150
2151 #[test]
2152 fn apply_maps_elements() {
2153 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
2154 a.set_name("m");
2155 let b = a.apply(|x| x * 10.0);
2156 assert_eq!(b.get(&[1, 1]), 40.0);
2157 assert_eq!(b.name.as_deref(), Some("m"));
2158 assert_eq!(a.get(&[1, 1]), 4.0);
2160 }
2161
2162 #[test]
2163 fn apply_mut_in_place() {
2164 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2165 a.apply_mut(|x| x + 0.5);
2166 assert_eq!((&a).into_iter().collect::<Vec<f64>>(), vec![1.5, 2.5, 3.5]);
2167 }
2168
2169 #[cfg(feature = "matrix")]
2170 #[test]
2171 fn apply_mut_non_contiguous_touches_logical_only() {
2172 let mat = Matrix::from_f64_unaligned(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 2, None);
2175 let mut a = NdArray::from(mat);
2176 assert!(!a.is_contiguous());
2177 a.apply_mut(|x| x * 2.0);
2178 assert_eq!(a.get(&[0, 0]), 2.0);
2179 assert_eq!(a.get(&[2, 1]), 12.0);
2180 }
2181
2182 #[cfg(feature = "views")]
2183 #[test]
2184 fn apply_axis_collapses_axis() {
2185 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2186 let col_sums = a.apply_axis(0, |lane| (&lane).into_iter().sum());
2188 assert_eq!(col_sums.shape(), &[2]);
2189 assert_eq!(col_sums.get(&[0]), 6.0);
2190 assert_eq!(col_sums.get(&[1]), 15.0);
2191 let row_sums = a.apply_axis(1, |lane| (&lane).into_iter().sum());
2193 assert_eq!(row_sums.shape(), &[3]);
2194 assert_eq!(row_sums.get(&[0]), 5.0);
2195 assert_eq!(row_sums.get(&[2]), 9.0);
2196 }
2197
2198 #[cfg(feature = "views")]
2199 #[test]
2200 fn apply_axis_3d() {
2201 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
2202 let a = NdArray::from_slice(&data, &[2, 3, 4]);
2203 let maxes = a.apply_axis(1, |lane| {
2204 (&lane).into_iter().fold(f64::MIN, f64::max)
2205 });
2206 assert_eq!(maxes.shape(), &[2, 4]);
2207 assert_eq!(maxes.get(&[0, 0]), a.get(&[0, 2, 0]));
2209 assert_eq!(maxes.get(&[1, 3]), a.get(&[1, 2, 3]));
2210 }
2211
2212 #[test]
2217 fn new_zeroed_1d() {
2218 let a = NdArray::<f64>::new(&[5]);
2219 assert_eq!(a.ndim(), 1);
2220 assert_eq!(a.shape(), &[5]);
2221 assert_eq!(a.len(), 5);
2222 assert!(!a.is_empty());
2223 for v in &a { assert_eq!(v, 0.0); }
2224 }
2225
2226 #[test]
2227 fn new_zeroed_2d() {
2228 let a = NdArray::<f64>::new(&[3, 4]);
2229 assert_eq!(a.ndim(), 2);
2230 assert_eq!(a.shape(), &[3, 4]);
2231 assert_eq!(a.len(), 12);
2232 for v in &a { assert_eq!(v, 0.0); }
2233 }
2234
2235 #[test]
2236 fn new_zeroed_3d() {
2237 let a = NdArray::<f64>::new(&[2, 3, 4]);
2238 assert_eq!(a.ndim(), 3);
2239 assert_eq!(a.shape(), &[2, 3, 4]);
2240 assert_eq!(a.len(), 24);
2241 }
2242
2243 #[test]
2244 fn new_zeroed_5d() {
2245 let a = NdArray::<f64>::new(&[2, 3, 4, 5, 6]);
2246 assert_eq!(a.ndim(), 5);
2247 assert_eq!(a.len(), 720);
2248 }
2249
2250 #[test]
2251 fn new_zeroed_6d_heap() {
2252 let a = NdArray::<f64>::new(&[2, 3, 2, 2, 2, 2]);
2253 assert_eq!(a.ndim(), 6);
2254 assert_eq!(a.len(), 96);
2255 }
2256
2257 #[test]
2258 fn new_named() {
2259 let a = NdArray::<f64>::new_named(&[3, 3], "covariance");
2260 assert_eq!(a.name.as_deref(), Some("covariance"));
2261 }
2262
2263 #[test]
2264 fn from_slice_1d() {
2265 let data = [1.0, 2.0, 3.0, 4.0, 5.0];
2266 let a = NdArray::from_slice(&data, &[5]);
2267 assert_eq!(a.len(), 5);
2268 let vals: Vec<f64> = (&a).into_iter().collect();
2269 assert_eq!(vals, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
2270 }
2271
2272 #[test]
2273 fn from_slice_2d_column_major() {
2274 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2275 let a = NdArray::from_slice(&data, &[3, 2]);
2276 assert_eq!(a.get(&[0, 0]), 1.0);
2277 assert_eq!(a.get(&[1, 0]), 2.0);
2278 assert_eq!(a.get(&[2, 0]), 3.0);
2279 assert_eq!(a.get(&[0, 1]), 4.0);
2280 assert_eq!(a.get(&[1, 1]), 5.0);
2281 assert_eq!(a.get(&[2, 1]), 6.0);
2282 }
2283
2284 #[test]
2285 fn fill_and_ones() {
2286 let a = NdArray::fill(&[2, 3], 7.0);
2287 assert_eq!(a.len(), 6);
2288 for v in &a { assert_eq!(v, 7.0); }
2289
2290 let b = NdArray::<f64>::ones(&[4]);
2291 for v in &b { assert_eq!(v, 1.0); }
2292 }
2293
2294 #[test]
2295 fn eye_identity() {
2296 let a = NdArray::<f64>::eye(3);
2297 assert_eq!(a.shape(), &[3, 3]);
2298 assert_eq!(a.get(&[0, 0]), 1.0);
2299 assert_eq!(a.get(&[1, 1]), 1.0);
2300 assert_eq!(a.get(&[2, 2]), 1.0);
2301 assert_eq!(a.get(&[0, 1]), 0.0);
2302 assert_eq!(a.get(&[1, 0]), 0.0);
2303 }
2304
2305 #[test]
2306 fn linspace_basic() {
2307 let a = NdArray::<f64>::linspace(0.0, 1.0, 5);
2308 assert_eq!(a.shape(), &[5]);
2309 assert_eq!(a.get(&[0]), 0.0);
2310 assert_eq!(a.get(&[4]), 1.0);
2311 assert!((a.get(&[2]) - 0.5).abs() < 1e-15);
2312 }
2313
2314 #[test]
2315 fn arange_basic() {
2316 let a = NdArray::arange(0.0, 0.5, 4);
2317 assert_eq!(a.shape(), &[4]);
2318 assert_eq!(a.get(&[0]), 0.0);
2319 assert_eq!(a.get(&[1]), 0.5);
2320 assert_eq!(a.get(&[2]), 1.0);
2321 assert_eq!(a.get(&[3]), 1.5);
2322 }
2323
2324 #[test]
2325 fn from_vec64_moves_data() {
2326 let data: Vec64<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0].into();
2327 let a = NdArray::from_vec64(data, &[3, 2]);
2328 assert_eq!(a.shape(), &[3, 2]);
2329 assert_eq!(a.get(&[2, 1]), 6.0);
2330 }
2331
2332 #[cfg(feature = "views")]
2333 #[test]
2334 fn iter_obs_walks_observations() {
2335 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2336 let rows: Vec<(usize, Vec<f64>)> = a
2337 .iter_obs()
2338 .map(|(i, v)| (i, (&v).into_iter().collect()))
2339 .collect();
2340 assert_eq!(rows.len(), 3);
2341 assert_eq!(rows[0], (0, vec![1.0, 4.0]));
2342 assert_eq!(rows[2], (2, vec![3.0, 6.0]));
2343 }
2344
2345 #[test]
2346 fn equality_ignores_name() {
2347 let a = NdArray::from_slice(&[1.0, 2.0], &[2]);
2348 let mut b = a.clone();
2349 b.name = Some("named".to_string());
2350 assert_eq!(a, b);
2351 }
2352
2353 #[test]
2354 fn name_survives_reshape_and_concat() {
2355 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
2356 a.name = Some("px".to_string());
2357 assert_eq!(a.reshape(&[2, 2]).unwrap().name.as_deref(), Some("px"));
2358
2359 let mut b = NdArray::from_slice(&[5.0, 6.0], &[2]);
2360 b.name = Some("qty".to_string());
2361 let joined = a.concat(b).unwrap();
2362 assert_eq!(joined.name.as_deref(), Some("px+qty"));
2363 }
2364
2365 #[test]
2370 fn get_set_1d() {
2371 let mut a = NdArray::new(&[3]);
2372 a.set(&[0], 10.0);
2373 a.set(&[1], 20.0);
2374 a.set(&[2], 30.0);
2375 assert_eq!(a.get(&[0]), 10.0);
2376 assert_eq!(a.get(&[1]), 20.0);
2377 assert_eq!(a.get(&[2]), 30.0);
2378 }
2379
2380 #[test]
2381 fn tuple_index_1d() {
2382 let a = NdArray::from_slice(&[10.0, 20.0, 30.0], &[3]);
2383 assert_eq!(a[(0,)], 10.0);
2384 assert_eq!(a[(1,)], 20.0);
2385 assert_eq!(a[(2,)], 30.0);
2386 }
2387
2388 #[test]
2389 fn tuple_index_2d() {
2390 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2391 let a = NdArray::from_slice(&data, &[3, 2]);
2392 assert_eq!(a[(0, 0)], 1.0);
2393 assert_eq!(a[(2, 0)], 3.0);
2394 assert_eq!(a[(0, 1)], 4.0);
2395 assert_eq!(a[(2, 1)], 6.0);
2396 }
2397
2398 #[test]
2399 fn tuple_index_3d() {
2400 let data: Vec<f64> = (1..=12).map(|x| x as f64).collect();
2401 let a = NdArray::from_slice(&data, &[2, 3, 2]);
2402 assert_eq!(a[(0, 0, 0)], 1.0);
2403 assert_eq!(a[(1, 0, 0)], 2.0);
2404 assert_eq!(a[(0, 1, 0)], 3.0);
2405 assert_eq!(a[(1, 1, 0)], 4.0);
2406 assert_eq!(a[(0, 2, 0)], 5.0);
2407 assert_eq!(a[(1, 2, 0)], 6.0);
2408 assert_eq!(a[(0, 0, 1)], 7.0);
2409 assert_eq!(a[(1, 2, 1)], 12.0);
2410 }
2411
2412 #[test]
2413 fn index_mut_2d() {
2414 let mut a = NdArray::new(&[2, 2]);
2415 a[(0, 0)] = 1.0;
2416 a[(1, 0)] = 2.0;
2417 a[(0, 1)] = 3.0;
2418 a[(1, 1)] = 4.0;
2419 assert_eq!(a[(0, 0)], 1.0);
2420 assert_eq!(a[(1, 1)], 4.0);
2421 }
2422
2423 #[test]
2428 fn iter_1d() {
2429 let a = NdArray::from_slice(&[10.0, 20.0, 30.0], &[3]);
2430 let vals: Vec<f64> = (&a).into_iter().collect();
2431 assert_eq!(vals, vec![10.0, 20.0, 30.0]);
2432 }
2433
2434 #[test]
2435 fn iter_2d_column_major_order() {
2436 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2437 let vals: Vec<f64> = (&a).into_iter().collect();
2438 assert_eq!(vals, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
2439 }
2440
2441 #[test]
2442 fn iter_2d_compact_layout() {
2443 let data: Vec<f64> = (1..=30).map(|x| x as f64).collect();
2446 let a = NdArray::from_slice(&data, &[10, 3]);
2447 assert_eq!(a.strides()[1], 10);
2448 let vals: Vec<f64> = (&a).into_iter().collect();
2449 assert_eq!(vals.len(), 30);
2450 assert_eq!(&vals[..10], &data[..10]);
2451 assert_eq!(&vals[10..20], &data[10..20]);
2452 assert_eq!(&vals[20..30], &data[20..30]);
2453 }
2454
2455 #[test]
2456 fn iter_3d_column_major_order() {
2457 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
2458 let a = NdArray::from_slice(&data, &[2, 3, 4]);
2459 let vals: Vec<f64> = (&a).into_iter().collect();
2460 assert_eq!(vals.len(), 24);
2461 assert_eq!(vals, data);
2462 }
2463
2464 #[test]
2465 fn iter_exact_size() {
2466 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2467 let iter = (&a).into_iter();
2468 assert_eq!(iter.len(), 6);
2469 }
2470
2471 #[test]
2472 fn consuming_into_iter() {
2473 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2474 let vals: Vec<f64> = a.into_iter().collect();
2475 assert_eq!(vals, vec![1.0, 2.0, 3.0]);
2476 }
2477
2478 #[test]
2483 fn rank_zero_scalar_semantics() {
2484 let mut a = NdArray::from_slice(&[5.0], &[]);
2485 assert_eq!(a.ndim(), 0);
2486 assert!(a.shape().is_empty());
2487 assert!(a.strides().is_empty());
2488 assert_eq!(a.len(), 1);
2489 assert!(!a.is_empty());
2490 assert!(a.is_contiguous());
2491 assert_eq!(a[()], 5.0);
2492 assert_eq!((&a).into_iter().collect::<Vec<_>>(), vec![5.0]);
2493 assert_eq!(Shape::shape(&a), ShapeDim::Rank0(1));
2494
2495 a[()] = 7.0;
2496 assert_eq!(a[()], 7.0);
2497 assert!(a.transpose().shape().is_empty());
2498 assert_eq!(a.reshape(&[1]).unwrap().get(&[0]), 7.0);
2499 }
2500
2501 #[cfg(all(feature = "views", feature = "select"))]
2502 #[test]
2503 fn selecting_every_axis_can_produce_a_scalar_view() {
2504 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
2505 let scalar = a.slice(&[&1usize, &1usize]);
2506 assert!(scalar.shape().is_empty());
2507 assert!(scalar.strides().is_empty());
2508 assert_eq!(scalar.len(), 1);
2509 assert_eq!(scalar.get(&[]), 4.0);
2510 assert_eq!((&scalar).into_iter().collect::<Vec<_>>(), vec![4.0]);
2511 assert_eq!(Shape::shape(&scalar), ShapeDim::Rank0(1));
2512 }
2513
2514 #[test]
2515 fn is_contiguous_default() {
2516 let a = NdArray::<f64>::new(&[3, 4]);
2517 assert!(a.is_contiguous());
2518 }
2519
2520 #[test]
2521 fn shape_trait_1d() {
2522 let a = NdArray::<f64>::new(&[5]);
2523 assert_eq!(Shape::shape(&a), ShapeDim::Rank1(5));
2524 }
2525
2526 #[test]
2527 fn shape_trait_2d() {
2528 let a = NdArray::<f64>::new(&[3, 4]);
2529 assert_eq!(Shape::shape(&a), ShapeDim::Rank2 { rows: 3, cols: 4 });
2530 }
2531
2532 #[test]
2533 fn shape_trait_3d() {
2534 let a = NdArray::<f64>::new(&[2, 3, 4]);
2535 assert_eq!(Shape::shape(&a), ShapeDim::RankN(vec![2, 3, 4]));
2536 }
2537
2538 #[test]
2543 fn has_nan_false() {
2544 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2545 assert!(!a.has_nan());
2546 assert_eq!(a.nan_count(), 0);
2547 }
2548
2549 #[test]
2550 fn has_nan_true() {
2551 let a = NdArray::from_slice(&[1.0, f64::NAN, 3.0], &[3]);
2552 assert!(a.has_nan());
2553 assert_eq!(a.nan_count(), 1);
2554 }
2555
2556 #[test]
2557 fn has_nan_2d() {
2558 let a = NdArray::from_slice(&[1.0, 2.0, f64::NAN, 4.0, 5.0, f64::NAN], &[3, 2]);
2559 assert!(a.has_nan());
2560 assert_eq!(a.nan_count(), 2);
2561 }
2562
2563 #[test]
2568 fn col_access() {
2569 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2570 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2571 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2572 }
2573
2574 #[test]
2575 fn col_mut_access() {
2576 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2577 a.col_mut(0)[1] = 99.0;
2578 assert_eq!(a.col(0), &[1.0, 99.0, 3.0]);
2579 }
2580
2581 #[test]
2582 fn columns_access() {
2583 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2584 let cols = a.columns();
2585 assert_eq!(cols.len(), 2);
2586 assert_eq!(cols[0], &[1.0, 2.0, 3.0]);
2587 assert_eq!(cols[1], &[4.0, 5.0, 6.0]);
2588 }
2589
2590 #[test]
2591 fn columns_mut_access() {
2592 let mut a = NdArray::new(&[3, 2]);
2593 {
2594 let mut cols = a.columns_mut();
2595 cols[0].copy_from_slice(&[1.0, 2.0, 3.0]);
2596 cols[1].copy_from_slice(&[4.0, 5.0, 6.0]);
2597 }
2598 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2599 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2600 }
2601
2602 #[cfg(feature = "views")]
2603 #[test]
2604 fn obs_access() {
2605 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2606 let v = a.as_view();
2607 let obs0: Vec<f64> = (&v.obs(0)).into_iter().collect();
2608 let obs1: Vec<f64> = (&v.obs(1)).into_iter().collect();
2609 let obs2: Vec<f64> = (&v.obs(2)).into_iter().collect();
2610 assert_eq!(obs0, vec![1.0, 4.0]);
2611 assert_eq!(obs1, vec![2.0, 5.0]);
2612 assert_eq!(obs2, vec![3.0, 6.0]);
2613
2614 assert_eq!((&a.obs(1)).into_iter().collect::<Vec<f64>>(), vec![2.0, 5.0]);
2616
2617 let b = NdArray::from_slice(&(1..=24).map(|x| x as f64).collect::<Vec<_>>(), &[2, 3, 4]);
2619 let obs0_3d = b.obs(0);
2620 assert_eq!(obs0_3d.shape(), &[3, 4]);
2621 }
2622
2623 #[test]
2624 fn col_access_2d() {
2625 let data: Vec<f64> = (1..=20).map(|x| x as f64).collect();
2626 let a = NdArray::from_slice(&data, &[10, 2]);
2627 assert_eq!(a.col(0).len(), 10);
2628 assert_eq!(a.col(1).len(), 10);
2629 assert_eq!(a.col(0), &data[..10]);
2630 assert_eq!(a.col(1), &data[10..20]);
2631 }
2632
2633 #[test]
2638 fn blas_params() {
2639 let a = NdArray::<f64>::new(&[10, 5]);
2640 assert_eq!(a.m(), 10);
2641 assert_eq!(a.n(), 5);
2642 assert_eq!(a.lda(), 10);
2643 }
2644
2645 #[test]
2646 fn blas_params_aligned_rows() {
2647 let a = NdArray::<f64>::new(&[8, 3]);
2648 assert_eq!(a.m(), 8);
2649 assert_eq!(a.n(), 3);
2650 assert_eq!(a.lda(), 8);
2651 }
2652
2653 #[test]
2658 fn compact_strides_2d() {
2659 for n_rows in 1..=20 {
2660 let a = NdArray::<f64>::new(&[n_rows, 3]);
2661 assert_eq!(a.strides()[1], n_rows);
2662 assert!(a.is_contiguous());
2663 }
2664 }
2665
2666 #[test]
2667 fn compact_strides_3d() {
2668 let a = NdArray::<f64>::new(&[10, 3, 4]);
2669 let strides = a.strides();
2670 assert_eq!(strides[0], 1);
2671 assert_eq!(strides[1], 10);
2672 assert_eq!(strides[2], 10 * 3);
2673 assert!(a.is_contiguous());
2674 }
2675
2676 #[test]
2681 fn reshape_1d_to_2d() {
2682 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[6]);
2683 let b = a.reshape(&[3, 2]).unwrap();
2684 assert_eq!(b.shape(), &[3, 2]);
2685 assert_eq!(b.get(&[0, 0]), 1.0);
2686 assert_eq!(b.get(&[1, 0]), 2.0);
2687 assert_eq!(b.get(&[2, 0]), 3.0);
2688 assert_eq!(b.get(&[0, 1]), 4.0);
2689 }
2690
2691 #[test]
2692 fn reshape_size_mismatch() {
2693 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2694 assert!(a.reshape(&[2, 2]).is_err());
2695 }
2696
2697 #[test]
2698 fn transpose_2d() {
2699 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2700 let t = a.transpose();
2701 assert_eq!(t.shape(), &[2, 3]);
2702 assert_eq!(t.get(&[0, 0]), 1.0);
2703 assert_eq!(t.get(&[1, 0]), 4.0);
2704 assert_eq!(t.get(&[0, 1]), 2.0);
2705 assert_eq!(t.get(&[1, 1]), 5.0);
2706 assert_eq!(t.get(&[0, 2]), 3.0);
2707 assert_eq!(t.get(&[1, 2]), 6.0);
2708 }
2709
2710 #[test]
2711 fn transpose_3d_reverses_axes() {
2712 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
2713 let a = NdArray::from_slice(&data, &[2, 3, 4]);
2714 let t = a.transpose();
2715 assert_eq!(t.shape(), &[4, 3, 2]);
2716 assert!(t.is_contiguous());
2717 for i in 0..2 {
2719 for j in 0..3 {
2720 for k in 0..4 {
2721 assert_eq!(t.get(&[k, j, i]), a.get(&[i, j, k]));
2722 }
2723 }
2724 }
2725 }
2726
2727 #[test]
2728 fn transpose_1d_copies_through() {
2729 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2730 let t = a.transpose();
2731 assert_eq!(t.shape(), &[3]);
2732 assert_eq!(t, a);
2733 }
2734
2735 #[test]
2736 fn flatten() {
2737 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
2738 let flat = a.flatten();
2739 assert_eq!(flat.shape(), &[6]);
2740 let vals: Vec<f64> = (&flat).into_iter().collect();
2741 assert_eq!(vals, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
2742 }
2743
2744 #[test]
2745 fn to_contiguous_noop() {
2746 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2747 let b = a.to_contiguous();
2748 assert_eq!(a, b);
2749 }
2750
2751 #[test]
2752 fn fill_with_contiguous() {
2753 let mut a = NdArray::new(&[3, 2]);
2754 a.fill_with(42.0);
2755 for v in &a { assert_eq!(v, 42.0); }
2756 }
2757
2758 #[test]
2763 fn from_f64_slice() {
2764 let a = NdArray::from(&[1.0, 2.0, 3.0][..]);
2765 assert_eq!(a.ndim(), 1);
2766 assert_eq!(a.len(), 3);
2767 assert_eq!(a[(0,)], 1.0);
2768 }
2769
2770 #[test]
2771 fn from_vec64() {
2772 let v: Vec64<f64> = vec![10.0, 20.0].into_iter().collect();
2773 let a = NdArray::from(v);
2774 assert_eq!(a.ndim(), 1);
2775 assert_eq!(a[(0,)], 10.0);
2776 assert_eq!(a[(1,)], 20.0);
2777 }
2778
2779 #[test]
2780 fn from_column_vecs() {
2781 let cols = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
2782 let a = NdArray::from(cols.as_slice());
2783 assert_eq!(a.shape(), &[3, 2]);
2784 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2785 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2786 }
2787
2788 #[test]
2789 fn from_float_arrays() {
2790 let c0 = FloatArray::from_slice(&[1.0, 2.0]);
2791 let c1 = FloatArray::from_slice(&[3.0, 4.0]);
2792 let a = NdArray::from([c0, c1].as_slice());
2793 assert_eq!(a.shape(), &[2, 2]);
2794 assert_eq!(a.col(0), &[1.0, 2.0]);
2795 assert_eq!(a.col(1), &[3.0, 4.0]);
2796 }
2797
2798 #[test]
2799 fn from_buffer_explicit_strides() {
2800 let mut buf = Vec64::with_capacity(16);
2801 buf.0.resize(16, 0.0);
2802 buf[0] = 1.0; buf[1] = 2.0; buf[2] = 3.0;
2803 buf[8] = 4.0; buf[9] = 5.0; buf[10] = 6.0;
2804 let a = NdArray::from_buffer(Buffer::from_vec64(buf), &[3, 2], &[1, 8]);
2805 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2806 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2807 }
2808
2809 #[cfg(feature = "matrix")]
2814 #[test]
2815 fn from_matrix() {
2816 let mat = Matrix::from_f64_unaligned(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 2, Some("m".into()));
2817 let a = NdArray::from(mat);
2818 assert_eq!(a.shape(), &[3, 2]);
2819 assert_eq!(a.name.as_deref(), Some("m"));
2820 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2821 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2822 }
2823
2824 #[cfg(feature = "matrix")]
2825 #[test]
2826 fn to_matrix_roundtrip() {
2827 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2828 let a = NdArray::from_slice(&data, &[3, 2]);
2829 let mat = a.to_matrix().unwrap();
2830 assert_eq!(mat.n_rows, 3);
2831 assert_eq!(mat.n_cols, 2);
2832 assert_eq!(mat.col(0), &[1.0, 2.0, 3.0]);
2833 assert_eq!(mat.col(1), &[4.0, 5.0, 6.0]);
2834 }
2835
2836 #[cfg(feature = "matrix")]
2837 #[test]
2838 fn to_matrix_non_2d_fails() {
2839 let a = NdArray::new(&[5]);
2840 assert!(a.to_matrix().is_err());
2841 }
2842
2843 fn make_numeric_table() -> Table {
2848 let c0 = FieldArray::from_arr("x", Array::NumericArray(
2849 NumericArray::Float64(Arc::new(FloatArray::from_slice(&[1.0, 2.0, 3.0])))
2850 ));
2851 let c1 = FieldArray::from_arr("y", Array::NumericArray(
2852 NumericArray::Float64(Arc::new(FloatArray::from_slice(&[4.0, 5.0, 6.0])))
2853 ));
2854 Table::new("data".to_string(), Some(vec![c0, c1]))
2855 }
2856
2857 #[test]
2858 fn try_from_table() {
2859 let table = make_numeric_table();
2860 let a = NdArray::try_from(&table).unwrap();
2861 assert_eq!(a.shape(), &[3, 2]);
2862 assert_eq!(a.col(0), &[1.0, 2.0, 3.0]);
2863 assert_eq!(a.col(1), &[4.0, 5.0, 6.0]);
2864 assert_eq!(a.name.as_deref(), Some("data"));
2865 }
2866
2867 #[test]
2868 fn try_from_table_with_nulls_converts_to_nan() {
2869 let mut mask = Bitmask::new_set_all(3, true);
2870 mask.set(1, false);
2871 let arr = FloatArray::new(Buffer::from_slice(&[10.0, 0.0, 30.0]), Some(mask));
2872 let c0 = FieldArray::from_arr("v", Array::NumericArray(
2873 NumericArray::Float64(Arc::new(arr))
2874 ));
2875 let table = Table::new("nulls".to_string(), Some(vec![c0]));
2876 let a = NdArray::try_from(&table).unwrap();
2877 assert_eq!(a.get(&[0, 0]), 10.0);
2878 assert!(a.get(&[1, 0]).is_nan());
2879 assert_eq!(a.get(&[2, 0]), 30.0);
2880 }
2881
2882 #[test]
2883 fn try_from_table_coerces_unparseable_text_to_nan() {
2884 let c0 = FieldArray::from_arr("name", Array::from_string32(
2888 StringArray::from_slice(&["a", "b"])
2889 ));
2890 let table = Table::new("text".to_string(), Some(vec![c0]));
2891 let a = NdArray::try_from(&table).unwrap();
2892 assert_eq!(a.shape(), &[2, 1]);
2893 assert!(a.get(&[0, 0]).is_nan());
2894 assert!(a.get(&[1, 0]).is_nan());
2895 }
2896
2897 #[test]
2898 fn to_table_roundtrip() {
2899 let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
2900 let a = NdArray::from_slice(&data, &[3, 2]);
2901 let fields = vec![
2902 Field::new("x", ArrowType::Float64, false, None),
2903 Field::new("y", ArrowType::Float64, false, None),
2904 ];
2905 let table = a.to_table(Some(fields)).unwrap();
2906 assert_eq!(table.n_rows(), 3);
2907 assert_eq!(table.n_cols(), 2);
2908 assert_eq!(table.col_names(), vec!["x", "y"]);
2909 let col0 = table.cols[0].array.num().f64();
2910 assert_eq!(col0.data.as_slice(), &[1.0, 2.0, 3.0]);
2911 }
2912
2913 #[test]
2914 fn to_table_generated_names() {
2915 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
2916 let table = a.to_table(None).unwrap();
2917 assert_eq!(table.col_names(), vec!["col_0", "col_1"]);
2918 }
2919
2920 #[test]
2921 fn to_table_rejects_incompatible_field_metadata() {
2922 let wrong_dtype = NdArray::from_slice(&[1.0, 2.0], &[2, 1]).to_table(Some(vec![
2923 Field::new("value", ArrowType::Float32, false, None),
2924 ]));
2925 assert!(matches!(wrong_dtype, Err(MinarrowError::TypeError { .. })));
2926
2927 let nullable = NdArray::from_slice(&[1.0, 2.0], &[2, 1]).to_table(Some(vec![
2928 Field::new("value", ArrowType::Float64, true, None),
2929 ]));
2930 assert!(matches!(nullable, Err(MinarrowError::NullError { .. })));
2931 }
2932
2933 #[test]
2934 fn to_table_non_2d_fails() {
2935 let a = NdArray::new(&[5]);
2936 assert!(a.to_table(None).is_err());
2937 }
2938
2939 #[test]
2940 fn to_array_1d() {
2941 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
2942 let arr = a.to_array().unwrap();
2943 let f = arr.num().f64();
2944 assert_eq!(f.data.as_slice(), &[1.0, 2.0, 3.0]);
2945 }
2946
2947 #[test]
2948 fn to_array_materialises_strided_logical_values() {
2949 let a = NdArray::from_buffer(
2950 Buffer::from_slice(&[1.0, 99.0, 2.0, 99.0, 3.0]),
2951 &[3],
2952 &[2],
2953 );
2954 let arr = a.to_array().unwrap();
2955 assert_eq!(arr.num().f64().data.as_slice(), &[1.0, 2.0, 3.0]);
2956 }
2957
2958 #[test]
2959 fn to_array_ignores_unused_backing_elements() {
2960 let a = NdArray::from_buffer(
2961 Buffer::from_slice(&[1.0, 2.0, 3.0, 99.0]),
2962 &[3],
2963 &[1],
2964 );
2965 let arr = a.to_array().unwrap();
2966 assert_eq!(arr.num().f64().data.as_slice(), &[1.0, 2.0, 3.0]);
2967 }
2968
2969 #[test]
2970 fn to_array_non_1d_fails() {
2971 let a = NdArray::new(&[2, 3]);
2972 assert!(a.to_array().is_err());
2973 }
2974
2975 #[test]
2980 fn concat_1d() {
2981 let a = NdArray::from_slice(&[1.0, 2.0], &[2]);
2982 let b = NdArray::from_slice(&[3.0, 4.0, 5.0], &[3]);
2983 let c = a.concat(b).unwrap();
2984 assert_eq!(c.shape(), &[5]);
2985 let vals: Vec<f64> = (&c).into_iter().collect();
2986 assert_eq!(vals, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
2987 }
2988
2989 #[test]
2990 fn concat_2d() {
2991 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
2992 let b = NdArray::from_slice(&[5.0, 6.0, 7.0, 8.0, 9.0, 10.0], &[3, 2]);
2993 let c = a.concat(b).unwrap();
2994 assert_eq!(c.shape(), &[5, 2]);
2995 assert_eq!(c.col(0), &[1.0, 2.0, 5.0, 6.0, 7.0]);
2996 assert_eq!(c.col(1), &[3.0, 4.0, 8.0, 9.0, 10.0]);
2997 }
2998
2999 #[test]
3000 fn concat_dimension_mismatch_fails() {
3001 let a = NdArray::<f64>::new(&[3, 2]);
3002 let b = NdArray::new(&[3, 3]);
3003 assert!(a.concat(b).is_err());
3004 }
3005
3006 #[test]
3007 fn concat_rank_mismatch_fails() {
3008 let a = NdArray::<f64>::new(&[3]);
3009 let b = NdArray::new(&[3, 2]);
3010 assert!(a.concat(b).is_err());
3011 }
3012
3013 #[test]
3018 fn clone_and_eq() {
3019 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3020 let b = a.clone();
3021 assert_eq!(a, b);
3022 }
3023
3024 #[test]
3025 fn ne_different_data() {
3026 let a = NdArray::from_slice(&[1.0, 2.0], &[2]);
3027 let b = NdArray::from_slice(&[1.0, 3.0], &[2]);
3028 assert_ne!(a, b);
3029 }
3030
3031 #[test]
3032 fn ne_different_shape() {
3033 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[4]);
3034 let b = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3035 assert_ne!(a, b);
3036 }
3037
3038 #[test]
3043 fn debug_1d() {
3044 let a = NdArray::from_slice(&[1.0, 2.0, 3.0], &[3]);
3045 let s = format!("{:?}", a);
3046 assert!(s.contains("[3]"));
3047 assert!(s.contains("1D"));
3048 }
3049
3050 #[test]
3051 fn debug_2d_named() {
3052 let a = NdArray::<f64>::new_named(&[2, 3], "test");
3053 let s = format!("{:?}", a);
3054 assert!(s.contains("'test'"));
3055 assert!(s.contains("[2, 3]"));
3056 assert!(s.contains("2D"));
3057 }
3058
3059 #[test]
3064 fn empty_array() {
3065 let a = NdArray::new(&[0, 5]);
3066 assert!(a.is_empty());
3067 assert_eq!(a.len(), 0);
3068 let vals: Vec<f64> = (&a).into_iter().collect();
3069 assert!(vals.is_empty());
3070 }
3071
3072 #[test]
3073 fn single_element() {
3074 let a = NdArray::from_slice(&[42.0], &[1]);
3075 assert_eq!(a.len(), 1);
3076 assert_eq!(a[(0,)], 42.0);
3077 let vals: Vec<f64> = (&a).into_iter().collect();
3078 assert_eq!(vals, vec![42.0]);
3079 }
3080
3081 #[test]
3082 fn single_element_2d() {
3083 let a = NdArray::from_slice(&[42.0], &[1, 1]);
3084 assert_eq!(a[(0, 0)], 42.0);
3085 }
3086
3087 #[test]
3088 fn large_array_iteration_count() {
3089 let n = 1000;
3090 let a = NdArray::<f64>::ones(&[n, 100]);
3091 let count = (&a).into_iter().count();
3092 assert_eq!(count, n * 100);
3093 }
3094
3095 #[test]
3100 fn bracket_index_1d() {
3101 let a = NdArray::from_slice(&[10.0, 20.0, 30.0], &[3]);
3102 assert_eq!(a[0], [10.0]);
3103 assert_eq!(a[2], [30.0]);
3104 }
3105
3106 #[test]
3107 fn bracket_index_2d_column() {
3108 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3109 assert_eq!(a[0], [1.0, 2.0, 3.0]);
3110 assert_eq!(a[1], [4.0, 5.0, 6.0]);
3111 }
3112
3113 #[test]
3114 fn bracket_index_2d_chained() {
3115 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3116 assert_eq!(a[0][0], 1.0);
3118 assert_eq!(a[0][2], 3.0);
3119 assert_eq!(a[1][0], 4.0);
3120 assert_eq!(a[1][2], 6.0);
3121 }
3122
3123 #[test]
3124 fn bracket_index_mut_2d() {
3125 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3126 a[0][1] = 99.0;
3127 assert_eq!(a[0][1], 99.0);
3128 assert_eq!(a[0], [1.0, 99.0, 3.0]);
3129 }
3130
3131 #[test]
3136 fn range_index_1d() {
3137 let a = NdArray::from_slice(&[10.0, 20.0, 30.0, 40.0], &[4]);
3138 assert_eq!(a[1..3], [20.0, 30.0]);
3139 }
3140
3141 #[test]
3142 fn range_index_2d_columns() {
3143 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3146 let slab = &a[0..2];
3147 assert_eq!(slab, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3148 let col1 = &a[1..2];
3149 assert_eq!(col1, &[4.0, 5.0, 6.0]);
3150 }
3151
3152 #[cfg(feature = "matrix")]
3153 #[test]
3154 #[should_panic(expected = "contiguous")]
3155 fn range_index_non_contiguous_panics() {
3156 let mat = Matrix::from_f64_unaligned(
3159 &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 2, None,
3160 );
3161 let a = NdArray::from(mat);
3162 if a.is_contiguous() {
3163 panic!("premise failed: expected non-contiguous import");
3166 }
3167 let _ = &a[0..2];
3168 }
3169
3170 #[cfg(feature = "matrix")]
3171 #[test]
3172 fn to_matrix_repacks_compact_layout() {
3173 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3175 let mat = a.to_matrix().unwrap();
3176 assert_eq!(mat.n_rows, 3);
3177 assert_eq!(mat.n_cols, 2);
3178 assert_eq!(mat.stride, 8);
3179 assert_eq!(&mat.data.as_slice()[..3], &[1.0, 2.0, 3.0]);
3180 assert_eq!(&mat.data.as_slice()[8..11], &[4.0, 5.0, 6.0]);
3181 }
3182
3183 #[cfg(feature = "matrix")]
3184 #[test]
3185 fn matrix_roundtrip_via_contiguous() {
3186 let mat = Matrix::from_f64_unaligned(
3189 &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 2, None,
3190 );
3191 let a = NdArray::from(mat);
3192 assert!(!a.is_contiguous());
3193 assert_eq!(a.get(&[2, 1]), 6.0);
3194 let compact = a.to_contiguous();
3195 assert!(compact.is_contiguous());
3196 assert_eq!(&compact[0..2], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3197 let back = compact.to_matrix().unwrap();
3198 assert_eq!(back.stride, 8);
3199 assert_eq!(&back.data.as_slice()[8..11], &[4.0, 5.0, 6.0]);
3200 }
3201
3202 #[test]
3203 fn range_from_index() {
3204 let a = NdArray::from_slice(&[10.0, 20.0, 30.0, 40.0], &[4]);
3205 assert_eq!(a[2..], [30.0, 40.0]);
3206 }
3207
3208 #[test]
3209 fn range_to_index() {
3210 let a = NdArray::from_slice(&[10.0, 20.0, 30.0, 40.0], &[4]);
3211 assert_eq!(a[..2], [10.0, 20.0]);
3212 }
3213
3214 #[test]
3215 fn range_full_index() {
3216 let a = NdArray::from_slice(&[10.0, 20.0, 30.0], &[3]);
3217 assert_eq!(a[..], [10.0, 20.0, 30.0]);
3218 }
3219
3220 #[cfg(feature = "views")]
3225 #[test]
3226 fn slice_1d_single_index() {
3227 let a = NdArray::from_slice(&[10.0, 20.0, 30.0], &[3]);
3228 let v = a.slice(&[&1]);
3229 assert!(v.shape().is_empty());
3230 assert_eq!(v[()], 20.0);
3231 }
3232
3233 #[cfg(feature = "views")]
3234 #[test]
3235 fn slice_1d_range() {
3236 let a = NdArray::from_slice(&[10.0, 20.0, 30.0, 40.0], &[4]);
3237 let v = a.slice(&[&(1..3)]);
3238 assert_eq!(v.shape(), &[2]);
3239 assert_eq!(v[(0,)], 20.0);
3240 assert_eq!(v[(1,)], 30.0);
3241 }
3242
3243 #[cfg(feature = "views")]
3244 #[test]
3245 fn slice_2d_row_range_single_col() {
3246 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3247 let v = a.slice(nd![0..2, 1]);
3249 assert_eq!(v.shape(), &[2]);
3250 assert_eq!(v[(0,)], 4.0);
3251 assert_eq!(v[(1,)], 5.0);
3252 }
3253
3254 #[cfg(feature = "views")]
3255 #[test]
3256 fn slice_2d_single_row_col_range() {
3257 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3258 let v = a.slice(nd![1, 0..2]);
3260 assert_eq!(v.shape(), &[2]);
3261 let vals: Vec<f64> = (&v).into_iter().collect();
3263 assert_eq!(vals, vec![2.0, 5.0]);
3264 }
3265
3266 #[cfg(feature = "views")]
3267 #[test]
3268 fn slice_2d_both_ranges() {
3269 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3270 let v = a.slice(nd![0..2, 0..2]);
3272 assert_eq!(v.shape(), &[2, 2]);
3273 assert_eq!(v[(0, 0)], 1.0);
3274 assert_eq!(v[(1, 0)], 2.0);
3275 assert_eq!(v[(0, 1)], 4.0);
3276 assert_eq!(v[(1, 1)], 5.0);
3277 }
3278
3279 #[cfg(feature = "views")]
3280 #[test]
3281 fn slice_2d_both_indices_scalar() {
3282 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3283 let v = a.slice(nd![2, 1]);
3285 assert!(v.shape().is_empty());
3286 assert_eq!(v[()], 6.0);
3287 }
3288
3289 #[cfg(feature = "views")]
3290 #[test]
3291 fn slice_3d_mixed() {
3292 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
3294 let a = NdArray::from_slice(&data, &[2, 3, 4]);
3295 let v = a.slice(nd![0..2, 1, 0..2]);
3297 assert_eq!(v.shape(), &[2, 2]);
3298 assert_eq!(v[(0, 0)], 3.0);
3300 assert_eq!(v[(1, 0)], 4.0);
3301 assert_eq!(v[(0, 1)], 9.0);
3302 assert_eq!(v[(1, 1)], 10.0);
3303 }
3304
3305 #[cfg(feature = "views")]
3306 #[test]
3307 fn slice_with_nd_macro() {
3308 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
3309 let a = NdArray::from_slice(&data, &[2, 3, 4]);
3310 let v = a.slice(nd![0..2, 0..3, 0..4]);
3311 assert_eq!(v.shape(), &[2, 3, 4]);
3312 assert_eq!(v.len(), 24);
3313 }
3314
3315 #[cfg(feature = "views")]
3316 #[test]
3317 fn slice_preserves_data_through_iteration() {
3318 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3319 let v = a.slice(nd![1..3, 0..2]);
3320 let vals: Vec<f64> = (&v).into_iter().collect();
3322 assert_eq!(vals, vec![2.0, 3.0, 5.0, 6.0]);
3323 }
3324
3325 #[cfg(feature = "views")]
3326 #[test]
3327 fn slice_column_window() {
3328 let data: Vec<f64> = (1..=20).map(|x| x as f64).collect();
3330 let a = NdArray::from_slice(&data, &[10, 2]);
3331 let v = a.slice(nd![2..5, 1]);
3332 assert_eq!(v.shape(), &[3]);
3333 assert_eq!(v[(0,)], 13.0);
3334 assert_eq!(v[(1,)], 14.0);
3335 assert_eq!(v[(2,)], 15.0);
3336 }
3337
3338 #[test]
3343 #[should_panic(expected = "Column index out of bounds")]
3344 fn col_out_of_bounds_panics() {
3345 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3346 let _ = a.col(2);
3347 }
3348
3349 #[test]
3350 #[should_panic(expected = "indices for a 2D array")]
3351 fn get_rank_mismatch_panics() {
3352 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3353 let _ = a.get(&[0]);
3354 }
3355
3356 #[test]
3357 #[should_panic(expected = "out of bounds for dim")]
3358 fn get_index_out_of_bounds_panics() {
3359 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3360 let _ = a.get(&[2, 0]);
3361 }
3362
3363 #[test]
3364 #[should_panic(expected = "out of bounds for dim")]
3365 fn set_out_of_bounds_panics() {
3366 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3367 a.set(&[0, 5], 9.0);
3368 }
3369
3370 #[cfg(all(feature = "views", feature = "select"))]
3371 #[test]
3372 #[should_panic(expected = "expected 2 axes, got 1")]
3373 fn slice_wrong_axis_count_panics() {
3374 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3375 let _ = a.slice(nd![0..2]);
3376 }
3377
3378 #[cfg(all(feature = "views", feature = "select"))]
3379 #[test]
3380 #[should_panic(expected = "range 0..100 out of bounds")]
3381 fn slice_range_out_of_bounds_panics() {
3382 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3383 let _ = a.slice(nd![0..100, 0..2]);
3384 }
3385
3386 #[cfg(all(feature = "views", feature = "select"))]
3387 #[test]
3388 #[should_panic(expected = "index 5 out of bounds")]
3389 fn slice_single_index_out_of_bounds_panics() {
3390 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3391 let _ = a.slice(nd![5, 0..2]);
3392 }
3393
3394 #[test]
3395 #[should_panic(expected = "at least 2 points")]
3396 fn linspace_requires_two_points() {
3397 let _ = NdArray::<f64>::linspace(0.0, 1.0, 1);
3398 }
3399
3400 #[cfg(all(feature = "views", feature = "select"))]
3401 #[test]
3402 fn slice_one_element_index_array_collapses() {
3403 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3404 let idx: &[usize] = &[1];
3405 let v = a.slice(nd![idx, 0..2]);
3406 assert_eq!(v.shape(), &[2]);
3407 assert_eq!(v[(0,)], 2.0);
3408 assert_eq!(v[(1,)], 5.0);
3409 }
3410
3411 #[test]
3412 fn get_unchecked_matches_get() {
3413 let data: Vec<f64> = (1..=24).map(|x| x as f64).collect();
3414 let a = NdArray::from_slice(&data, &[2, 3, 4]);
3415 for i in 0..2 {
3416 for j in 0..3 {
3417 for k in 0..4 {
3418 let idx = [i, j, k];
3419 assert_eq!(unsafe { a.get_unchecked(&idx) }, a.get(&idx));
3421 }
3422 }
3423 }
3424 }
3425
3426 #[test]
3431 fn concat_3d_interleaves_axis0() {
3432 let da: Vec<f64> = (1..=8).map(|x| x as f64).collect();
3433 let db: Vec<f64> = (9..=16).map(|x| x as f64).collect();
3434 let a = NdArray::from_slice(&da, &[2, 2, 2]);
3435 let b = NdArray::from_slice(&db, &[2, 2, 2]);
3436 let c = a.clone().concat(b.clone()).unwrap();
3437 assert_eq!(c.shape(), &[4, 2, 2]);
3438 for i in 0..4 {
3439 for j in 0..2 {
3440 for k in 0..2 {
3441 let expected = if i < 2 {
3442 a.get(&[i, j, k])
3443 } else {
3444 b.get(&[i - 2, j, k])
3445 };
3446 assert_eq!(c.get(&[i, j, k]), expected);
3447 }
3448 }
3449 }
3450 }
3451
3452 #[test]
3453 fn concat_non_contiguous_operand() {
3454 let a = NdArray::from_buffer(
3457 Buffer::from_slice(&[1.0, 2.0, 3.0, 4.0]),
3458 &[2, 2],
3459 &[2, 1],
3460 );
3461 assert!(!a.is_contiguous());
3462 let b = NdArray::from_slice(&[5.0, 6.0, 7.0, 8.0], &[2, 2]);
3463 let c = a.concat(b).unwrap();
3464 assert_eq!(c.shape(), &[4, 2]);
3465 assert_eq!(c.col(0), &[1.0, 3.0, 5.0, 6.0]);
3466 assert_eq!(c.col(1), &[2.0, 4.0, 7.0, 8.0]);
3467 }
3468
3469 #[cfg(feature = "views")]
3474 #[test]
3475 fn set_after_view_copy_on_write() {
3476 let mut a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
3477 let v = a.as_view();
3478 a.set(&[0, 0], 99.0);
3479 assert_eq!(a.get(&[0, 0]), 99.0);
3482 assert_eq!(v.get(&[0, 0]), 1.0);
3483 }
3484
3485 #[test]
3486 fn fill_with_non_contiguous_logical_only() {
3487 let mut buf = Vec64::with_capacity(11);
3489 buf.0.resize(11, 0.0);
3490 let mut a = NdArray::from_buffer(Buffer::from_vec64(buf), &[3, 2], &[1, 8]);
3491 assert!(!a.is_contiguous());
3492 a.fill_with(7.0);
3493 for v in &a { assert_eq!(v, 7.0); }
3494 assert_eq!(a.as_slice()[3], 0.0);
3496 assert_eq!(a.as_slice()[7], 0.0);
3497 }
3498
3499 #[test]
3504 fn range_from_index_2d() {
3505 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3506 assert_eq!(&a[1..], &[4.0, 5.0, 6.0]);
3507 }
3508
3509 #[test]
3510 fn range_to_index_2d() {
3511 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3512 assert_eq!(&a[..1], &[1.0, 2.0, 3.0]);
3513 }
3514
3515 #[test]
3516 fn range_full_index_2d() {
3517 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3518 assert_eq!(&a[..], &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3519 }
3520
3521 #[cfg(all(feature = "views", feature = "select"))]
3526 #[test]
3527 fn row_selection_single_index_window() {
3528 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3529 assert_eq!(a.n_obs(), 3);
3530 let v = a.r(1usize);
3531 assert_eq!(v.shape(), &[1, 2]);
3532 assert_eq!(v.get(&[0, 0]), 2.0);
3533 assert_eq!(v.get(&[0, 1]), 5.0);
3534 assert_eq!(v.source.len(), a.len());
3536 assert_eq!(unsafe { v.get_unchecked(&[0, 1]) }, 5.0);
3538 }
3539
3540 #[cfg(feature = "size")]
3541 #[test]
3542 fn est_bytes_covers_buffer() {
3543 use crate::traits::byte_size::ByteSize;
3544 let a = NdArray::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[3, 2]);
3545 assert!(a.est_bytes() >= 6 * size_of::<f64>());
3546 }
3547}