From 3a4b9c7769d4fc3f7b2aaca46612014982fdbfb6 Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 25 Mar 2025 19:13:14 +0000 Subject: [PATCH 01/54] Changes Dot impl to be on ArrayRef (#1494) Also adds an accelerate option to the blas-tests crate --- Cargo.lock | 7 +++++++ crates/blas-tests/Cargo.toml | 1 + src/linalg/impl_linalg.rs | 10 +++------- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0530aff0..d1a513a74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "accelerate-src" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415ed64958754dbe991900f3940677e6a7eefb4d7367afd70d642677b0c7d19d" + [[package]] name = "adler2" version = "2.0.0" @@ -66,6 +72,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95e83dc868db96e69795c0213143095f03de9dd3252f205d4ac716e4076a7e0" dependencies = [ + "accelerate-src", "blis-src", "netlib-src", "openblas-src", diff --git a/crates/blas-tests/Cargo.toml b/crates/blas-tests/Cargo.toml index ff556873a..08acc7fa5 100644 --- a/crates/blas-tests/Cargo.toml +++ b/crates/blas-tests/Cargo.toml @@ -34,3 +34,4 @@ openblas-cache = ["blas-src", "blas-src/openblas", "openblas-src/cache"] netlib = ["blas-src", "blas-src/netlib"] netlib-system = ["blas-src", "blas-src/netlib", "netlib-src/system"] blis-system = ["blas-src", "blas-src/blis", "blis-src/system"] +accelerate = ["blas-src", "blas-src/accelerate"] diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index 0f28cac1d..d34fd9156 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -1099,16 +1099,12 @@ mod blas_tests /// - The arrays have dimensions other than 1 or 2 /// - The array shapes are incompatible for the operation /// - For vector dot product: the vectors have different lengths -/// -impl Dot> for ArrayBase -where - S: Data, - S2: Data, - A: LinalgScalar, +impl Dot> for ArrayRef +where A: LinalgScalar { type Output = Array; - fn dot(&self, rhs: &ArrayBase) -> Self::Output + fn dot(&self, rhs: &ArrayRef) -> Self::Output { match (self.ndim(), rhs.ndim()) { (1, 1) => { From 2a5cae1dc0f01ec31a336c575434e3a88cdcba25 Mon Sep 17 00:00:00 2001 From: HuiSeomKim <126950833+NewBornRustacean@users.noreply.github.com> Date: Thu, 27 Mar 2025 08:55:03 +0900 Subject: [PATCH 02/54] Add cumprod (#1491) --- src/numeric/impl_numeric.rs | 41 +++++++++++++++++++++- tests/numeric.rs | 70 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index 27c5687ee..ae82a482a 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -10,7 +10,7 @@ use num_traits::Float; use num_traits::One; use num_traits::{FromPrimitive, Zero}; -use std::ops::{Add, Div, Mul, Sub}; +use std::ops::{Add, Div, Mul, MulAssign, Sub}; use crate::imp_prelude::*; use crate::numeric_util; @@ -97,6 +97,45 @@ where D: Dimension sum } + /// Return the cumulative product of elements along a given axis. + /// + /// ``` + /// use ndarray::{arr2, Axis}; + /// + /// let a = arr2(&[[1., 2., 3.], + /// [4., 5., 6.]]); + /// + /// // Cumulative product along rows (axis 0) + /// assert_eq!( + /// a.cumprod(Axis(0)), + /// arr2(&[[1., 2., 3.], + /// [4., 10., 18.]]) + /// ); + /// + /// // Cumulative product along columns (axis 1) + /// assert_eq!( + /// a.cumprod(Axis(1)), + /// arr2(&[[1., 2., 6.], + /// [4., 20., 120.]]) + /// ); + /// ``` + /// + /// **Panics** if `axis` is out of bounds. + #[track_caller] + pub fn cumprod(&self, axis: Axis) -> Array + where + A: Clone + Mul + MulAssign, + D: Dimension + RemoveAxis, + { + if axis.0 >= self.ndim() { + panic!("axis is out of bounds for array of dimension"); + } + + let mut result = self.to_owned(); + result.accumulate_axis_inplace(axis, |prev, curr| *curr *= prev.clone()); + result + } + /// Return variance of elements in the array. /// /// The variance is computed using the [Welford one-pass diff --git a/tests/numeric.rs b/tests/numeric.rs index 839aba58e..7e6964812 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -75,6 +75,76 @@ fn sum_mean_prod_empty() assert_eq!(a, None); } +#[test] +fn test_cumprod_1d() +{ + let a = array![1, 2, 3, 4]; + let result = a.cumprod(Axis(0)); + assert_eq!(result, array![1, 2, 6, 24]); +} + +#[test] +fn test_cumprod_2d() +{ + let a = array![[1, 2], [3, 4]]; + + let result_axis0 = a.cumprod(Axis(0)); + assert_eq!(result_axis0, array![[1, 2], [3, 8]]); + + let result_axis1 = a.cumprod(Axis(1)); + assert_eq!(result_axis1, array![[1, 2], [3, 12]]); +} + +#[test] +fn test_cumprod_3d() +{ + let a = array![[[1, 2], [3, 4]], [[5, 6], [7, 8]]]; + + let result_axis0 = a.cumprod(Axis(0)); + assert_eq!(result_axis0, array![[[1, 2], [3, 4]], [[5, 12], [21, 32]]]); + + let result_axis1 = a.cumprod(Axis(1)); + assert_eq!(result_axis1, array![[[1, 2], [3, 8]], [[5, 6], [35, 48]]]); + + let result_axis2 = a.cumprod(Axis(2)); + assert_eq!(result_axis2, array![[[1, 2], [3, 12]], [[5, 30], [7, 56]]]); +} + +#[test] +fn test_cumprod_empty() +{ + // For 2D empty array + let b: Array2 = Array2::zeros((0, 0)); + let result_axis0 = b.cumprod(Axis(0)); + assert_eq!(result_axis0, Array2::zeros((0, 0))); + let result_axis1 = b.cumprod(Axis(1)); + assert_eq!(result_axis1, Array2::zeros((0, 0))); +} + +#[test] +fn test_cumprod_1_element() +{ + // For 1D array with one element + let a = array![5]; + let result = a.cumprod(Axis(0)); + assert_eq!(result, array![5]); + + // For 2D array with one element + let b = array![[5]]; + let result_axis0 = b.cumprod(Axis(0)); + let result_axis1 = b.cumprod(Axis(1)); + assert_eq!(result_axis0, array![[5]]); + assert_eq!(result_axis1, array![[5]]); +} + +#[test] +#[should_panic(expected = "axis is out of bounds for array of dimension")] +fn test_cumprod_axis_out_of_bounds() +{ + let a = array![[1, 2], [3, 4]]; + let _result = a.cumprod(Axis(2)); +} + #[test] #[cfg(feature = "std")] fn var() From 4e2a70f186560292bb73e030b67d54bf6faf1a9f Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 30 Mar 2025 00:35:18 -0400 Subject: [PATCH 03/54] Allows benchmarks that do not use linspace to run on no_std (#1495) --- benches/bench1.rs | 5 +++++ benches/construct.rs | 2 ++ benches/higher-order.rs | 5 +++++ benches/iter.rs | 6 ++++++ benches/numeric.rs | 1 + src/linalg/impl_linalg.rs | 1 - 6 files changed, 19 insertions(+), 1 deletion(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 33185844a..c07b8e3d9 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -982,6 +982,7 @@ fn dot_extended(bench: &mut test::Bencher) const MEAN_SUM_N: usize = 127; +#[cfg(feature = "std")] fn range_mat(m: Ix, n: Ix) -> Array2 { assert!(m * n != 0); @@ -990,6 +991,7 @@ fn range_mat(m: Ix, n: Ix) -> Array2 .unwrap() } +#[cfg(feature = "std")] #[bench] fn mean_axis0(bench: &mut test::Bencher) { @@ -997,6 +999,7 @@ fn mean_axis0(bench: &mut test::Bencher) bench.iter(|| a.mean_axis(Axis(0))); } +#[cfg(feature = "std")] #[bench] fn mean_axis1(bench: &mut test::Bencher) { @@ -1004,6 +1007,7 @@ fn mean_axis1(bench: &mut test::Bencher) bench.iter(|| a.mean_axis(Axis(1))); } +#[cfg(feature = "std")] #[bench] fn sum_axis0(bench: &mut test::Bencher) { @@ -1011,6 +1015,7 @@ fn sum_axis0(bench: &mut test::Bencher) bench.iter(|| a.sum_axis(Axis(0))); } +#[cfg(feature = "std")] #[bench] fn sum_axis1(bench: &mut test::Bencher) { diff --git a/benches/construct.rs b/benches/construct.rs index 278174388..380d87799 100644 --- a/benches/construct.rs +++ b/benches/construct.rs @@ -19,6 +19,7 @@ fn zeros_f64(bench: &mut Bencher) bench.iter(|| Array::::zeros((128, 128))) } +#[cfg(feature = "std")] #[bench] fn map_regular(bench: &mut test::Bencher) { @@ -28,6 +29,7 @@ fn map_regular(bench: &mut test::Bencher) bench.iter(|| a.map(|&x| 2. * x)); } +#[cfg(feature = "std")] #[bench] fn map_stride(bench: &mut test::Bencher) { diff --git a/benches/higher-order.rs b/benches/higher-order.rs index 9cc3bd961..1b4e8340c 100644 --- a/benches/higher-order.rs +++ b/benches/higher-order.rs @@ -12,6 +12,7 @@ const N: usize = 1024; const X: usize = 64; const Y: usize = 16; +#[cfg(feature = "std")] #[bench] fn map_regular(bench: &mut Bencher) { @@ -26,6 +27,7 @@ pub fn double_array(mut a: ArrayViewMut2<'_, f64>) a *= 2.0; } +#[cfg(feature = "std")] #[bench] fn map_stride_double_f64(bench: &mut Bencher) { @@ -38,6 +40,7 @@ fn map_stride_double_f64(bench: &mut Bencher) }); } +#[cfg(feature = "std")] #[bench] fn map_stride_f64(bench: &mut Bencher) { @@ -48,6 +51,7 @@ fn map_stride_f64(bench: &mut Bencher) bench.iter(|| av.map(|&x| 2. * x)); } +#[cfg(feature = "std")] #[bench] fn map_stride_u32(bench: &mut Bencher) { @@ -59,6 +63,7 @@ fn map_stride_u32(bench: &mut Bencher) bench.iter(|| av.map(|&x| 2 * x)); } +#[cfg(feature = "std")] #[bench] fn fold_axis(bench: &mut Bencher) { diff --git a/benches/iter.rs b/benches/iter.rs index 77f511745..154ee4eaf 100644 --- a/benches/iter.rs +++ b/benches/iter.rs @@ -45,6 +45,7 @@ fn iter_sum_2d_transpose(bench: &mut Bencher) bench.iter(|| a.iter().sum::()); } +#[cfg(feature = "std")] #[bench] fn iter_filter_sum_2d_u32(bench: &mut Bencher) { @@ -55,6 +56,7 @@ fn iter_filter_sum_2d_u32(bench: &mut Bencher) bench.iter(|| b.iter().filter(|&&x| x < 75).sum::()); } +#[cfg(feature = "std")] #[bench] fn iter_filter_sum_2d_f32(bench: &mut Bencher) { @@ -65,6 +67,7 @@ fn iter_filter_sum_2d_f32(bench: &mut Bencher) bench.iter(|| b.iter().filter(|&&x| x < 75.).sum::()); } +#[cfg(feature = "std")] #[bench] fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) { @@ -76,6 +79,7 @@ fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) bench.iter(|| b.iter().filter(|&&x| x < 75).sum::()); } +#[cfg(feature = "std")] #[bench] fn iter_filter_sum_2d_stride_f32(bench: &mut Bencher) { @@ -87,6 +91,7 @@ fn iter_filter_sum_2d_stride_f32(bench: &mut Bencher) bench.iter(|| b.iter().filter(|&&x| x < 75.).sum::()); } +#[cfg(feature = "std")] #[bench] fn iter_rev_step_by_contiguous(bench: &mut Bencher) { @@ -98,6 +103,7 @@ fn iter_rev_step_by_contiguous(bench: &mut Bencher) }); } +#[cfg(feature = "std")] #[bench] fn iter_rev_step_by_discontiguous(bench: &mut Bencher) { diff --git a/benches/numeric.rs b/benches/numeric.rs index e2ffa1b84..ceb57fbd7 100644 --- a/benches/numeric.rs +++ b/benches/numeric.rs @@ -9,6 +9,7 @@ const N: usize = 1024; const X: usize = 64; const Y: usize = 16; +#[cfg(feature = "std")] #[bench] fn clip(bench: &mut Bencher) { diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index d34fd9156..0bbc0b026 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -1071,7 +1071,6 @@ mod blas_tests for stride in 1..=MAXSTRIDE { let m = ArrayView::from_shape((N, N).strides((stride, 1)), &data).unwrap(); - eprintln!("{:?}", m); if stride < N { assert_eq!(get_blas_compatible_layout(&m), None); From 2324d2a49cb19d848b3aa8629d63e73095d783b1 Mon Sep 17 00:00:00 2001 From: HuiSeomKim <126950833+NewBornRustacean@users.noreply.github.com> Date: Mon, 7 Apr 2025 02:31:05 +0900 Subject: [PATCH 04/54] Add partition(similar to numpy.partition) (#1498) * fn partition --- src/impl_methods.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index d2f04ef1f..42d843781 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -3184,6 +3184,81 @@ impl ArrayRef f(&*prev, &mut *curr) }); } + + /// Return a partitioned copy of the array. + /// + /// Creates a copy of the array and partially sorts it around the k-th element along the given axis. + /// The k-th element will be in its sorted position, with: + /// - All elements smaller than the k-th element to its left + /// - All elements equal or greater than the k-th element to its right + /// - The ordering within each partition is undefined + /// + /// # Parameters + /// + /// * `kth` - Index to partition by. The k-th element will be in its sorted position. + /// * `axis` - Axis along which to partition. + /// + /// # Returns + /// + /// A new array of the same shape and type as the input array, with elements partitioned. + /// + /// # Examples + /// + /// ``` + /// use ndarray::prelude::*; + /// + /// let a = array![7, 1, 5, 2, 6, 0, 3, 4]; + /// let p = a.partition(3, Axis(0)); + /// + /// // The element at position 3 is now 3, with smaller elements to the left + /// // and greater elements to the right + /// assert_eq!(p[3], 3); + /// assert!(p.slice(s![..3]).iter().all(|&x| x <= 3)); + /// assert!(p.slice(s![4..]).iter().all(|&x| x >= 3)); + /// ``` + pub fn partition(&self, kth: usize, axis: Axis) -> Array + where + A: Clone + Ord + num_traits::Zero, + D: Dimension, + { + // Bounds checking + let axis_len = self.len_of(axis); + if kth >= axis_len { + panic!("partition index {} is out of bounds for axis of length {}", kth, axis_len); + } + + let mut result = self.to_owned(); + + // Check if the first lane is contiguous + let is_contiguous = result + .lanes_mut(axis) + .into_iter() + .next() + .unwrap() + .is_contiguous(); + + if is_contiguous { + Zip::from(result.lanes_mut(axis)).for_each(|mut lane| { + lane.as_slice_mut().unwrap().select_nth_unstable(kth); + }); + } else { + let mut temp_vec = vec![A::zero(); axis_len]; + + Zip::from(result.lanes_mut(axis)).for_each(|mut lane| { + Zip::from(&mut temp_vec).and(&lane).for_each(|dest, src| { + *dest = src.clone(); + }); + + temp_vec.select_nth_unstable(kth); + + Zip::from(&mut lane).and(&temp_vec).for_each(|dest, src| { + *dest = src.clone(); + }); + }); + } + + result + } } /// Transmute from A to B. @@ -3277,4 +3352,121 @@ mod tests let _a2 = a.clone(); assert_first!(a); } + + #[test] + fn test_partition_1d() + { + // Test partitioning a 1D array + let array = arr1(&[3, 1, 4, 1, 5, 9, 2, 6]); + let result = array.partition(3, Axis(0)); + // After partitioning, the element at index 3 should be in its final sorted position + assert!(result.slice(s![..3]).iter().all(|&x| x <= result[3])); + assert!(result.slice(s![4..]).iter().all(|&x| x >= result[3])); + } + + #[test] + fn test_partition_2d() + { + // Test partitioning a 2D array along both axes + let array = arr2(&[[3, 1, 4], [1, 5, 9], [2, 6, 5]]); + + // Partition along axis 0 (rows) + let result0 = array.partition(1, Axis(0)); + // After partitioning along axis 0, each column should have its middle element in the correct position + assert!(result0[[0, 0]] <= result0[[1, 0]] && result0[[2, 0]] >= result0[[1, 0]]); + assert!(result0[[0, 1]] <= result0[[1, 1]] && result0[[2, 1]] >= result0[[1, 1]]); + assert!(result0[[0, 2]] <= result0[[1, 2]] && result0[[2, 2]] >= result0[[1, 2]]); + + // Partition along axis 1 (columns) + let result1 = array.partition(1, Axis(1)); + // After partitioning along axis 1, each row should have its middle element in the correct position + assert!(result1[[0, 0]] <= result1[[0, 1]] && result1[[0, 2]] >= result1[[0, 1]]); + assert!(result1[[1, 0]] <= result1[[1, 1]] && result1[[1, 2]] >= result1[[1, 1]]); + assert!(result1[[2, 0]] <= result1[[2, 1]] && result1[[2, 2]] >= result1[[2, 1]]); + } + + #[test] + fn test_partition_3d() + { + // Test partitioning a 3D array + let array = arr3(&[[[3, 1], [4, 1]], [[5, 9], [2, 6]]]); + + // Partition along axis 0 + let result = array.partition(0, Axis(0)); + // After partitioning, each 2x2 slice should have its first element in the correct position + assert!(result[[0, 0, 0]] <= result[[1, 0, 0]]); + assert!(result[[0, 0, 1]] <= result[[1, 0, 1]]); + assert!(result[[0, 1, 0]] <= result[[1, 1, 0]]); + assert!(result[[0, 1, 1]] <= result[[1, 1, 1]]); + } + + #[test] + #[should_panic] + fn test_partition_invalid_kth() + { + let a = array![1, 2, 3, 4]; + // This should panic because kth=4 is out of bounds + let _ = a.partition(4, Axis(0)); + } + + #[test] + #[should_panic] + fn test_partition_invalid_axis() + { + let a = array![1, 2, 3, 4]; + // This should panic because axis=1 is out of bounds for a 1D array + let _ = a.partition(0, Axis(1)); + } + + #[test] + fn test_partition_contiguous_or_not() + { + // Test contiguous case (C-order) + let a = array![ + [7, 1, 5], + [2, 6, 0], + [3, 4, 8] + ]; + + // Partition along axis 0 (contiguous) + let p_axis0 = a.partition(1, Axis(0)); + + // For each column, verify the partitioning: + // - First row should be <= middle row (kth element) + // - Last row should be >= middle row (kth element) + for col in 0..3 { + let kth = p_axis0[[1, col]]; + assert!(p_axis0[[0, col]] <= kth, + "Column {}: First row {} should be <= middle row {}", + col, p_axis0[[0, col]], kth); + assert!(p_axis0[[2, col]] >= kth, + "Column {}: Last row {} should be >= middle row {}", + col, p_axis0[[2, col]], kth); + } + + // Test non-contiguous case (F-order) + let a = array![ + [7, 1, 5], + [2, 6, 0], + [3, 4, 8] + ]; + + // Make array non-contiguous by transposing + let a = a.t().to_owned(); + + // Partition along axis 1 (non-contiguous) + let p_axis1 = a.partition(1, Axis(1)); + + // For each row, verify the partitioning: + // - First column should be <= middle column + // - Last column should be >= middle column + for row in 0..3 { + assert!(p_axis1[[row, 0]] <= p_axis1[[row, 1]], + "Row {}: First column {} should be <= middle column {}", + row, p_axis1[[row, 0]], p_axis1[[row, 1]]); + assert!(p_axis1[[row, 2]] >= p_axis1[[row, 1]], + "Row {}: Last column {} should be >= middle column {}", + row, p_axis1[[row, 2]], p_axis1[[row, 1]]); + } + } } From da115c919ec76314109ff280bbba9fb9e6056a65 Mon Sep 17 00:00:00 2001 From: akern40 Date: Thu, 10 Apr 2025 00:00:46 -0400 Subject: [PATCH 05/54] Fix partition on empty arrays (#1502) Closes #1501 --- src/impl_methods.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 42d843781..ea9c9a0d5 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -3229,16 +3229,22 @@ impl ArrayRef let mut result = self.to_owned(); + // Must guarantee that the array isn't empty before checking for contiguity + if result.shape().iter().any(|s| *s == 0) { + return result; + } + // Check if the first lane is contiguous let is_contiguous = result .lanes_mut(axis) .into_iter() .next() + // This unwrap shouldn't cause panics because the array isn't empty .unwrap() .is_contiguous(); if is_contiguous { - Zip::from(result.lanes_mut(axis)).for_each(|mut lane| { + result.lanes_mut(axis).into_iter().for_each(|mut lane| { lane.as_slice_mut().unwrap().select_nth_unstable(kth); }); } else { From 8bd70b0c51e6ff3a6b6c2df94b4bcf9547dc9128 Mon Sep 17 00:00:00 2001 From: HuiSeomKim <126950833+NewBornRustacean@users.noreply.github.com> Date: Sun, 20 Apr 2025 22:24:21 +0900 Subject: [PATCH 06/54] add test case for partition on empty array (#1504) * add test case for empty array * return early when the array has zero lenth dims --------- Co-authored-by: Adam Kern --- src/impl_methods.rs | 141 +++++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 48 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index ea9c9a0d5..9a1741be6 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -576,11 +576,7 @@ where pub fn slice_move(mut self, info: I) -> ArrayBase where I: SliceArg { - assert_eq!( - info.in_ndim(), - self.ndim(), - "The input dimension of `info` must match the array to be sliced.", - ); + assert_eq!(info.in_ndim(), self.ndim(), "The input dimension of `info` must match the array to be sliced.",); let out_ndim = info.out_ndim(); let mut new_dim = I::OutDim::zeros(out_ndim); let mut new_strides = I::OutDim::zeros(out_ndim); @@ -648,11 +644,7 @@ impl LayoutRef pub fn slice_collapse(&mut self, info: I) where I: SliceArg { - assert_eq!( - info.in_ndim(), - self.ndim(), - "The input dimension of `info` must match the array to be sliced.", - ); + assert_eq!(info.in_ndim(), self.ndim(), "The input dimension of `info` must match the array to be sliced.",); let mut axis = 0; info.as_ref().iter().for_each(|&ax_info| match ax_info { SliceInfoElem::Slice { start, end, step } => { @@ -1120,8 +1112,7 @@ impl ArrayRef // bounds check the indices first if let Some(max_index) = indices.iter().cloned().max() { if max_index >= axis_len { - panic!("ndarray: index {} is out of bounds in array of len {}", - max_index, self.len_of(axis)); + panic!("ndarray: index {} is out of bounds in array of len {}", max_index, self.len_of(axis)); } } // else: indices empty is ok let view = self.view().into_dimensionality::().unwrap(); @@ -1530,10 +1521,7 @@ impl ArrayRef ndassert!( axis_index < self.ndim(), - concat!( - "Window axis {} does not match array dimension {} ", - "(with array of shape {:?})" - ), + concat!("Window axis {} does not match array dimension {} ", "(with array of shape {:?})"), axis_index, self.ndim(), self.shape() @@ -3119,8 +3107,7 @@ where /// ***Panics*** if not `index < self.len_of(axis)`. pub fn remove_index(&mut self, axis: Axis, index: usize) { - assert!(index < self.len_of(axis), "index {} must be less than length of Axis({})", - index, axis.index()); + assert!(index < self.len_of(axis), "index {} must be less than length of Axis({})", index, axis.index()); let (_, mut tail) = self.view_mut().split_at(axis, index); // shift elements to the front Zip::from(tail.lanes_mut(axis)).for_each(|mut lane| lane.rotate1_front()); @@ -3193,15 +3180,16 @@ impl ArrayRef /// - All elements equal or greater than the k-th element to its right /// - The ordering within each partition is undefined /// + /// Empty arrays (i.e., those with any zero-length axes) are considered partitioned already, + /// and will be returned unchanged. + /// + /// **Panics** if `k` is out of bounds for a non-zero axis length. + /// /// # Parameters /// /// * `kth` - Index to partition by. The k-th element will be in its sorted position. /// * `axis` - Axis along which to partition. /// - /// # Returns - /// - /// A new array of the same shape and type as the input array, with elements partitioned. - /// /// # Examples /// /// ``` @@ -3221,19 +3209,19 @@ impl ArrayRef A: Clone + Ord + num_traits::Zero, D: Dimension, { - // Bounds checking - let axis_len = self.len_of(axis); - if kth >= axis_len { - panic!("partition index {} is out of bounds for axis of length {}", kth, axis_len); - } - let mut result = self.to_owned(); - // Must guarantee that the array isn't empty before checking for contiguity - if result.shape().iter().any(|s| *s == 0) { + // Return early if the array has zero-length dimensions + if self.shape().iter().any(|s| *s == 0) { return result; } + // Bounds checking. Panics if kth is out of bounds + let axis_len = self.len_of(axis); + if kth >= axis_len { + panic!("Partition index {} is out of bounds for axis {} of length {}", kth, axis.0, axis_len); + } + // Check if the first lane is contiguous let is_contiguous = result .lanes_mut(axis) @@ -3428,11 +3416,7 @@ mod tests fn test_partition_contiguous_or_not() { // Test contiguous case (C-order) - let a = array![ - [7, 1, 5], - [2, 6, 0], - [3, 4, 8] - ]; + let a = array![[7, 1, 5], [2, 6, 0], [3, 4, 8]]; // Partition along axis 0 (contiguous) let p_axis0 = a.partition(1, Axis(0)); @@ -3442,20 +3426,24 @@ mod tests // - Last row should be >= middle row (kth element) for col in 0..3 { let kth = p_axis0[[1, col]]; - assert!(p_axis0[[0, col]] <= kth, + assert!( + p_axis0[[0, col]] <= kth, "Column {}: First row {} should be <= middle row {}", - col, p_axis0[[0, col]], kth); - assert!(p_axis0[[2, col]] >= kth, + col, + p_axis0[[0, col]], + kth + ); + assert!( + p_axis0[[2, col]] >= kth, "Column {}: Last row {} should be >= middle row {}", - col, p_axis0[[2, col]], kth); + col, + p_axis0[[2, col]], + kth + ); } // Test non-contiguous case (F-order) - let a = array![ - [7, 1, 5], - [2, 6, 0], - [3, 4, 8] - ]; + let a = array![[7, 1, 5], [2, 6, 0], [3, 4, 8]]; // Make array non-contiguous by transposing let a = a.t().to_owned(); @@ -3467,12 +3455,69 @@ mod tests // - First column should be <= middle column // - Last column should be >= middle column for row in 0..3 { - assert!(p_axis1[[row, 0]] <= p_axis1[[row, 1]], + assert!( + p_axis1[[row, 0]] <= p_axis1[[row, 1]], "Row {}: First column {} should be <= middle column {}", - row, p_axis1[[row, 0]], p_axis1[[row, 1]]); - assert!(p_axis1[[row, 2]] >= p_axis1[[row, 1]], + row, + p_axis1[[row, 0]], + p_axis1[[row, 1]] + ); + assert!( + p_axis1[[row, 2]] >= p_axis1[[row, 1]], "Row {}: Last column {} should be >= middle column {}", - row, p_axis1[[row, 2]], p_axis1[[row, 1]]); + row, + p_axis1[[row, 2]], + p_axis1[[row, 1]] + ); } } + + #[test] + fn test_partition_empty() + { + // Test 1D empty array + let empty1d = Array1::::zeros(0); + let result1d = empty1d.partition(0, Axis(0)); + assert_eq!(result1d.len(), 0); + + // Test 1D empty array with kth out of bounds + let result1d_out_of_bounds = empty1d.partition(1, Axis(0)); + assert_eq!(result1d_out_of_bounds.len(), 0); + + // Test 2D empty array + let empty2d = Array2::::zeros((0, 3)); + let result2d = empty2d.partition(0, Axis(0)); + assert_eq!(result2d.shape(), &[0, 3]); + + // Test 2D empty array with zero columns + let empty2d_cols = Array2::::zeros((2, 0)); + let result2d_cols = empty2d_cols.partition(0, Axis(1)); + assert_eq!(result2d_cols.shape(), &[2, 0]); + + // Test 3D empty array + let empty3d = Array3::::zeros((0, 2, 3)); + let result3d = empty3d.partition(0, Axis(0)); + assert_eq!(result3d.shape(), &[0, 2, 3]); + + // Test 3D empty array with zero in middle dimension + let empty3d_mid = Array3::::zeros((2, 0, 3)); + let result3d_mid = empty3d_mid.partition(0, Axis(1)); + assert_eq!(result3d_mid.shape(), &[2, 0, 3]); + + // Test 4D empty array + let empty4d = Array4::::zeros((0, 2, 3, 4)); + let result4d = empty4d.partition(0, Axis(0)); + assert_eq!(result4d.shape(), &[0, 2, 3, 4]); + + // Test empty array with non-zero dimensions in other axes + let empty_mixed = Array2::::zeros((0, 5)); + let result_mixed = empty_mixed.partition(0, Axis(0)); + assert_eq!(result_mixed.shape(), &[0, 5]); + + // Test empty array with negative strides + let arr = Array2::::zeros((3, 3)); + let empty_slice = arr.slice(s![0..0, ..]); + let result_slice = empty_slice.partition(0, Axis(0)); + assert_eq!(result_slice.shape(), &[0, 3]); + } } From dbaebd01a8403e2aaafef854bb517c1f4130dcdc Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 20 May 2025 17:28:25 -0700 Subject: [PATCH 07/54] Move from `.iter().any()` to `.contains` and fixes some typos (#1509) --- src/dimension/mod.rs | 2 +- src/impl_methods.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs index eb07252b2..4ada0b127 100644 --- a/src/dimension/mod.rs +++ b/src/dimension/mod.rs @@ -269,7 +269,7 @@ fn can_index_slice_impl( ) -> Result<(), ShapeError> { // Check condition 3. - let is_empty = dim.slice().iter().any(|&d| d == 0); + let is_empty = dim.slice().contains(&0); if is_empty && max_offset > data_len { return Err(from_kind(ErrorKind::OutOfBounds)); } diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 9a1741be6..01a0a9a4d 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2338,7 +2338,7 @@ impl ArrayRef /// The implementation creates a view with strides set to zero for the /// axes that are to be repeated. /// - /// The broadcasting documentation for Numpy has more information. + /// The broadcasting documentation for NumPy has more information. /// /// ``` /// use ndarray::{aview1, aview2}; @@ -2690,7 +2690,7 @@ where impl ArrayRef { - /// Perform an elementwise assigment to `self` from `rhs`. + /// Perform an elementwise assignment to `self` from `rhs`. /// /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. /// @@ -2702,7 +2702,7 @@ impl ArrayRef self.zip_mut_with(rhs, |x, y| x.clone_from(y)); } - /// Perform an elementwise assigment of values cloned from `self` into array or producer `to`. + /// Perform an elementwise assignment of values cloned from `self` into array or producer `to`. /// /// The destination `to` can be another array or a producer of assignable elements. /// [`AssignElem`] determines how elements are assigned. @@ -2718,7 +2718,7 @@ impl ArrayRef Zip::from(self).map_assign_into(to, A::clone); } - /// Perform an elementwise assigment to `self` from element `x`. + /// Perform an elementwise assignment to `self` from element `x`. pub fn fill(&mut self, x: A) where A: Clone { @@ -3212,7 +3212,7 @@ impl ArrayRef let mut result = self.to_owned(); // Return early if the array has zero-length dimensions - if self.shape().iter().any(|s| *s == 0) { + if result.shape().contains(&0) { return result; } From ae25a9b3b697a87cfa44ed87d94a445dd7ba90e4 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova <58857108+ada4a@users.noreply.github.com> Date: Wed, 21 May 2025 02:52:35 +0200 Subject: [PATCH 08/54] Fix typo in impl_methods.rs docs (#1508) --- src/impl_methods.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 01a0a9a4d..0822ebd13 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -208,7 +208,7 @@ impl ArrayRef /// memory layout. Otherwise, the layout of the output array is unspecified. /// If you need a particular layout, you can allocate a new array with the /// desired memory layout and [`.assign()`](Self::assign) the data. - /// Alternatively, you can collectan iterator, like this for a result in + /// Alternatively, you can collect an iterator, like this for a result in /// standard layout: /// /// ``` From 0a8498a78c9f5d7ea02e9656f5e65f5c3969c9ef Mon Sep 17 00:00:00 2001 From: Daniel Goertzen Date: Tue, 20 May 2025 19:53:08 -0500 Subject: [PATCH 09/54] add axis_windows_with_stride() method (#1460) --------- Co-authored-by: Adam Kern --- src/impl_methods.rs | 18 ++++- src/iterators/windows.rs | 9 +-- tests/windows.rs | 142 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 5 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 0822ebd13..5a864d842 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -1516,6 +1516,17 @@ impl ArrayRef /// } /// ``` pub fn axis_windows(&self, axis: Axis, window_size: usize) -> AxisWindows<'_, A, D> + { + self.axis_windows_with_stride(axis, window_size, 1) + } + + /// Returns a producer which traverses over windows of a given length and + /// stride along an axis. + /// + /// Note that a calling this method with a stride of 1 is equivalent to + /// calling [`ArrayRef::axis_windows()`]. + pub fn axis_windows_with_stride(&self, axis: Axis, window_size: usize, stride_size: usize) + -> AxisWindows<'_, A, D> { let axis_index = axis.index(); @@ -1527,7 +1538,12 @@ impl ArrayRef self.shape() ); - AxisWindows::new(self.view(), axis, window_size) + ndassert!( + stride_size >0, + "Stride size must be greater than zero" + ); + + AxisWindows::new_with_stride(self.view(), axis, window_size, stride_size) } /// Return a view of the diagonal elements of the array. diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs index afdaaa895..f3442c0af 100644 --- a/src/iterators/windows.rs +++ b/src/iterators/windows.rs @@ -141,7 +141,7 @@ pub struct AxisWindows<'a, A, D> impl<'a, A, D: Dimension> AxisWindows<'a, A, D> { - pub(crate) fn new(a: ArrayView<'a, A, D>, axis: Axis, window_size: usize) -> Self + pub(crate) fn new_with_stride(a: ArrayView<'a, A, D>, axis: Axis, window_size: usize, stride_size: usize) -> Self { let window_strides = a.strides.clone(); let axis_idx = axis.index(); @@ -150,10 +150,11 @@ impl<'a, A, D: Dimension> AxisWindows<'a, A, D> window[axis_idx] = window_size; let ndim = window.ndim(); - let mut unit_stride = D::zeros(ndim); - unit_stride.slice_mut().fill(1); + let mut stride = D::zeros(ndim); + stride.slice_mut().fill(1); + stride[axis_idx] = stride_size; - let base = build_base(a, window.clone(), unit_stride); + let base = build_base(a, window.clone(), stride); AxisWindows { base, axis_idx, diff --git a/tests/windows.rs b/tests/windows.rs index 6506d8301..4d4d0d7d7 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -294,6 +294,148 @@ fn tests_axis_windows_3d_zips_with_1d() assert_eq!(b,arr1(&[207, 261])); } +/// Test verifies that non existent Axis results in panic +#[test] +#[should_panic] +fn axis_windows_with_stride_outofbound() +{ + let a = Array::from_iter(10..37) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + a.axis_windows_with_stride(Axis(4), 2, 2); +} + +/// Test verifies that zero sizes results in panic +#[test] +#[should_panic] +fn axis_windows_with_stride_zero_size() +{ + let a = Array::from_iter(10..37) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + a.axis_windows_with_stride(Axis(0), 0, 2); +} + +/// Test verifies that zero stride results in panic +#[test] +#[should_panic] +fn axis_windows_with_stride_zero_stride() +{ + let a = Array::from_iter(10..37) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + a.axis_windows_with_stride(Axis(0), 2, 0); +} + +/// Test verifies that over sized windows yield nothing +#[test] +fn axis_windows_with_stride_oversized() +{ + let a = Array::from_iter(10..37) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + let mut iter = a.axis_windows_with_stride(Axis(2), 4, 2).into_iter(); + assert_eq!(iter.next(), None); +} + +/// Simple test for iterating 1d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_with_stride_1d() +{ + let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap(); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(0), 5, 2), vec![ + arr1(&[10, 11, 12, 13, 14]), + arr1(&[12, 13, 14, 15, 16]), + arr1(&[14, 15, 16, 17, 18]), + ]); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(0), 5, 3), vec![ + arr1(&[10, 11, 12, 13, 14]), + arr1(&[13, 14, 15, 16, 17]), + ]); +} + +/// Simple test for iterating 2d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_with_stride_2d() +{ + let a = Array::from_iter(10..30) + .into_shape_with_order((5, 4)) + .unwrap(); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(0), 2, 1), vec![ + arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]), + arr2(&[[14, 15, 16, 17], [18, 19, 20, 21]]), + arr2(&[[18, 19, 20, 21], [22, 23, 24, 25]]), + arr2(&[[22, 23, 24, 25], [26, 27, 28, 29]]), + ]); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(0), 2, 2), vec![ + arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]), + arr2(&[[18, 19, 20, 21], [22, 23, 24, 25]]), + ]); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(0), 2, 3), vec![ + arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]), + arr2(&[[22, 23, 24, 25], [26, 27, 28, 29]]), + ]); +} + +/// Simple test for iterating 3d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_with_stride_3d() +{ + let a = Array::from_iter(0..27) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(1), 2, 1), vec![ + arr3(&[ + [[0, 1, 2], [3, 4, 5]], + [[9, 10, 11], [12, 13, 14]], + [[18, 19, 20], [21, 22, 23]], + ]), + arr3(&[ + [[3, 4, 5], [6, 7, 8]], + [[12, 13, 14], [15, 16, 17]], + [[21, 22, 23], [24, 25, 26]], + ]), + ]); + + itertools::assert_equal(a.axis_windows_with_stride(Axis(1), 2, 2), vec![ + arr3(&[ + [[0, 1, 2], [3, 4, 5]], + [[9, 10, 11], [12, 13, 14]], + [[18, 19, 20], [21, 22, 23]], + ]), + ]); +} + +#[test] +fn tests_axis_windows_with_stride_3d_zips_with_1d() +{ + let a = Array::from_iter(0..27) + .into_shape_with_order((3, 3, 3)) + .unwrap(); + let mut b1 = Array::zeros(2); + let mut b2 = Array::zeros(1); + + Zip::from(b1.view_mut()) + .and(a.axis_windows_with_stride(Axis(1), 2, 1)) + .for_each(|b, a| { + *b = a.sum(); + }); + assert_eq!(b1,arr1(&[207, 261])); + + Zip::from(b2.view_mut()) + .and(a.axis_windows_with_stride(Axis(1), 2, 2)) + .for_each(|b, a| { + *b = a.sum(); + }); + assert_eq!(b2,arr1(&[207])); +} + #[test] fn test_window_neg_stride() { From 2a2287cec6b0f709ec890315f9157951ada2a403 Mon Sep 17 00:00:00 2001 From: HuiSeomKim <126950833+NewBornRustacean@users.noreply.github.com> Date: Wed, 21 May 2025 09:58:33 +0900 Subject: [PATCH 10/54] In-place axis permutation with cycle detection (#1505) * draft for inplace reverse, permute * add test cases * formatter * cycle detection logic with bitmask * formatter * satisfying CI(cargo doc, etc.) * add comments from doc, to describe how the logic works --- src/impl_methods.rs | 76 +++++++++++++++++++++++++++++++++++++++++++ tests/array.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 5a864d842..9c2c5efc9 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2542,6 +2542,72 @@ where unsafe { self.with_strides_dim(new_strides, new_dim) } } + /// Permute the axes in-place. + /// + /// This does not move any data, it just adjusts the array's dimensions + /// and strides. + /// + /// *i* in the *j*-th place in the axes sequence means `self`'s *i*-th axis + /// becomes `self`'s *j*-th axis + /// + /// **Panics** if any of the axes are out of bounds, if an axis is missing, + /// or if an axis is repeated more than once. + /// + /// # Example + /// ```rust + /// use ndarray::{arr2, Array3}; + /// + /// let mut a = arr2(&[[0, 1], [2, 3]]); + /// a.permute_axes([1, 0]); + /// assert_eq!(a, arr2(&[[0, 2], [1, 3]])); + /// + /// let mut b = Array3::::zeros((1, 2, 3)); + /// b.permute_axes([1, 0, 2]); + /// assert_eq!(b.shape(), &[2, 1, 3]); + /// ``` + #[track_caller] + pub fn permute_axes(&mut self, axes: T) + where T: IntoDimension + { + let axes = axes.into_dimension(); + // Ensure that each axis is used exactly once. + let mut usage_counts = D::zeros(self.ndim()); + for axis in axes.slice() { + usage_counts[*axis] += 1; + } + for count in usage_counts.slice() { + assert_eq!(*count, 1, "each axis must be listed exactly once"); + } + + let dim = self.layout.dim.slice_mut(); + let strides = self.layout.strides.slice_mut(); + let axes = axes.slice(); + + // The cycle detection is done using a bitmask to track visited positions. + // For example, axes from [0,1,2] to [2, 0, 1] + // For axis values [1, 0, 2]: + // 1 << 1 // 0b0001 << 1 = 0b0010 (decimal 2) + // 1 << 0 // 0b0001 << 0 = 0b0001 (decimal 1) + // 1 << 2 // 0b0001 << 2 = 0b0100 (decimal 4) + // + // Each axis gets its own unique bit position in the bitmask: + // - Axis 0: bit 0 (rightmost) + // - Axis 1: bit 1 + // - Axis 2: bit 2 + // + let mut visited = 0usize; + for (new_axis, &axis) in axes.iter().enumerate() { + if (visited & (1 << axis)) != 0 { + continue; + } + + dim.swap(axis, new_axis); + strides.swap(axis, new_axis); + + visited |= (1 << axis) | (1 << new_axis); + } + } + /// Transpose the array by reversing axes. /// /// Transposition reverses the order of the axes (dimensions and strides) @@ -2552,6 +2618,16 @@ where self.layout.strides.slice_mut().reverse(); self } + + /// Reverse the axes of the array in-place. + /// + /// This does not move any data, it just adjusts the array's dimensions + /// and strides. + pub fn reverse_axes(&mut self) + { + self.layout.dim.slice_mut().reverse(); + self.layout.strides.slice_mut().reverse(); + } } impl ArrayRef diff --git a/tests/array.rs b/tests/array.rs index f1426625c..3d6fa6715 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -2828,3 +2828,81 @@ fn test_slice_assign() *a.slice_mut(s![1..3]) += 1; assert_eq!(a, array![0, 2, 3, 3, 4]); } + +#[test] +fn reverse_axes() +{ + let mut a = arr2(&[[1, 2], [3, 4]]); + a.reverse_axes(); + assert_eq!(a, arr2(&[[1, 3], [2, 4]])); + + let mut a = arr2(&[[1, 2, 3], [4, 5, 6]]); + a.reverse_axes(); + assert_eq!(a, arr2(&[[1, 4], [2, 5], [3, 6]])); + + let mut a = Array::from_iter(0..24) + .into_shape_with_order((2, 3, 4)) + .unwrap(); + let original = a.clone(); + a.reverse_axes(); + for ((i0, i1, i2), elem) in original.indexed_iter() { + assert_eq!(*elem, a[(i2, i1, i0)]); + } +} + +#[test] +fn permute_axes() +{ + let mut a = arr2(&[[1, 2], [3, 4]]); + a.permute_axes([1, 0]); + assert_eq!(a, arr2(&[[1, 3], [2, 4]])); + + let mut a = Array::from_iter(0..24) + .into_shape_with_order((2, 3, 4)) + .unwrap(); + let original = a.clone(); + a.permute_axes([2, 1, 0]); + for ((i0, i1, i2), elem) in original.indexed_iter() { + assert_eq!(*elem, a[(i2, i1, i0)]); + } + + let mut a = Array::from_iter(0..120) + .into_shape_with_order((2, 3, 4, 5)) + .unwrap(); + let original = a.clone(); + a.permute_axes([1, 0, 3, 2]); + for ((i0, i1, i2, i3), elem) in original.indexed_iter() { + assert_eq!(*elem, a[(i1, i0, i3, i2)]); + } +} + +#[should_panic] +#[test] +fn permute_axes_repeated_axis() +{ + let mut a = Array::from_iter(0..24) + .into_shape_with_order((2, 3, 4)) + .unwrap(); + a.permute_axes([1, 0, 1]); +} + +#[should_panic] +#[test] +fn permute_axes_missing_axis() +{ + let mut a = Array::from_iter(0..24) + .into_shape_with_order((2, 3, 4)) + .unwrap() + .into_dyn(); + a.permute_axes(&[2, 0][..]); +} + +#[should_panic] +#[test] +fn permute_axes_oob() +{ + let mut a = Array::from_iter(0..24) + .into_shape_with_order((2, 3, 4)) + .unwrap(); + a.permute_axes([1, 0, 3]); +} From e5a8d23e2cfb1fc35a28a30411e67255dcfab46c Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 20 May 2025 18:43:23 -0700 Subject: [PATCH 11/54] Removes `DataOwned` bound on `remove_index` (#1511) Closes #1442 --- src/impl_methods.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 9c2c5efc9..2025ca4ff 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -3179,13 +3179,7 @@ impl ArrayRef Zip::from(self.lanes_mut(axis)).map_collect(mapping) } } -} -impl ArrayBase -where - S: DataOwned + DataMut, - D: Dimension, -{ /// Remove the `index`th elements along `axis` and shift down elements from higher indexes. /// /// Note that this "removes" the elements by swapping them around to the end of the axis and From d974f6ca05e5581477ebc66136afaed4460274ff Mon Sep 17 00:00:00 2001 From: akern40 Date: Wed, 21 May 2025 19:22:33 -0400 Subject: [PATCH 12/54] Adds into-iter methods on views (#1510) These allow the iterators to act on the lifetime of the data, not the lifetime of the view. --- src/array_serde.rs | 2 +- src/arraytraits.rs | 4 +- src/impl_methods.rs | 6 +- src/impl_views/conversions.rs | 120 +++++++++++++++++++++++++++++----- src/lib.rs | 2 +- 5 files changed, 112 insertions(+), 22 deletions(-) diff --git a/src/array_serde.rs b/src/array_serde.rs index 50d9c2905..5d51a8011 100644 --- a/src/array_serde.rs +++ b/src/array_serde.rs @@ -18,7 +18,7 @@ use std::marker::PhantomData; use crate::imp_prelude::*; use super::arraytraits::ARRAY_FORMAT_VERSION; -use super::Iter; +use crate::iterators::Iter; use crate::IntoDimension; /// Verifies that the version of the deserialized array matches the current diff --git a/src/arraytraits.rs b/src/arraytraits.rs index 5068cd6c2..a34b1985e 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -357,7 +357,7 @@ where D: Dimension fn into_iter(self) -> Self::IntoIter { - self.into_iter_() + Iter::new(self) } } @@ -369,7 +369,7 @@ where D: Dimension fn into_iter(self) -> Self::IntoIter { - self.into_iter_() + IterMut::new(self) } } diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 2025ca4ff..7291f07cb 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -463,7 +463,7 @@ impl ArrayRef pub fn iter(&self) -> Iter<'_, A, D> { // debug_assert!(self.pointer_is_inbounds()); - self.view().into_iter_() + self.view().into_iter() } /// Return an iterator of mutable references to the elements of the array. @@ -474,7 +474,7 @@ impl ArrayRef /// Iterator element type is `&mut A`. pub fn iter_mut(&mut self) -> IterMut<'_, A, D> { - self.view_mut().into_iter_() + self.view_mut().into_iter() } /// Return an iterator of indexes and references to the elements of the array. @@ -1290,7 +1290,7 @@ impl ArrayRef pub fn outer_iter_mut(&mut self) -> AxisIterMut<'_, A, D::Smaller> where D: RemoveAxis { - self.view_mut().into_outer_iter() + self.view_mut().into_outer_iter_mut() } /// Return an iterator that traverses over `axis` diff --git a/src/impl_views/conversions.rs b/src/impl_views/conversions.rs index efd876f7a..5bc5f9ad6 100644 --- a/src/impl_views/conversions.rs +++ b/src/impl_views/conversions.rs @@ -13,7 +13,7 @@ use std::mem::MaybeUninit; use crate::imp_prelude::*; -use crate::{Baseiter, ElementsBase, ElementsBaseMut, Iter, IterMut}; +use crate::{Baseiter, ElementsBase, ElementsBaseMut}; use crate::dimension::offset_from_low_addr_ptr_to_logical_ptr; use crate::iter::{self, AxisIter, AxisIterMut}; @@ -213,7 +213,7 @@ where D: Dimension } } -/// Private array view methods +/// Methods for iterating over array views. impl<'a, A, D> ArrayView<'a, A, D> where D: Dimension { @@ -229,21 +229,47 @@ where D: Dimension ElementsBase::new(self) } - pub(crate) fn into_iter_(self) -> Iter<'a, A, D> + /// Convert into an outer iterator for this view. + /// + /// Unlike [ArrayRef::outer_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_outer_iter(self) -> iter::AxisIter<'a, A, D::Smaller> + where D: RemoveAxis { - Iter::new(self) + AxisIter::new(self, Axis(0)) } - /// Return an outer iterator for this view. - #[doc(hidden)] // not official - #[deprecated(note = "This method will be replaced.")] - pub fn into_outer_iter(self) -> iter::AxisIter<'a, A, D::Smaller> + /// Convert into an indexed iterator. + /// + /// Unlike [ArrayRef::indexed_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_indexed_iter(self) -> iter::IndexedIter<'a, A, D> + { + iter::IndexedIter::new(self.into_elements_base()) + } + + /// Convert into an iterator over an `axis`. + /// + /// Unlike [ArrayRef::axis_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_iter(self, axis: Axis) -> iter::AxisIter<'a, A, D::Smaller> where D: RemoveAxis { - AxisIter::new(self, Axis(0)) + AxisIter::new(self, axis) + } + + /// Convert into an iterator over an `axis` by chunks. + /// + /// Unlike [`ArrayRef::axis_chunks_iter`], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_chunks_iter(self, axis: Axis, chunk_size: usize) -> iter::AxisChunksIter<'a, A, D> + where D: RemoveAxis + { + iter::AxisChunksIter::new(self, axis, chunk_size) } } +/// Methods for iterating over mutable array views. impl<'a, A, D> ArrayViewMut<'a, A, D> where D: Dimension { @@ -294,17 +320,81 @@ where D: Dimension } } - pub(crate) fn into_iter_(self) -> IterMut<'a, A, D> + /// Convert into an outer iterator for this view. + /// + /// Unlike [ArrayRef::outer_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_outer_iter(self) -> iter::AxisIter<'a, A, D::Smaller> + where D: RemoveAxis + { + AxisIter::new(self.into_view(), Axis(0)) + } + + /// Convert into an indexed iterator. + /// + /// Unlike [ArrayRef::indexed_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_indexed_iter(self) -> iter::IndexedIter<'a, A, D> + { + iter::IndexedIter::new(self.into_view().into_elements_base()) + } + + /// Convert into an iterator over an `axis`. + /// + /// Unlike [ArrayRef::axis_iter], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_iter(self, axis: Axis) -> iter::AxisIter<'a, A, D::Smaller> + where D: RemoveAxis + { + AxisIter::new(self.into_view(), axis) + } + + /// Convert into an iterator over an `axis` by chunks. + /// + /// Unlike [`ArrayRef::axis_chunks_iter`], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_chunks_iter(self, axis: Axis, chunk_size: usize) -> iter::AxisChunksIter<'a, A, D> + where D: RemoveAxis { - IterMut::new(self) + iter::AxisChunksIter::new(self.into_view(), axis, chunk_size) } - /// Return an outer iterator for this view. - #[doc(hidden)] // not official - #[deprecated(note = "This method will be replaced.")] - pub fn into_outer_iter(self) -> iter::AxisIterMut<'a, A, D::Smaller> + /// Convert into an outer iterator for this view. + /// + /// Unlike [ArrayRef::outer_iter_mut], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_outer_iter_mut(self) -> iter::AxisIterMut<'a, A, D::Smaller> where D: RemoveAxis { AxisIterMut::new(self, Axis(0)) } + + /// Convert into an indexed iterator. + /// + /// Unlike [ArrayRef::indexed_iter_mut], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_indexed_iter_mut(self) -> iter::IndexedIterMut<'a, A, D> + { + iter::IndexedIterMut::new(self.into_elements_base()) + } + + /// Convert into an iterator over an `axis`. + /// + /// Unlike [ArrayRef::axis_iter_mut], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_iter_mut(self, axis: Axis) -> iter::AxisIterMut<'a, A, D::Smaller> + where D: RemoveAxis + { + AxisIterMut::new(self, axis) + } + + /// Convert into an iterator over an `axis` by chunks. + /// + /// Unlike [`ArrayRef::axis_chunks_iter_mut`], this methods preserves the lifetime of the data, + /// not the view itself. + pub fn into_axis_chunks_iter_mut(self, axis: Axis, chunk_size: usize) -> iter::AxisChunksIterMut<'a, A, D> + where D: RemoveAxis + { + iter::AxisChunksIterMut::new(self, axis, chunk_size) + } } diff --git a/src/lib.rs b/src/lib.rs index 77bdc9313..3efb378ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,7 +150,7 @@ pub use crate::order::Order; pub use crate::slice::{MultiSliceArg, NewAxis, Slice, SliceArg, SliceInfo, SliceInfoElem, SliceNextDim}; use crate::iterators::Baseiter; -use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut}; +use crate::iterators::{ElementsBase, ElementsBaseMut}; pub use crate::arraytraits::AsArray; pub use crate::linalg_traits::LinalgScalar; From 744ce5624c0bb772263e4c71786bdf7059aaa2f9 Mon Sep 17 00:00:00 2001 From: akern40 Date: Wed, 21 May 2025 23:25:38 -0400 Subject: [PATCH 13/54] Fixes dependabot alerts (#1512) Specifically, deals with CVE-2025-4574, GHSA-4fcv-w3qc-ppgg, and GHSA-255r-3prx-mf99 by updating the lockfile and one set of dependencies for testing serialization. --- Cargo.lock | 27 +++++++++++++++++---------- crates/serialization-tests/Cargo.toml | 7 ++++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1a513a74..a4c37d21b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,9 +173,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -593,9 +593,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags", "cfg-if", @@ -625,9 +625,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -641,6 +641,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -822,19 +828,20 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.10" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" +checksum = "bddb316f4b9cae1a3e89c02f1926d557d1142d0d2e684b038c11c1b77705229a" dependencies = [ "byteorder", "num-traits", + "paste", ] [[package]] name = "rmp-serde" -version = "0.14.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" dependencies = [ "byteorder", "rmp", diff --git a/crates/serialization-tests/Cargo.toml b/crates/serialization-tests/Cargo.toml index be7c4c17b..4ad165f39 100644 --- a/crates/serialization-tests/Cargo.toml +++ b/crates/serialization-tests/Cargo.toml @@ -18,7 +18,8 @@ ron = { version = "0.8.1" } [dev-dependencies] serde_json = { version = "1.0.40" } +# >=0.8.11 to avoid rmp-serde security vulnerability +# <0.8.14 to allows MSRV 1.64.0 +rmp = { version = ">=0.8.11,<0.8.14" } # Old version to work with Rust 1.64+ -rmp = { version = "=0.8.10" } -# Old version to work with Rust 1.64+ -rmp-serde = { version = "0.14" } +rmp-serde = { version = ">=1.1.1" } From 60a513b3a372b91d0943f7fab1e7a419ca57f48d Mon Sep 17 00:00:00 2001 From: Waterdragen <96000090+Waterdragen@users.noreply.github.com> Date: Fri, 23 May 2025 04:01:31 +0800 Subject: [PATCH 14/54] Add more element-wise math functions for floats (#1507) --- src/numeric/impl_float_maths.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/numeric/impl_float_maths.rs b/src/numeric/impl_float_maths.rs index 7012a8b93..358d57cf3 100644 --- a/src/numeric/impl_float_maths.rs +++ b/src/numeric/impl_float_maths.rs @@ -103,12 +103,16 @@ where fn exp /// `2^x` of each element. fn exp2 + /// `e^x - 1` of each element. + fn exp_m1 /// Natural logarithm of each element. fn ln /// Base 2 logarithm of each element. fn log2 /// Base 10 logarithm of each element. fn log10 + /// `ln(1 + x)` of each element. + fn ln_1p /// Cubic root of each element. fn cbrt /// Sine of each element (in radians). @@ -117,6 +121,24 @@ where fn cos /// Tangent of each element (in radians). fn tan + /// Arcsine of each element (return in radians). + fn asin + /// Arccosine of each element (return in radians). + fn acos + /// Arctangent of each element (return in radians). + fn atan + /// Hyperbolic sine of each element. + fn sinh + /// Hyperbolic cosine of each element. + fn cosh + /// Hyperbolic tangent of each element. + fn tanh + /// Inverse hyperbolic sine of each element. + fn asinh + /// Inverse hyperbolic cosine of each element. + fn acosh + /// Inverse hyperbolic tangent of each element. + fn atanh /// Converts radians to degrees for each element. fn to_degrees /// Converts degrees to radians for each element. @@ -133,6 +155,8 @@ where fn log(A) /// The positive difference between given number and each element. fn abs_sub(A) + /// Length of the hypotenuse of a right-angle triangle of each element + fn hypot(A) } /// Square (two powers) of each element. From 3ce0134250162967cad8a9f637dd91121733ebed Mon Sep 17 00:00:00 2001 From: akern40 Date: Mon, 26 May 2025 14:30:30 -0400 Subject: [PATCH 15/54] Updates to Edition 2021 (#1513) Simple update, just an edition change in Cargo.toml and a run of `cargo update` with an MSRV-aware resolver. --- Cargo.lock | 142 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4c37d21b..057a36d03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "approx" @@ -49,9 +49,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ "shlex", ] @@ -240,9 +240,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -302,9 +302,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -313,14 +313,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "idna_mapping" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5422cc5bc64289a77dbb45e970b86b5e9a04cb500abc7240505aedc1bf40f38" +checksum = "11c13906586a4b339310541a274dd927aff6fcbb5b8e3af90634c4b31681c792" dependencies = [ "unicode-joining-type", ] @@ -377,15 +377,15 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -400,21 +400,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "num_cpus", @@ -431,9 +431,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -491,7 +491,7 @@ version = "0.15.0" dependencies = [ "ndarray", "quickcheck", - "rand 0.9.0", + "rand 0.9.1", "rand_distr", "rand_isaac", ] @@ -554,7 +554,7 @@ dependencies = [ "num-complex", "num-traits", "openblas-src", - "rand 0.9.0", + "rand 0.9.1", "rand_distr", ] @@ -688,9 +688,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -713,6 +713,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -724,13 +730,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core 0.9.3", - "zerocopy", ] [[package]] @@ -749,7 +754,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -758,7 +763,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -768,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.9.0", + "rand 0.9.1", ] [[package]] @@ -808,9 +813,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -821,7 +826,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -839,9 +844,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +checksum = "938a142ab806f18b88a97b0dea523d39e0fd730a064b035726adcfc58a8a5188" dependencies = [ "byteorder", "rmp", @@ -862,9 +867,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -897,9 +902,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "ryu" @@ -991,15 +999,15 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1019,12 +1027,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1108,9 +1116,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-joining-type" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5" +checksum = "d8d00a78170970967fdb83f9d49b92f959ab2bb829186b113e4f4604ad98e180" [[package]] name = "unicode-normalization" @@ -1167,9 +1175,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -1315,9 +1323,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] @@ -1334,20 +1342,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 98326a598..14226986e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ndarray" version = "0.16.1" -edition = "2018" +edition = "2021" rust-version = "1.64" authors = [ "Ulrik Sverdrup \"bluss\"", From 62a0ebb8e31a3cc72a2d6a072030df31f45334ba Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 23 Sep 2025 19:36:13 -0400 Subject: [PATCH 16/54] Fix docs and miri tests (#1523) * Fixes some broken references in the docs * Raise allowable epsilon errors on logspace and geomspace For some reason, when Miri runs, we get higher floating point error on these methods. --- src/geomspace.rs | 8 ++++---- src/impl_constructors.rs | 8 ++++---- src/impl_methods.rs | 4 ++-- src/logspace.rs | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/geomspace.rs b/src/geomspace.rs index 0ac91f529..26a44f82e 100644 --- a/src/geomspace.rs +++ b/src/geomspace.rs @@ -117,16 +117,16 @@ mod tests use approx::assert_abs_diff_eq; let array: Array1<_> = geomspace(1e0, 1e3, 4).unwrap().collect(); - assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); + assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-11); let array: Array1<_> = geomspace(1e3, 1e0, 4).unwrap().collect(); - assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0]), epsilon = 1e-12); + assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0]), epsilon = 1e-11); let array: Array1<_> = geomspace(-1e3, -1e0, 4).unwrap().collect(); - assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); + assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-11); let array: Array1<_> = geomspace(-1e0, -1e3, 4).unwrap().collect(); - assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3]), epsilon = 1e-12); + assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3]), epsilon = 1e-11); } #[test] diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index c1e5b1b8b..071c7fe49 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -140,10 +140,10 @@ where S: DataOwned /// use ndarray::{Array, arr1}; /// /// let array = Array::logspace(10.0, 0.0, 3.0, 4); - /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3])); + /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); /// /// let array = Array::logspace(-10.0, 3.0, 0.0, 4); - /// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0])); + /// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); /// # } /// ``` #[cfg(feature = "std")] @@ -171,10 +171,10 @@ where S: DataOwned /// use ndarray::{Array, arr1}; /// /// let array = Array::geomspace(1e0, 1e3, 4)?; - /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); + /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-11); /// /// let array = Array::geomspace(-1e3, -1e0, 4)?; - /// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); + /// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-11); /// # } /// # Some(()) /// # } diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 7291f07cb..453cc05b3 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -1435,7 +1435,7 @@ impl ArrayRef /// The windows are all distinct overlapping views of size `window_size` /// that fit into the array's shape. /// - /// This is essentially equivalent to [`.windows_with_stride()`] with unit stride. + /// This is essentially equivalent to [`ArrayRef::windows_with_stride()`] with unit stride. #[track_caller] pub fn windows(&self, window_size: E) -> Windows<'_, A, D> where E: IntoDimension @@ -2170,7 +2170,7 @@ where /// **Panics** if shapes are incompatible. /// /// *This method is obsolete, because it is inflexible in how logical order - /// of the array is handled. See [`.to_shape()`].* + /// of the array is handled. See [`ArrayRef::to_shape()`].* /// /// ``` /// use ndarray::{rcarr1, rcarr2}; diff --git a/src/logspace.rs b/src/logspace.rs index 6f8de885d..463012018 100644 --- a/src/logspace.rs +++ b/src/logspace.rs @@ -111,16 +111,16 @@ mod tests use approx::assert_abs_diff_eq; let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); - assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3])); + assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); - assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0])); + assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0]), epsilon = 1e-12); let array: Array1<_> = logspace(-10.0, 3.0, 0.0, 4).collect(); - assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0])); + assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); let array: Array1<_> = logspace(-10.0, 0.0, 3.0, 4).collect(); - assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3])); + assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3]), epsilon = 1e-12); } #[test] From 07bedad5bf53ceee94939e51bf7b01ce1978ecb3 Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 23 Sep 2025 19:51:01 -0400 Subject: [PATCH 17/54] Moves scripts and CI/CD to nextest (#1514) --- .config/nextest.toml | 5 +++++ .github/workflows/ci.yaml | 8 +++++--- scripts/all-tests.sh | 14 +++++++------- scripts/blas-integ-tests.sh | 4 ++-- scripts/miri-tests.sh | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 .config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000..6a109e171 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,5 @@ +[profile.ci] +fail-fast = false + +[profile.ci.junit] +path = "junit.xml" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 13fb9d0d6..adcf8cb59 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -101,6 +101,7 @@ jobs: toolchain: ${{ matrix.rust }} - uses: rui314/setup-mold@v1 - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - name: Install openblas run: sudo apt-get install libopenblas-dev gfortran - run: ./scripts/all-tests.sh "$FEATURES" ${{ matrix.rust }} @@ -116,10 +117,9 @@ jobs: toolchain: ${{ needs.pass-msrv.outputs.BLAS_MSRV }} - uses: rui314/setup-mold@v1 - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - name: Install openblas run: sudo apt-get install libopenblas-dev gfortran - - run: cargo tree -p blas-tests -i openblas-src -F blas-tests/openblas-system - - run: cargo tree -p blas-tests -i openblas-build -F blas-tests/openblas-system - run: ./scripts/blas-integ-tests.sh $BLAS_MSRV miri: @@ -131,6 +131,7 @@ jobs: with: components: miri - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - run: ./scripts/miri-tests.sh cross_test: @@ -167,9 +168,10 @@ jobs: with: toolchain: nightly - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - name: Install cargo-careful run: cargo install cargo-careful - - run: cargo careful test -Zcareful-sanitizer --features="$FEATURES" + - run: cargo careful nextest run -Zcareful-sanitizer --features="$FEATURES" docs: #if: ${{ github.event_name == 'merge_group' }} diff --git a/scripts/all-tests.sh b/scripts/all-tests.sh index 612a7d758..4135ebeb8 100755 --- a/scripts/all-tests.sh +++ b/scripts/all-tests.sh @@ -12,23 +12,23 @@ QC_FEAT=--features=ndarray-rand/quickcheck cargo build -v --no-default-features # ndarray with no features -cargo test -p ndarray -v --no-default-features +cargo nextest run -p ndarray -v --no-default-features # ndarray with no_std-compatible features -cargo test -p ndarray -v --no-default-features --features approx +cargo nextest run -p ndarray -v --no-default-features --features approx # all with features -cargo test -v --features "$FEATURES" $QC_FEAT +cargo nextest run -v --features "$FEATURES" $QC_FEAT # all with features and release (ignore test crates which is already optimized) -cargo test -v -p ndarray -p ndarray-rand --release --features "$FEATURES" $QC_FEAT --lib --tests +cargo nextest run -v -p ndarray -p ndarray-rand --release --features "$FEATURES" $QC_FEAT --lib --tests # BLAS tests -cargo test -p ndarray --lib -v --features blas -cargo test -p blas-mock-tests -v +cargo nextest run -p ndarray --lib -v --features blas +cargo nextest run -p blas-mock-tests -v if [[ -z "${MSRV}" ]] && [ "$CHANNEL" != "$MSRV" ]; then ./scripts/blas-integ-tests.sh "$FEATURES" $CHANNEL fi # Examples -cargo test --examples +cargo nextest run --examples # Benchmarks ([ "$CHANNEL" != "nightly" ] || cargo bench --no-run --verbose --features "$FEATURES") diff --git a/scripts/blas-integ-tests.sh b/scripts/blas-integ-tests.sh index 3d769e0af..3a6258d77 100755 --- a/scripts/blas-integ-tests.sh +++ b/scripts/blas-integ-tests.sh @@ -4,5 +4,5 @@ set -x set -e # BLAS tests -cargo test -p blas-tests -v --features blas-tests/openblas-system -cargo test -p numeric-tests -v --features numeric-tests/test_blas +cargo nextest run -p blas-tests -v --features blas-tests/openblas-system +cargo nextest run -p numeric-tests -v --features numeric-tests/test_blas diff --git a/scripts/miri-tests.sh b/scripts/miri-tests.sh index 0100f3e6a..1f291bade 100755 --- a/scripts/miri-tests.sh +++ b/scripts/miri-tests.sh @@ -15,4 +15,4 @@ RUSTFLAGS="-Zrandomize-layout" # General tests # Note that we exclude blas feature because Miri can't do cblas_gemm -cargo miri test -v -p ndarray -p ndarray-rand --features approx,serde +cargo miri nextest run -v -p ndarray -p ndarray-rand --features approx,serde From a141f375597ae7f596e8ea6d67245d370951bc80 Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 23 Sep 2025 20:04:26 -0400 Subject: [PATCH 18/54] Remove as many `feature = "std"` gates in tests as possible (#1522) --- tests/array.rs | 26 +++++++++++--------------- tests/broadcast.rs | 21 +++++++++------------ tests/dimension.rs | 3 +-- tests/iterator_chunks.rs | 3 +-- tests/iterators.rs | 25 +++++++++++-------------- tests/ixdyn.rs | 3 +-- tests/oper.rs | 12 ++++++------ 7 files changed, 40 insertions(+), 53 deletions(-) diff --git a/tests/array.rs b/tests/array.rs index 3d6fa6715..6512043b2 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -68,17 +68,16 @@ fn arrayviewmut_shrink_lifetime<'a, 'b: 'a>(view: ArrayViewMut1<'b, f64>) -> Arr } #[test] -#[cfg(feature = "std")] fn test_mat_mul() { // smoke test, a big matrix multiplication of uneven size let (n, m) = (45, 33); - let a = ArcArray::linspace(0., ((n * m) - 1) as f32, n as usize * m as usize) + let a = Array::from_iter(0..(n * m)) .into_shape_with_order((n, m)) .unwrap(); - let b = ArcArray::eye(m); + let b = Array::eye(m); assert_eq!(a.dot(&b), a); - let c = ArcArray::eye(n); + let c = Array::eye(n); assert_eq!(c.dot(&a), a); } @@ -692,21 +691,20 @@ fn test_cow_shrink() } #[test] -#[cfg(feature = "std")] fn test_sub() { - let mat = ArcArray::linspace(0., 15., 16) + let mat = Array::from_iter(0..16) .into_shape_with_order((2, 4, 2)) .unwrap(); let s1 = mat.index_axis(Axis(0), 0); let s2 = mat.index_axis(Axis(0), 1); assert_eq!(s1.shape(), &[4, 2]); assert_eq!(s2.shape(), &[4, 2]); - let n = ArcArray::linspace(8., 15., 8) + let n = Array::from_iter(8..16) .into_shape_with_order((4, 2)) .unwrap(); assert_eq!(n, s2); - let m = ArcArray::from(vec![2., 3., 10., 11.]) + let m = Array::from(vec![2, 3, 10, 11]) .into_shape_with_order((2, 2)) .unwrap(); assert_eq!(m, mat.index_axis(Axis(1), 1)); @@ -714,10 +712,9 @@ fn test_sub() #[should_panic] #[test] -#[cfg(feature = "std")] fn test_sub_oob_1() { - let mat = ArcArray::linspace(0., 15., 16) + let mat = Array::from_iter(0..16) .into_shape_with_order((2, 4, 2)) .unwrap(); mat.index_axis(Axis(0), 2); @@ -1845,7 +1842,6 @@ fn scalar_ops() } #[test] -#[cfg(feature = "std")] fn split_at() { let mut a = arr2(&[[1., 2.], [3., 4.]]); @@ -1864,7 +1860,7 @@ fn split_at() } assert_eq!(a, arr2(&[[1., 5.], [8., 4.]])); - let b = ArcArray::linspace(0., 59., 60) + let b = ArcArray::from_iter(0..60) .into_shape_with_order((3, 4, 5)) .unwrap(); @@ -1874,9 +1870,9 @@ fn split_at() assert_eq!( left, arr3(&[ - [[0., 1.], [5., 6.], [10., 11.], [15., 16.]], - [[20., 21.], [25., 26.], [30., 31.], [35., 36.]], - [[40., 41.], [45., 46.], [50., 51.], [55., 56.]] + [[0, 1], [5, 6], [10, 11], [15, 16]], + [[20, 21], [25, 26], [30, 31], [35, 36]], + [[40, 41], [45, 46], [50, 51], [55, 56]] ]) ); diff --git a/tests/broadcast.rs b/tests/broadcast.rs index 288ccb38a..eda9babf6 100644 --- a/tests/broadcast.rs +++ b/tests/broadcast.rs @@ -1,21 +1,20 @@ use ndarray::prelude::*; #[test] -#[cfg(feature = "std")] fn broadcast_1() { let a_dim = Dim([2, 4, 2, 2]); let b_dim = Dim([2, 1, 2, 1]); - let a = ArcArray::linspace(0., 1., a_dim.size()) + let a = Array::from_iter(0..a_dim.size()) .into_shape_with_order(a_dim) .unwrap(); - let b = ArcArray::linspace(0., 1., b_dim.size()) + let b = Array::from_iter(0..b_dim.size()) .into_shape_with_order(b_dim) .unwrap(); assert!(b.broadcast(a.dim()).is_some()); let c_dim = Dim([2, 1]); - let c = ArcArray::linspace(0., 1., c_dim.size()) + let c = Array::from_iter(0..c_dim.size()) .into_shape_with_order(c_dim) .unwrap(); assert!(c.broadcast(1).is_none()); @@ -26,7 +25,7 @@ fn broadcast_1() assert!(c.broadcast((32, 1, 2)).is_none()); /* () can be broadcast to anything */ - let z = ArcArray::::zeros(()); + let z = Array::::zeros(()); assert!(z.broadcast(()).is_some()); assert!(z.broadcast(1).is_some()); assert!(z.broadcast(3).is_some()); @@ -34,32 +33,30 @@ fn broadcast_1() } #[test] -#[cfg(feature = "std")] fn test_add() { let a_dim = Dim([2, 4, 2, 2]); let b_dim = Dim([2, 1, 2, 1]); - let mut a = ArcArray::linspace(0.0, 1., a_dim.size()) + let mut a = Array::from_iter(0..a_dim.size()) .into_shape_with_order(a_dim) .unwrap(); - let b = ArcArray::linspace(0.0, 1., b_dim.size()) + let b = Array::from_iter(0..b_dim.size()) .into_shape_with_order(b_dim) .unwrap(); a += &b; - let t = ArcArray::from_elem((), 1.0f32); + let t = Array::from_elem((), 1); a += &t; } #[test] #[should_panic] -#[cfg(feature = "std")] fn test_add_incompat() { let a_dim = Dim([2, 4, 2, 2]); - let mut a = ArcArray::linspace(0.0, 1., a_dim.size()) + let mut a = Array::from_iter(0..a_dim.size()) .into_shape_with_order(a_dim) .unwrap(); - let incompat = ArcArray::from_elem(3, 1.0f32); + let incompat = Array::from_elem(3, 1); a += &incompat; } diff --git a/tests/dimension.rs b/tests/dimension.rs index fe53d96b3..53f204c6b 100644 --- a/tests/dimension.rs +++ b/tests/dimension.rs @@ -324,7 +324,6 @@ fn test_array_view() #[test] #[cfg_attr(miri, ignore)] // Very slow on CI/CD machines -#[cfg(feature = "std")] #[allow(clippy::cognitive_complexity)] fn test_all_ndindex() { @@ -334,7 +333,7 @@ fn test_all_ndindex() for &rev in &[false, true] { // rev is for C / F order let size = $($i *)* 1; - let mut a = Array::linspace(0., (size - 1) as f64, size); + let mut a = Array::from_iter(0..size); if rev { a = a.reversed_axes(); } diff --git a/tests/iterator_chunks.rs b/tests/iterator_chunks.rs index 79b5403ef..c16d8bc81 100644 --- a/tests/iterator_chunks.rs +++ b/tests/iterator_chunks.rs @@ -6,11 +6,10 @@ use ndarray::prelude::*; #[test] -#[cfg(feature = "std")] fn chunks() { use ndarray::NdProducer; - let a = >::linspace(1., 100., 10 * 10) + let a = Array1::from_iter(0..100) .into_shape_with_order((10, 10)) .unwrap(); diff --git a/tests/iterators.rs b/tests/iterators.rs index bdfd3ee50..9890e05a7 100644 --- a/tests/iterators.rs +++ b/tests/iterators.rs @@ -22,15 +22,14 @@ macro_rules! assert_panics { } #[test] -#[cfg(feature = "std")] fn double_ended() { - let a = ArcArray::linspace(0., 7., 8); + let a = Array::from_iter(0..8); let mut it = a.iter().cloned(); - assert_eq!(it.next(), Some(0.)); - assert_eq!(it.next_back(), Some(7.)); - assert_eq!(it.next(), Some(1.)); - assert_eq!(it.rev().last(), Some(2.)); + assert_eq!(it.next(), Some(0)); + assert_eq!(it.next_back(), Some(7)); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.rev().last(), Some(2)); assert_equal(aview1(&[1, 2, 3]), &[1, 2, 3]); assert_equal(aview1(&[1, 2, 3]).into_iter().rev(), [1, 2, 3].iter().rev()); } @@ -82,7 +81,7 @@ fn iter_size_hint() #[cfg(feature = "std")] fn indexed() { - let a = ArcArray::linspace(0., 7., 8); + let a = Array::from_iter(0..8); for (i, elt) in a.indexed_iter() { assert_eq!(i, *elt as usize); } @@ -100,7 +99,6 @@ fn indexed() } #[test] -#[cfg(feature = "std")] fn as_slice() { use ndarray::Data; @@ -118,7 +116,7 @@ fn as_slice() assert_equal(v.iter(), slc); } - let a = ArcArray::linspace(0., 7., 8); + let a = Array::from_iter(0..8); let a = a.into_shape_with_order((2, 4, 1)).unwrap(); assert_slice_correct(&a); @@ -546,7 +544,6 @@ fn axis_iter_mut_zip_partially_consumed_discontiguous() } #[test] -#[cfg(feature = "std")] fn axis_chunks_iter_corner_cases() { // examples provided by @bluss in PR #65 @@ -554,7 +551,7 @@ fn axis_chunks_iter_corner_cases() // and enable checking if no pointer offsetting is out of bounds. However // checking the absence of of out of bounds offsetting cannot (?) be // done automatically, so one has to launch this test in a debugger. - let a = ArcArray::::linspace(0., 7., 8) + let a = Array::from_iter(0..8) .into_shape_with_order((8, 1)) .unwrap(); let it = a.axis_chunks_iter(Axis(0), 4); @@ -564,9 +561,9 @@ fn axis_chunks_iter_corner_cases() assert_equal(it, vec![a.view()]); let it = a.axis_chunks_iter(Axis(0), 3); assert_equal(it, vec![ - array![[7.], [6.], [5.]], - array![[4.], [3.], [2.]], - array![[1.], [0.]], + array![[7], [6], [5]], + array![[4], [3], [2]], + array![[1], [0]], ]); let b = ArcArray::::zeros((8, 2)); diff --git a/tests/ixdyn.rs b/tests/ixdyn.rs index 05f123ba1..f14df9f0e 100644 --- a/tests/ixdyn.rs +++ b/tests/ixdyn.rs @@ -163,12 +163,11 @@ fn test_0_add_broad() } #[test] -#[cfg(feature = "std")] fn test_into_dimension() { use ndarray::{Ix0, Ix1, Ix2, IxDyn}; - let a = Array::linspace(0., 41., 6 * 7) + let a = Array::from_iter(0..42) .into_shape_with_order((6, 7)) .unwrap(); let a2 = a.clone().into_shape_with_order(IxDyn(&[6, 7])).unwrap(); diff --git a/tests/oper.rs b/tests/oper.rs index 401913e2b..0751c0c13 100644 --- a/tests/oper.rs +++ b/tests/oper.rs @@ -1,7 +1,6 @@ #![allow( clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names )] -#![cfg(feature = "std")] use ndarray::linalg::general_mat_mul; use ndarray::linalg::kron; use ndarray::prelude::*; @@ -133,7 +132,7 @@ where #[test] fn dot_product() { - let a = Array::range(0., 69., 1.); + let a = Array::from_iter((0..69).map(|x| x as f32)); let b = &a * 2. - 7.; let dot = 197846.; assert_abs_diff_eq!(a.dot(&b), reference_dot(&a, &b), epsilon = 1e-5); @@ -172,7 +171,7 @@ fn dot_product() #[test] fn dot_product_0() { - let a = Array::range(0., 69., 1.); + let a = Array::from_iter((0..69).map(|x| x as f32)); let x = 1.5; let b = aview0(&x); let b = b.broadcast(a.dim()).unwrap(); @@ -194,7 +193,7 @@ fn dot_product_0() fn dot_product_neg_stride() { // test that we can dot with negative stride - let a = Array::range(0., 69., 1.); + let a = Array::from_iter((0..69).map(|x| x as f32)); let b = &a * 2. - 7.; for stride in -10..0 { // both negative @@ -213,7 +212,7 @@ fn dot_product_neg_stride() #[test] fn fold_and_sum() { - let a = Array::linspace(0., 127., 128) + let a = Array::from_iter((0..128).map(|x| x as f32)) .into_shape_with_order((8, 16)) .unwrap(); assert_abs_diff_eq!(a.fold(0., |acc, &x| acc + x), a.sum(), epsilon = 1e-5); @@ -255,7 +254,8 @@ fn fold_and_sum() #[test] fn product() { - let a = Array::linspace(0.5, 2., 128) + let step = (2. - 0.5) / 127.; + let a = Array::from_iter((0..128).map(|i| 0.5 + step * (i as f64))) .into_shape_with_order((8, 16)) .unwrap(); assert_abs_diff_eq!(a.fold(1., |acc, &x| acc * x), a.product(), epsilon = 1e-5); From aa135d4973ace22fca02e55b078381a02c8ee1ae Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 23 Sep 2025 20:36:00 -0400 Subject: [PATCH 19/54] Document `Array::zeros` with how to control the return type (#1524) --- src/impl_constructors.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index 071c7fe49..ded1bbf79 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -337,6 +337,20 @@ where /// Create an array with zeros, shape `shape`. /// + /// The element type is inferred; to control it, you can either specify + /// type of the returned array or use turbofish syntax in the function call: + /// ``` + /// use ndarray::{Array1, arr1}; + /// + /// // Specify f32 + /// let arr_f32: Array1 = Array1::zeros(3); + /// assert_eq!(arr_f32, arr1(&[0_f32, 0.0, 0.0])); + /// + /// // Specify i64 + /// let arr_i64 = Array1::::zeros(3); + /// assert_eq!(arr_i64, arr1(&[0_i64, 0, 0])); + /// ``` + /// /// **Panics** if the product of non-zero axis lengths overflows `isize`. pub fn zeros(shape: Sh) -> Self where From a44d71fe0ca9bd78e43b5b3827fef49efb6ba055 Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 23 Sep 2025 22:36:17 -0400 Subject: [PATCH 20/54] Fills missing documentation and adds warn(missing_docs) (#1525) --- src/dimension/broadcast.rs | 7 +++++++ src/dimension/conversion.rs | 3 +++ src/dimension/remove_axis.rs | 1 + src/lib.rs | 1 + src/linalg/impl_linalg.rs | 3 +++ src/shape_builder.rs | 13 +++++++++++++ src/zip/ndproducer.rs | 1 + 7 files changed, 29 insertions(+) diff --git a/src/dimension/broadcast.rs b/src/dimension/broadcast.rs index fb9fc1a0c..9e7474388 100644 --- a/src/dimension/broadcast.rs +++ b/src/dimension/broadcast.rs @@ -34,6 +34,13 @@ where Ok(out) } +/// A trait to specify when one dimension is strictly larger than another. +/// +/// Broadcasting two arrays together frequently requires typing the resultant +/// array has having a dimensionality equal to the maximum of the two input arrays. +/// This trait is what determines that typing. +/// +/// For example, `Ix1: DimMax`, but not vice-versa. pub trait DimMax { /// The resulting dimension type after broadcasting. diff --git a/src/dimension/conversion.rs b/src/dimension/conversion.rs index 0cf2e1296..cee8a2eb5 100644 --- a/src/dimension/conversion.rs +++ b/src/dimension/conversion.rs @@ -42,7 +42,10 @@ macro_rules! index_item { /// Argument conversion a dimension. pub trait IntoDimension { + /// The concrete type of the resultant dimension. type Dim: Dimension; + + /// Convert into a type that implements [`Dimension`]. fn into_dimension(self) -> Self::Dim; } diff --git a/src/dimension/remove_axis.rs b/src/dimension/remove_axis.rs index cbb039fc5..7ba3b5330 100644 --- a/src/dimension/remove_axis.rs +++ b/src/dimension/remove_axis.rs @@ -14,6 +14,7 @@ use crate::{Axis, Dim, Dimension, Ix, Ix0, Ix1}; /// removing one axis from *Self* gives smaller dimension *Smaller*. pub trait RemoveAxis: Dimension { + /// Remove the specified axis from a dimension. fn remove_axis(&self, axis: Axis) -> Self::Smaller; } diff --git a/src/lib.rs b/src/lib.rs index 3efb378ce..e11a8a5ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ #![cfg_attr(not(feature = "std"), no_std)] // Enable the doc_cfg nightly feature for including feature gate flags in the documentation #![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] //! The `ndarray` crate provides an *n*-dimensional container for general elements //! and for numerics. diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index 0bbc0b026..1e4eb80ee 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -164,6 +164,9 @@ pub trait Dot /// For two-dimensional arrays: a rectangular array. type Output; + /// Compute the dot product of two arrays. + /// + /// **Panics** if the arrays' shapes are not compatible. fn dot(&self, rhs: &Rhs) -> Self::Output; } diff --git a/src/shape_builder.rs b/src/shape_builder.rs index b9a4b0ab6..50c00b044 100644 --- a/src/shape_builder.rs +++ b/src/shape_builder.rs @@ -96,12 +96,22 @@ impl Strides /// `Array::from_shape_vec`. pub trait ShapeBuilder { + /// The type that captures the built shape's dimensionality. type Dim: Dimension; + + /// The type that captures the built shape's stride pattern. type Strides; + /// Turn into a contiguous-element [`Shape`]. fn into_shape_with_order(self) -> Shape; + + /// Convert into a Fortran-order (a.k.a., column-order) [`Shape`]. fn f(self) -> Shape; + + /// Set the order of the shape to either Fortran or non-Fortran. fn set_f(self, is_f: bool) -> Shape; + + /// Set the strides of the shape. fn strides(self, strides: Self::Strides) -> StrideShape; } @@ -213,7 +223,10 @@ where D: Dimension /// See for example [`.to_shape()`](crate::ArrayRef::to_shape). pub trait ShapeArg { + /// The type that captures the shape's dimensionality. type Dim: Dimension; + + /// Convert the argument into a shape and an [`Order`]. fn into_shape_and_order(self) -> (Self::Dim, Option); } diff --git a/src/zip/ndproducer.rs b/src/zip/ndproducer.rs index 82f3f43a7..de9d66979 100644 --- a/src/zip/ndproducer.rs +++ b/src/zip/ndproducer.rs @@ -16,6 +16,7 @@ pub trait IntoNdProducer type Item; /// Dimension type of the producer type Dim: Dimension; + /// The concrete type of the producer type Output: NdProducer; /// Convert the value into an `NdProducer`. fn into_producer(self) -> Self::Output; From 0d75035f2205440830e193cc26e4f0fcd9644124 Mon Sep 17 00:00:00 2001 From: akern40 Date: Mon, 13 Oct 2025 16:04:42 -0400 Subject: [PATCH 21/54] Add nextest to our latest dependencies CI check (#1526) Also, remove the `if: always()` on the conclusion step of CI so that it doesn't pop up when we just change the `latest-deps.yaml`. --- .github/workflows/ci.yaml | 1 - .github/workflows/latest-deps.yaml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index adcf8cb59..5bb03f81e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -200,7 +200,6 @@ jobs: - cross_test - cargo-careful - docs - if: always() runs-on: ubuntu-latest steps: - name: Result diff --git a/.github/workflows/latest-deps.yaml b/.github/workflows/latest-deps.yaml index 3b28169ec..52e61442f 100644 --- a/.github/workflows/latest-deps.yaml +++ b/.github/workflows/latest-deps.yaml @@ -35,6 +35,8 @@ jobs: uses: rui314/setup-mold@v1 - name: Setup Rust Cache uses: Swatinem/rust-cache@v2 + - name: Install nextest + uses: taiki-e/install-action@nextest - name: Install openblas run: sudo apt-get install libopenblas-dev gfortran - name: Ensure latest dependencies @@ -56,6 +58,8 @@ jobs: uses: rui314/setup-mold@v1 - name: Setup Rust Cache uses: Swatinem/rust-cache@v2 + - name: Install nextest + uses: taiki-e/install-action@nextest - name: Install openblas run: sudo apt-get install libopenblas-dev gfortran - name: Ensure latest dependencies From 8a39cadfe80e95b522616ed76d257225411be7d2 Mon Sep 17 00:00:00 2001 From: akern40 Date: Mon, 13 Oct 2025 22:36:33 -0400 Subject: [PATCH 22/54] Add a changelog for Version 0.17.0 (#1527) --- RELEASES.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 8b4786666..b94ef88f0 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,125 @@ +Version 0.17.0 (2025-10-13) +=========================== +Version 0.17.0 introduces a new **array reference type** — the preferred way to write functions and extension traits in `ndarray`. +This release is fully backwards-compatible but represents a major usability improvement. +The first section of this changelog explains the change in detail. + +It also includes numerous new methods, math functions, and internal improvements — all credited below. + +A New Way to Write Functions +------------- + +### TL;DR +`ndarray` 0.17.0 adds new reference types for writing functions and traits that work seamlessly with owned arrays and views. + +When writing functions that accept array arguments: +- **Use `&ArrayRef`** to read elements from any array. +- **Use `&mut ArrayRef`** to modify elements. +- **Use `&T where T: AsRef>`** to inspect shape/stride only. +- **Use `&mut T where T: AsMut>`** to modify shape/stride only. + +All existing function signatures continue to work; these new types are fully opt-in. + +### Background +ndarray has multiple ways to write functions that take arrays (a problem captured well in issue [#1059](https://github.com/rust-ndarray/ndarray/issues/1059)). +For example: +```rust +fn sum(a: ArrayView1) -> f64; +fn sum(a: &ArrayView1) -> f64; +fn sum(a: &Array1) -> f64; +``` +All of these work, but having several equivalent forms causes confusion. +The most general solution, writing generically over storage types: +```rust +fn sum(a: &ArrayBase) -> f64 +where S: Data; +``` +is powerful but verbose and often hard to read. +Version 0.17.0 introduces a new, simpler pattern that expresses the same flexibility more clearly. + +### Solution +Three new reference types make it easier to write functions that accept any kind of array while clearly expressing what kind of access (data or layout) they need. + +#### Reading / Writing Elements: `ArrayRef` +`ArrayRef` is the `Deref` target of `ArrayBase`. +It behaves like `&[T]` for `Vec`, giving access to elements and layout. +Mutability is expressed through the reference itself (`&` vs `&mut`), not through a trait bound or the type itself. +It is used as follows: +```rust +fn sum(a: &ArrayRef1) -> f64; +fn cumsum_mut(a: &mut ArrayRef1); +``` +(ArrayRef1 is available from the prelude.) + +#### Reading / Writing Shape: `LayoutRef` +LayoutRef lets functions view or modify shape/stride information without touching data. +This replaces verbose signatures like: +```rust +fn alter_view(a: &mut ArrayBase) +where S: Data; +``` +Use AsRef / AsMut for best compatibility: +```rust +fn alter_shape(a: &mut T) +where T: AsMut>; +``` +(Accepting a `LayoutRef` directly can cause unnecessary copies; see [#1440](https://github.com/rust-ndarray/ndarray/pull/1440).) + +#### Reading / Writing Unsafe Elements: `RawRef` +`RawRef` augments `RawArrayView` and `RawArrayViewMut` for power users needing unsafe element access (e.g. uninitialized buffers). +Like `LayoutRef`, it is best used via `AsRef` / `AsMut`. + +Added +----- +- A new "array reference" type by [@akern40](https://github.com/akern40) [#1440](https://github.com/rust-ndarray/ndarray/pull/1440) +- A `diff` method for calculating the difference between elements by [@johann-cm](https://github.com/johann-cm) [#1437](https://github.com/rust-ndarray/ndarray/pull/1437) +- A `partition` method for partially sorting an array by [@NewBornRustacean](https://github.com/NewBornRustacean) [#1498](https://github.com/rust-ndarray/ndarray/pull/1498) +- A `meshgrid` method for building regular grids of values by [@akern40](https://github.com/akern40) [#1477](https://github.com/rust-ndarray/ndarray/pull/1477) +- A `cumprod` method for cumulative products by [@NewBornRustacean](https://github.com/NewBornRustacean) [#1491](https://github.com/rust-ndarray/ndarray/pull/1491) +- More element-wise math functions for floats by [@Waterdragen](https://github.com/Waterdragen) [#1507](https://github.com/rust-ndarray/ndarray/pull/1507) + - Additions include `exp_m1`, `ln_1p`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, and `hypot` +- Dot product support for dynamic arrays by [@NewBornRustacean](https://github.com/NewBornRustacean) [#1483](https://github.com/rust-ndarray/ndarray/pull/1483) and [@akern40](https://github.com/akern40) [#1494](https://github.com/rust-ndarray/ndarray/pull/1494) +- An `axis_windows_with_stride` method for strided windows by [@goertzenator](https://github.com/goertzenator) [#1460](https://github.com/rust-ndarray/ndarray/pull/1460) +- In-place methods for permuting (`permute_axes`) and reversing (`reverse_axes`) axes by [@NewBornRustacean](https://github.com/NewBornRustacean) [#1505](https://github.com/rust-ndarray/ndarray/pull/1505) +- Adds `into_*_iter` functions as lifetime-preserving versions of into-iterator functionality by [@akern40](https://github.com/akern40) [#1510](https://github.com/rust-ndarray/ndarray/pull/1510) + +Changed +------- +- `remove_index` can now be called on views, in addition to owned arrays by [@akern40](https://github.com/akern40) + +Removed +------- +- Removed the `serde-1`, `test`, and `docs` feature flags; by [@akern40](https://github.com/akern40) [#1479](https://github.com/rust-ndarray/ndarray/pull/1479) + - Use `approx,serde,rayon` instead of `docs`. + - Use `serde` instead of `serde-1` + + +Fixed +----- +- `last_mut()` now guarantees that the underlying data is uniquely held by [@bluss](https://github.com/bluss) [#1429](https://github.com/rust-ndarray/ndarray/pull/1429) +- `ArrayView` is now covariant over lifetime by [@akern40](https://github.com/akern40) [#1480](https://github.com/rust-ndarray/ndarray/pull/1480), so that the following code now compiles +```rust +fn fn_cov<'a>(x: ArrayView1<'static, f64>) -> ArrayView1<'a, f64> { + x +} +``` + +Documentation +----- +- Filled missing documentation and adds warn(missing_docs) by [@akern40](https://github.com/akern40) +- Fixed a typo in the documentation of `select` by [@Drazhar](https://github.com/Drazhar) +- Fixed a typo in the documentation of `into_raw_vec_and_offset` by [@benliepert](https://github.com/benliepert) +- Documented `Array::zeros` with how to control the return type by [@akern40](https://github.com/akern40) + +Other +----- +- Expanded the gitignore file for IDEs by [@XXMA16](https://github.com/XXMA16) and [@akern40](https://github.com/akern40) +- Stabilized the MSRV to 1.64 by [@akern40](https://github.com/akern40) +- Switched to nextest for testing by [@akern40](https://github.com/akern40) +- Added Miri to CI by [@akern40](https://github.com/akern40) +- Smoothed out tests that can be run with `no_std` by [@akern40](https://github.com/akern40) +- Updated to Edition 2021 by [@akern40](https://github.com/akern40) + Version 0.16.1 (2024-08-14) =========================== From b4e76582d19bf756394eb6e5723889d8995e5306 Mon Sep 17 00:00:00 2001 From: akern40 Date: Mon, 13 Oct 2025 23:04:07 -0400 Subject: [PATCH 23/54] Update configurations for cargo-release (#1528) --- Cargo.toml | 2 +- crates/blas-mock-tests/Cargo.toml | 4 ++++ crates/blas-tests/Cargo.toml | 5 +++++ crates/ndarray-gen/Cargo.toml | 5 +++++ crates/numeric-tests/Cargo.toml | 4 ++++ crates/serialization-tests/Cargo.toml | 4 ++++ ndarray-rand/Cargo.toml | 1 - 7 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14226986e..bdac749c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,8 +109,8 @@ opt-level = 2 [profile.test.package.blas-tests] opt-level = 2 +# Config for cargo-release [package.metadata.release] -no-dev-version = true tag-name = "{{version}}" # Config specific to docs.rs diff --git a/crates/blas-mock-tests/Cargo.toml b/crates/blas-mock-tests/Cargo.toml index 39ef9cf99..f3f1cd559 100644 --- a/crates/blas-mock-tests/Cargo.toml +++ b/crates/blas-mock-tests/Cargo.toml @@ -16,3 +16,7 @@ cblas-sys = { workspace = true } ndarray = { workspace = true, features = ["approx", "blas"] } ndarray-gen = { workspace = true } itertools = { workspace = true } + +# Config for cargo-release +[package.metadata.release] +release = false diff --git a/crates/blas-tests/Cargo.toml b/crates/blas-tests/Cargo.toml index 08acc7fa5..4b0209ac5 100644 --- a/crates/blas-tests/Cargo.toml +++ b/crates/blas-tests/Cargo.toml @@ -35,3 +35,8 @@ netlib = ["blas-src", "blas-src/netlib"] netlib-system = ["blas-src", "blas-src/netlib", "netlib-src/system"] blis-system = ["blas-src", "blas-src/blis", "blis-src/system"] accelerate = ["blas-src", "blas-src/accelerate"] + +# Config for cargo-release +[package.metadata.release] +release = false + diff --git a/crates/ndarray-gen/Cargo.toml b/crates/ndarray-gen/Cargo.toml index 6818e4b65..33394ccfc 100644 --- a/crates/ndarray-gen/Cargo.toml +++ b/crates/ndarray-gen/Cargo.toml @@ -7,3 +7,8 @@ publish = false [dependencies] ndarray = { workspace = true, default-features = false } num-traits = { workspace = true } + +# Config for cargo-release +[package.metadata.release] +release = false + diff --git a/crates/numeric-tests/Cargo.toml b/crates/numeric-tests/Cargo.toml index 93a182e66..3e4014d25 100644 --- a/crates/numeric-tests/Cargo.toml +++ b/crates/numeric-tests/Cargo.toml @@ -27,3 +27,7 @@ num-complex = { workspace = true } [features] test_blas = ["ndarray/blas", "blas-src", "openblas-src"] + +# Config for cargo-release +[package.metadata.release] +release = false diff --git a/crates/serialization-tests/Cargo.toml b/crates/serialization-tests/Cargo.toml index 4ad165f39..7531fc978 100644 --- a/crates/serialization-tests/Cargo.toml +++ b/crates/serialization-tests/Cargo.toml @@ -23,3 +23,7 @@ serde_json = { version = "1.0.40" } rmp = { version = ">=0.8.11,<0.8.14" } # Old version to work with Rust 1.64+ rmp-serde = { version = ">=1.1.1" } + +# Config for cargo-release +[package.metadata.release] +release = false diff --git a/ndarray-rand/Cargo.toml b/ndarray-rand/Cargo.toml index 72b959020..3cafb552e 100644 --- a/ndarray-rand/Cargo.toml +++ b/ndarray-rand/Cargo.toml @@ -25,6 +25,5 @@ rand_isaac = "0.4.0" quickcheck = { workspace = true } [package.metadata.release] -no-dev-version = true tag-name = "ndarray-rand-{{version}}" From 6cd209bd02a0916b9c418ec4bfd81aa7addb8c4f Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 14 Oct 2025 17:58:14 -0400 Subject: [PATCH 24/54] Release ndarray 0.17.0 (#1529) --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- RELEASES.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 057a36d03..b82cbe288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,7 +457,7 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.16.1" +version = "0.17.0" dependencies = [ "approx", "cblas-sys", diff --git a/Cargo.toml b/Cargo.toml index bdac749c8..0223226db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ndarray" -version = "0.16.1" +version = "0.17.0" edition = "2021" rust-version = "1.64" authors = [ @@ -87,7 +87,7 @@ default-members = [ ] [workspace.dependencies] -ndarray = { version = "0.16", path = ".", default-features = false } +ndarray = { version = "0.17", path = ".", default-features = false } ndarray-rand = { path = "ndarray-rand" } ndarray-gen = { path = "crates/ndarray-gen" } diff --git a/RELEASES.md b/RELEASES.md index b94ef88f0..1f78a4ce7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.17.0 (2025-10-13) +Version 0.17.0 (2025-10-14) =========================== Version 0.17.0 introduces a new **array reference type** — the preferred way to write functions and extension traits in `ndarray`. This release is fully backwards-compatible but represents a major usability improvement. From e6bf80445525ded975cef8f456daad3142b09ccc Mon Sep 17 00:00:00 2001 From: Miikka Salminen Date: Sun, 19 Oct 2025 05:43:53 +0300 Subject: [PATCH 25/54] Mention the used rand version 0.9 in ndarray-rand (#1533) ndarray-rand crate on the main branch uses the workspace rand and rand_distr versions, which are 0.9 and 0.5 respectively. This commit documents those versions at ndarray-rand's lib.rs' module level. --- ndarray-rand/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ndarray-rand/src/lib.rs b/ndarray-rand/src/lib.rs index 795e246d4..8ee2cda75 100644 --- a/ndarray-rand/src/lib.rs +++ b/ndarray-rand/src/lib.rs @@ -12,7 +12,7 @@ //! //! ## Note //! -//! `ndarray-rand` depends on [`rand` 0.8][rand]. +//! `ndarray-rand` depends on [`rand` 0.9][rand]. //! //! [`rand`][rand] and [`rand_distr`][rand_distr] //! are re-exported as sub-modules, [`ndarray_rand::rand`](rand) @@ -20,8 +20,8 @@ //! You can use these submodules for guaranteed version compatibility or //! convenience. //! -//! [rand]: https://docs.rs/rand/0.8 -//! [rand_distr]: https://docs.rs/rand_distr/0.4 +//! [rand]: https://docs.rs/rand/0.9 +//! [rand_distr]: https://docs.rs/rand_distr/0.5 //! //! If you want to use a random number generator or distribution from another crate //! with `ndarray-rand`, you need to make sure that the other crate also depends on the From a3b3537788c799025fc7317a15869efbf7ec69e4 Mon Sep 17 00:00:00 2001 From: akern40 Date: Tue, 28 Oct 2025 21:55:22 -0400 Subject: [PATCH 26/54] Makes the reference types sound via a DST (#1532) The current implementation of ArrayRef and its cousins has them as sized types, which turns out to be a critical and unsound mistake. This PR is large, but its heart is small: change the ArrayRef implementation to be unsized. The approach this PR takes is to package the core array "metadata" - the pointer, dim, and strides - into a struct that can either be sized or unsized. This is done by appending a generic "_dst_control" field. For the "sized" version of the metadata, that field is a 0-length array. For the "unsized" version of the metadata, that sized field is a struct. This core type is private, so users cannot construct any other variants other than these two. We then put the sized version into the ArrayBase types, put the unsized version into the reference types, and perform an unsizing coercion to convert from one to the other. Because Rust has no (safe, supported) "resizing" coercion, this switch is irreversible. Sized types cannot be recovered from the unsized reference types. --- examples/functions_and_traits.rs | 14 +- scripts/all-tests.sh | 3 + src/arraytraits.rs | 19 +-- src/data_traits.rs | 29 ++-- src/free_functions.rs | 24 ++-- src/impl_clone.rs | 16 +-- src/impl_cow.rs | 8 +- src/impl_dyn.rs | 8 +- src/impl_internal_constructors.rs | 14 +- src/impl_methods.rs | 230 ++++++++++++++++-------------- src/impl_owned_array.rs | 42 +++--- src/impl_raw_views.rs | 52 +++---- src/impl_ref_types.rs | 62 ++++++-- src/impl_special_element_types.rs | 10 +- src/impl_views/constructors.rs | 2 +- src/impl_views/conversions.rs | 28 ++-- src/impl_views/indexing.rs | 6 +- src/iterators/chunks.rs | 12 +- src/iterators/into_iter.rs | 8 +- src/iterators/lanes.rs | 8 +- src/iterators/mod.rs | 14 +- src/iterators/windows.rs | 4 +- src/lib.rs | 80 ++++++++--- src/linalg/impl_linalg.rs | 34 ++--- src/macro_utils.rs | 20 ++- src/numeric/impl_numeric.rs | 8 +- src/tri.rs | 8 +- src/zip/mod.rs | 4 +- src/zip/ndproducer.rs | 24 ++-- 29 files changed, 457 insertions(+), 334 deletions(-) diff --git a/examples/functions_and_traits.rs b/examples/functions_and_traits.rs index dc8f73da4..56ec1f2e2 100644 --- a/examples/functions_and_traits.rs +++ b/examples/functions_and_traits.rs @@ -139,7 +139,7 @@ fn takes_rawref_mut(arr: &mut RawRef) /// Immutable, take a generic that implements `AsRef` to `RawRef` #[allow(dead_code)] fn takes_rawref_asref(_arr: &T) -where T: AsRef> +where T: AsRef> + ?Sized { takes_layout(_arr.as_ref()); takes_layout_asref(_arr.as_ref()); @@ -148,7 +148,7 @@ where T: AsRef> /// Mutable, take a generic that implements `AsMut` to `RawRef` #[allow(dead_code)] fn takes_rawref_asmut(_arr: &mut T) -where T: AsMut> +where T: AsMut> + ?Sized { takes_layout_mut(_arr.as_mut()); takes_layout_asmut(_arr.as_mut()); @@ -169,10 +169,16 @@ fn takes_layout_mut(_arr: &mut LayoutRef) {} /// Immutable, take a generic that implements `AsRef` to `LayoutRef` #[allow(dead_code)] -fn takes_layout_asref>, A, D>(_arr: &T) {} +fn takes_layout_asref(_arr: &T) +where T: AsRef> + ?Sized +{ +} /// Mutable, take a generic that implements `AsMut` to `LayoutRef` #[allow(dead_code)] -fn takes_layout_asmut>, A, D>(_arr: &mut T) {} +fn takes_layout_asmut(_arr: &mut T) +where T: AsMut> + ?Sized +{ +} fn main() {} diff --git a/scripts/all-tests.sh b/scripts/all-tests.sh index 4135ebeb8..f6c9b27a8 100755 --- a/scripts/all-tests.sh +++ b/scripts/all-tests.sh @@ -30,5 +30,8 @@ fi # Examples cargo nextest run --examples +# Doc tests +cargo test --doc + # Benchmarks ([ "$CHANNEL" != "nightly" ] || cargo bench --no-run --verbose --features "$FEATURES") diff --git a/src/arraytraits.rs b/src/arraytraits.rs index a34b1985e..da87e3a58 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -19,7 +19,6 @@ use std::{iter::FromIterator, slice}; use crate::imp_prelude::*; use crate::Arc; -use crate::LayoutRef; use crate::{ dimension, iter::{Iter, IterMut}, @@ -38,12 +37,14 @@ pub(crate) fn array_out_of_bounds() -> ! } #[inline(always)] -pub fn debug_bounds_check(_a: &LayoutRef, _index: &I) +pub fn debug_bounds_check(_a: &T, _index: &I) where D: Dimension, I: NdIndex, + T: AsRef> + ?Sized, { - debug_bounds_check!(_a, *_index); + let _layout_ref = _a.as_ref(); + debug_bounds_check_ref!(_layout_ref, *_index); } /// Access the element at **index**. @@ -59,11 +60,11 @@ where #[inline] fn index(&self, index: I) -> &Self::Output { - debug_bounds_check!(self, index); + debug_bounds_check_ref!(self, index); unsafe { - &*self.ptr.as_ptr().offset( + &*self._ptr().as_ptr().offset( index - .index_checked(&self.dim, &self.strides) + .index_checked(self._dim(), self._strides()) .unwrap_or_else(|| array_out_of_bounds()), ) } @@ -81,11 +82,11 @@ where #[inline] fn index_mut(&mut self, index: I) -> &mut A { - debug_bounds_check!(self, index); + debug_bounds_check_ref!(self, index); unsafe { &mut *self.as_mut_ptr().offset( index - .index_checked(&self.dim, &self.strides) + .index_checked(self._dim(), self._strides()) .unwrap_or_else(|| array_out_of_bounds()), ) } @@ -581,7 +582,7 @@ where D: Dimension { let data = OwnedArcRepr(Arc::new(arr.data)); // safe because: equivalent unmoved data, ptr and dims remain valid - unsafe { ArrayBase::from_data_ptr(data, arr.layout.ptr).with_strides_dim(arr.layout.strides, arr.layout.dim) } + unsafe { ArrayBase::from_data_ptr(data, arr.parts.ptr).with_strides_dim(arr.parts.strides, arr.parts.dim) } } } diff --git a/src/data_traits.rs b/src/data_traits.rs index 4266e4017..a0b33ea12 100644 --- a/src/data_traits.rs +++ b/src/data_traits.rs @@ -251,7 +251,7 @@ where A: Clone if Arc::get_mut(&mut self_.data.0).is_some() { return; } - if self_.layout.dim.size() <= self_.data.0.len() / 2 { + if self_.parts.dim.size() <= self_.data.0.len() / 2 { // Clone only the visible elements if the current view is less than // half of backing data. *self_ = self_.to_owned().into_shared(); @@ -260,13 +260,13 @@ where A: Clone let rcvec = &mut self_.data.0; let a_size = mem::size_of::() as isize; let our_off = if a_size != 0 { - (self_.layout.ptr.as_ptr() as isize - rcvec.as_ptr() as isize) / a_size + (self_.parts.ptr.as_ptr() as isize - rcvec.as_ptr() as isize) / a_size } else { 0 }; let rvec = Arc::make_mut(rcvec); unsafe { - self_.layout.ptr = rvec.as_nonnull_mut().offset(our_off); + self_.parts.ptr = rvec.as_nonnull_mut().offset(our_off); } } @@ -287,7 +287,7 @@ unsafe impl Data for OwnedArcRepr let data = Arc::try_unwrap(self_.data.0).ok().unwrap(); // safe because data is equivalent unsafe { - ArrayBase::from_data_ptr(data, self_.layout.ptr).with_strides_dim(self_.layout.strides, self_.layout.dim) + ArrayBase::from_data_ptr(data, self_.parts.ptr).with_strides_dim(self_.parts.strides, self_.parts.dim) } } @@ -297,14 +297,14 @@ unsafe impl Data for OwnedArcRepr match Arc::try_unwrap(self_.data.0) { Ok(owned_data) => unsafe { // Safe because the data is equivalent. - Ok(ArrayBase::from_data_ptr(owned_data, self_.layout.ptr) - .with_strides_dim(self_.layout.strides, self_.layout.dim)) + Ok(ArrayBase::from_data_ptr(owned_data, self_.parts.ptr) + .with_strides_dim(self_.parts.strides, self_.parts.dim)) }, Err(arc_data) => unsafe { // Safe because the data is equivalent; we're just // reconstructing `self_`. - Err(ArrayBase::from_data_ptr(OwnedArcRepr(arc_data), self_.layout.ptr) - .with_strides_dim(self_.layout.strides, self_.layout.dim)) + Err(ArrayBase::from_data_ptr(OwnedArcRepr(arc_data), self_.parts.ptr) + .with_strides_dim(self_.parts.strides, self_.parts.dim)) }, } } @@ -603,9 +603,9 @@ where A: Clone CowRepr::View(_) => { let owned = ArrayRef::to_owned(array); array.data = CowRepr::Owned(owned.data); - array.layout.ptr = owned.layout.ptr; - array.layout.dim = owned.layout.dim; - array.layout.strides = owned.layout.strides; + array.parts.ptr = owned.parts.ptr; + array.parts.dim = owned.parts.dim; + array.parts.strides = owned.parts.strides; } CowRepr::Owned(_) => {} } @@ -666,8 +666,7 @@ unsafe impl<'a, A> Data for CowRepr<'a, A> CowRepr::View(_) => self_.to_owned(), CowRepr::Owned(data) => unsafe { // safe because the data is equivalent so ptr, dims remain valid - ArrayBase::from_data_ptr(data, self_.layout.ptr) - .with_strides_dim(self_.layout.strides, self_.layout.dim) + ArrayBase::from_data_ptr(data, self_.parts.ptr).with_strides_dim(self_.parts.strides, self_.parts.dim) }, } } @@ -679,8 +678,8 @@ unsafe impl<'a, A> Data for CowRepr<'a, A> CowRepr::View(_) => Err(self_), CowRepr::Owned(data) => unsafe { // safe because the data is equivalent so ptr, dims remain valid - Ok(ArrayBase::from_data_ptr(data, self_.layout.ptr) - .with_strides_dim(self_.layout.strides, self_.layout.dim)) + Ok(ArrayBase::from_data_ptr(data, self_.parts.ptr) + .with_strides_dim(self_.parts.strides, self_.parts.dim)) }, } } diff --git a/src/free_functions.rs b/src/free_functions.rs index a2ad6137c..4ad69f2c3 100644 --- a/src/free_functions.rs +++ b/src/free_functions.rs @@ -16,7 +16,7 @@ use std::mem::{forget, size_of}; use std::ptr::NonNull; use crate::{dimension, ArcArray1, ArcArray2}; -use crate::{imp_prelude::*, LayoutRef}; +use crate::{imp_prelude::*, ArrayPartsSized}; /// Create an **[`Array`]** with one, two, three, four, five, or six dimensions. /// @@ -109,12 +109,12 @@ pub const fn aview0(x: &A) -> ArrayView0<'_, A> { ArrayBase { data: ViewRepr::new(), - layout: LayoutRef { + parts: ArrayPartsSized::new( // Safe because references are always non-null. - ptr: unsafe { NonNull::new_unchecked(x as *const A as *mut A) }, - dim: Ix0(), - strides: Ix0(), - }, + unsafe { NonNull::new_unchecked(x as *const A as *mut A) }, + Ix0(), + Ix0(), + ), } } @@ -149,12 +149,12 @@ pub const fn aview1(xs: &[A]) -> ArrayView1<'_, A> } ArrayBase { data: ViewRepr::new(), - layout: LayoutRef { + parts: ArrayPartsSized::new( // Safe because references are always non-null. - ptr: unsafe { NonNull::new_unchecked(xs.as_ptr() as *mut A) }, - dim: Ix1(xs.len()), - strides: Ix1(1), - }, + unsafe { NonNull::new_unchecked(xs.as_ptr() as *mut A) }, + Ix1(xs.len()), + Ix1(1), + ), } } @@ -207,7 +207,7 @@ pub const fn aview2(xs: &[[A; N]]) -> ArrayView2<'_, A> }; ArrayBase { data: ViewRepr::new(), - layout: LayoutRef { ptr, dim, strides }, + parts: ArrayPartsSized::new(ptr, dim, strides), } } diff --git a/src/impl_clone.rs b/src/impl_clone.rs index 402437941..bef783bd8 100644 --- a/src/impl_clone.rs +++ b/src/impl_clone.rs @@ -7,7 +7,7 @@ // except according to those terms. use crate::imp_prelude::*; -use crate::LayoutRef; +use crate::ArrayPartsSized; use crate::RawDataClone; impl Clone for ArrayBase @@ -16,14 +16,10 @@ impl Clone for ArrayBase { // safe because `clone_with_ptr` promises to provide equivalent data and ptr unsafe { - let (data, ptr) = self.data.clone_with_ptr(self.layout.ptr); + let (data, ptr) = self.data.clone_with_ptr(self.parts.ptr); ArrayBase { data, - layout: LayoutRef { - ptr, - dim: self.layout.dim.clone(), - strides: self.layout.strides.clone(), - }, + parts: ArrayPartsSized::new(ptr, self.parts.dim.clone(), self.parts.strides.clone()), } } } @@ -34,9 +30,9 @@ impl Clone for ArrayBase fn clone_from(&mut self, other: &Self) { unsafe { - self.layout.ptr = self.data.clone_from_with_ptr(&other.data, other.layout.ptr); - self.layout.dim.clone_from(&other.layout.dim); - self.layout.strides.clone_from(&other.layout.strides); + self.parts.ptr = self.data.clone_from_with_ptr(&other.data, other.parts.ptr); + self.parts.dim.clone_from(&other.parts.dim); + self.parts.strides.clone_from(&other.parts.strides); } } } diff --git a/src/impl_cow.rs b/src/impl_cow.rs index 0ecc3c44b..1a28996d6 100644 --- a/src/impl_cow.rs +++ b/src/impl_cow.rs @@ -34,8 +34,8 @@ where D: Dimension { // safe because equivalent data unsafe { - ArrayBase::from_data_ptr(CowRepr::View(view.data), view.ptr) - .with_strides_dim(view.layout.strides, view.layout.dim) + ArrayBase::from_data_ptr(CowRepr::View(view.data), view.parts.ptr) + .with_strides_dim(view.parts.strides, view.parts.dim) } } } @@ -47,8 +47,8 @@ where D: Dimension { // safe because equivalent data unsafe { - ArrayBase::from_data_ptr(CowRepr::Owned(array.data), array.layout.ptr) - .with_strides_dim(array.layout.strides, array.layout.dim) + ArrayBase::from_data_ptr(CowRepr::Owned(array.data), array.parts.ptr) + .with_strides_dim(array.parts.strides, array.parts.dim) } } } diff --git a/src/impl_dyn.rs b/src/impl_dyn.rs index 409fe991a..404f3d4b6 100644 --- a/src/impl_dyn.rs +++ b/src/impl_dyn.rs @@ -31,8 +31,8 @@ impl LayoutRef pub fn insert_axis_inplace(&mut self, axis: Axis) { assert!(axis.index() <= self.ndim()); - self.dim = self.dim.insert_axis(axis); - self.strides = self.strides.insert_axis(axis); + self.0.dim = self._dim().insert_axis(axis); + self.0.strides = self._strides().insert_axis(axis); } /// Collapses the array to `index` along the axis and removes the axis, @@ -54,8 +54,8 @@ impl LayoutRef pub fn index_axis_inplace(&mut self, axis: Axis, index: usize) { self.collapse_axis(axis, index); - self.dim = self.dim.remove_axis(axis); - self.strides = self.strides.remove_axis(axis); + self.0.dim = self._dim().remove_axis(axis); + self.0.strides = self._strides().remove_axis(axis); } } diff --git a/src/impl_internal_constructors.rs b/src/impl_internal_constructors.rs index 7f95339d5..ef2964fff 100644 --- a/src/impl_internal_constructors.rs +++ b/src/impl_internal_constructors.rs @@ -8,7 +8,7 @@ use std::ptr::NonNull; -use crate::{imp_prelude::*, LayoutRef}; +use crate::{imp_prelude::*, ArrayPartsSized}; // internal "builder-like" methods impl ArrayBase @@ -27,11 +27,7 @@ where S: RawData { let array = ArrayBase { data, - layout: LayoutRef { - ptr, - dim: Ix1(0), - strides: Ix1(1), - }, + parts: ArrayPartsSized::new(ptr, Ix1(0), Ix1(1)), }; debug_assert!(array.pointer_is_inbounds()); array @@ -60,11 +56,7 @@ where debug_assert_eq!(strides.ndim(), dim.ndim()); ArrayBase { data: self.data, - layout: LayoutRef { - ptr: self.layout.ptr, - dim, - strides, - }, + parts: ArrayPartsSized::new(self.parts.ptr, dim, strides), } } } diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 453cc05b3..2170a8d93 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -70,7 +70,7 @@ impl LayoutRef /// Return the total number of elements in the array. pub fn len(&self) -> usize { - self.dim.size() + self._dim().size() } /// Return the length of `axis`. @@ -82,7 +82,7 @@ impl LayoutRef #[track_caller] pub fn len_of(&self, axis: Axis) -> usize { - self.dim[axis.index()] + self._dim()[axis.index()] } /// Return whether the array has any elements @@ -94,7 +94,7 @@ impl LayoutRef /// Return the number of dimensions (axes) in the array pub fn ndim(&self) -> usize { - self.dim.ndim() + self._dim().ndim() } /// Return the shape of the array in its “pattern” form, @@ -102,7 +102,7 @@ impl LayoutRef /// and so on. pub fn dim(&self) -> D::Pattern { - self.dim.clone().into_pattern() + self._dim().clone().into_pattern() } /// Return the shape of the array as it's stored in the array. @@ -121,7 +121,7 @@ impl LayoutRef /// ``` pub fn raw_dim(&self) -> D { - self.dim.clone() + self._dim().clone() } /// Return the shape of the array as a slice. @@ -150,13 +150,13 @@ impl LayoutRef /// ``` pub fn shape(&self) -> &[usize] { - self.dim.slice() + self._dim().slice() } /// Return the strides of the array as a slice. pub fn strides(&self) -> &[isize] { - let s = self.strides.slice(); + let s = self._strides().slice(); // reinterpret unsigned integer as signed unsafe { slice::from_raw_parts(s.as_ptr() as *const _, s.len()) } } @@ -171,7 +171,7 @@ impl LayoutRef pub fn stride_of(&self, axis: Axis) -> isize { // strides are reinterpreted as isize - self.strides[axis.index()] as isize + self._strides()[axis.index()] as isize } } @@ -181,13 +181,13 @@ impl ArrayRef pub fn view(&self) -> ArrayView<'_, A, D> { // debug_assert!(self.pointer_is_inbounds()); - unsafe { ArrayView::new(self.ptr, self.dim.clone(), self.strides.clone()) } + unsafe { ArrayView::new(*self._ptr(), self._dim().clone(), self._strides().clone()) } } /// Return a read-write view of the array pub fn view_mut(&mut self) -> ArrayViewMut<'_, A, D> { - unsafe { ArrayViewMut::new(self.ptr, self.dim.clone(), self.strides.clone()) } + unsafe { ArrayViewMut::new(*self._ptr(), self._dim().clone(), self._strides().clone()) } } /// Return a shared view of the array with elements as if they were embedded in cells. @@ -236,7 +236,9 @@ impl ArrayRef where A: Clone { if let Some(slc) = self.as_slice_memory_order() { - unsafe { Array::from_shape_vec_unchecked(self.dim.clone().strides(self.strides.clone()), slc.to_vec()) } + unsafe { + Array::from_shape_vec_unchecked(self._dim().clone().strides(self._strides().clone()), slc.to_vec()) + } } else { self.map(A::clone) } @@ -588,8 +590,8 @@ where // Slice the axis in-place to update the `dim`, `strides`, and `ptr`. self.slice_axis_inplace(Axis(old_axis), Slice { start, end, step }); // Copy the sliced dim and stride to corresponding axis. - new_dim[new_axis] = self.layout.dim[old_axis]; - new_strides[new_axis] = self.layout.strides[old_axis]; + new_dim[new_axis] = self.parts.dim[old_axis]; + new_strides[new_axis] = self.parts.strides[old_axis]; old_axis += 1; new_axis += 1; } @@ -700,10 +702,11 @@ impl LayoutRef #[track_caller] pub fn slice_axis_inplace(&mut self, axis: Axis, indices: Slice) { + let parts = &mut self.0; let offset = - do_slice(&mut self.dim.slice_mut()[axis.index()], &mut self.strides.slice_mut()[axis.index()], indices); + do_slice(&mut parts.dim.slice_mut()[axis.index()], &mut parts.strides.slice_mut()[axis.index()], indices); unsafe { - self.ptr = self.ptr.offset(offset); + self.0.ptr = self._ptr().offset(offset); } // debug_assert!(self.pointer_is_inbounds()); } @@ -779,8 +782,8 @@ impl LayoutRef Axis(ax), f(AxisDescription { axis: Axis(ax), - len: self.dim[ax], - stride: self.strides[ax] as isize, + len: self._dim()[ax], + stride: self._strides()[ax] as isize, }), ) } @@ -832,9 +835,9 @@ impl RawRef pub fn get_ptr(&self, index: I) -> Option<*const A> where I: NdIndex { - let ptr = self.ptr; + let ptr = self._ptr(); index - .index_checked(&self.dim, &self.strides) + .index_checked(self._dim(), self._strides()) .map(move |offset| unsafe { ptr.as_ptr().offset(offset) as *const _ }) } } @@ -876,7 +879,7 @@ impl RawRef // extra code in as_mut_ptr let ptr = self.as_mut_ptr(); index - .index_checked(&self.dim, &self.strides) + .index_checked(self._dim(), self._strides()) .map(move |offset| unsafe { ptr.offset(offset) }) } } @@ -897,8 +900,8 @@ impl ArrayRef where I: NdIndex { arraytraits::debug_bounds_check(self, &index); - let off = index.index_unchecked(&self.strides); - &*self.ptr.as_ptr().offset(off) + let off = index.index_unchecked(self._strides()); + &*self._ptr().as_ptr().offset(off) } /// Perform *unchecked* array indexing. @@ -921,8 +924,8 @@ impl ArrayRef { // debug_assert!(self.data.is_unique()); arraytraits::debug_bounds_check(self, &index); - let off = index.index_unchecked(&self.strides); - &mut *self.ptr.as_ptr().offset(off) + let off = index.index_unchecked(self._strides()); + &mut *self._ptr().as_ptr().offset(off) } /// Swap elements at indices `index1` and `index2`. @@ -935,8 +938,8 @@ impl ArrayRef where I: NdIndex { let ptr = self.as_mut_ptr(); - let offset1 = index1.index_checked(&self.dim, &self.strides); - let offset2 = index2.index_checked(&self.dim, &self.strides); + let offset1 = index1.index_checked(self._dim(), self._strides()); + let offset2 = index2.index_checked(self._dim(), self._strides()); if let Some(offset1) = offset1 { if let Some(offset2) = offset2 { unsafe { @@ -968,9 +971,9 @@ impl ArrayRef // debug_assert!(self.data.is_unique()); arraytraits::debug_bounds_check(self, &index1); arraytraits::debug_bounds_check(self, &index2); - let off1 = index1.index_unchecked(&self.strides); - let off2 = index2.index_unchecked(&self.strides); - std::ptr::swap(self.ptr.as_ptr().offset(off1), self.ptr.as_ptr().offset(off2)); + let off1 = index1.index_unchecked(self._strides()); + let off2 = index2.index_unchecked(self._strides()); + std::ptr::swap(self._ptr().as_ptr().offset(off1), self._ptr().as_ptr().offset(off2)); } // `get` for zero-dimensional arrays @@ -1056,8 +1059,8 @@ where where D: RemoveAxis { self.collapse_axis(axis, index); - let dim = self.layout.dim.remove_axis(axis); - let strides = self.layout.strides.remove_axis(axis); + let dim = self.parts.dim.remove_axis(axis); + let strides = self.parts.strides.remove_axis(axis); // safe because new dimension, strides allow access to a subset of old data unsafe { self.with_strides_dim(strides, dim) } } @@ -1071,8 +1074,9 @@ impl LayoutRef #[track_caller] pub fn collapse_axis(&mut self, axis: Axis, index: usize) { - let offset = dimension::do_collapse_axis(&mut self.dim, &self.strides, axis.index(), index); - self.ptr = unsafe { self.ptr.offset(offset) }; + let parts = &mut self.0; + let offset = dimension::do_collapse_axis(&mut parts.dim, &parts.strides, axis.index(), index); + self.0.ptr = unsafe { self._ptr().offset(offset) }; // debug_assert!(self.pointer_is_inbounds()); } } @@ -1571,7 +1575,7 @@ where fn diag_params(&self) -> (Ix, Ixs) { /* empty shape has len 1 */ - let len = self.layout.dim.slice().iter().cloned().min().unwrap_or(1); + let len = self.parts.dim.slice().iter().cloned().min().unwrap_or(1); let stride = self.strides().iter().sum(); (len, stride) } @@ -1618,13 +1622,13 @@ impl LayoutRef /// contiguous in memory, it has custom strides, etc. pub fn is_standard_layout(&self) -> bool { - dimension::is_layout_c(&self.dim, &self.strides) + dimension::is_layout_c(self._dim(), self._strides()) } /// Return true if the array is known to be contiguous. pub(crate) fn is_contiguous(&self) -> bool { - D::is_contiguous(&self.dim, &self.strides) + D::is_contiguous(self._dim(), self._strides()) } } @@ -1659,7 +1663,7 @@ impl ArrayRef CowArray::from(self.view()) } else { let v = crate::iterators::to_vec_mapped(self.iter(), A::clone); - let dim = self.dim.clone(); + let dim = self._dim().clone(); debug_assert_eq!(v.len(), dim.size()); unsafe { @@ -1685,14 +1689,14 @@ impl RawRef #[inline(always)] pub fn as_ptr(&self) -> *const A { - self.ptr.as_ptr() as *const A + self._ptr().as_ptr() as *const A } /// Return a mutable pointer to the first element in the array reference. #[inline(always)] pub fn as_mut_ptr(&mut self) -> *mut A { - self.ptr.as_ptr() + self._ptr().as_ptr() } } @@ -1716,7 +1720,7 @@ where where S: RawDataMut { self.try_ensure_unique(); // for ArcArray - self.layout.ptr.as_ptr() + self.parts.ptr.as_ptr() } } @@ -1726,14 +1730,14 @@ impl RawRef #[inline] pub fn raw_view(&self) -> RawArrayView { - unsafe { RawArrayView::new(self.ptr, self.dim.clone(), self.strides.clone()) } + unsafe { RawArrayView::new(*self._ptr(), self._dim().clone(), self._strides().clone()) } } /// Return a raw mutable view of the array. #[inline] pub fn raw_view_mut(&mut self) -> RawArrayViewMut { - unsafe { RawArrayViewMut::new(self.ptr, self.dim.clone(), self.strides.clone()) } + unsafe { RawArrayViewMut::new(*self._ptr(), self._dim().clone(), self._strides().clone()) } } } @@ -1751,7 +1755,7 @@ where where S: RawDataMut { self.try_ensure_unique(); // for ArcArray - unsafe { RawArrayViewMut::new(self.layout.ptr, self.layout.dim.clone(), self.layout.strides.clone()) } + unsafe { RawArrayViewMut::new(self.parts.ptr, self.parts.dim.clone(), self.parts.strides.clone()) } } /// Return a raw mutable view of the array. @@ -1761,7 +1765,7 @@ where pub(crate) unsafe fn raw_view_mut_unchecked(&mut self) -> RawArrayViewMut where S: DataOwned { - RawArrayViewMut::new(self.ptr, self.dim.clone(), self.strides.clone()) + RawArrayViewMut::new(*self._ptr(), self._dim().clone(), self._strides().clone()) } /// Return the array’s data as a slice, if it is contiguous and in standard order. @@ -1771,7 +1775,7 @@ where { if self.is_standard_layout() { self.ensure_unique(); - unsafe { Some(slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len())) } + unsafe { Some(slice::from_raw_parts_mut(self._ptr().as_ptr(), self.len())) } } else { None } @@ -1796,8 +1800,8 @@ where { if self.is_contiguous() { self.ensure_unique(); - let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); - unsafe { Ok(slice::from_raw_parts_mut(self.ptr.sub(offset).as_ptr(), self.len())) } + let offset = offset_from_low_addr_ptr_to_logical_ptr(self._dim(), self._strides()); + unsafe { Ok(slice::from_raw_parts_mut(self._ptr().sub(offset).as_ptr(), self.len())) } } else { Err(self) } @@ -1814,7 +1818,7 @@ impl ArrayRef pub fn as_slice(&self) -> Option<&[A]> { if self.is_standard_layout() { - unsafe { Some(slice::from_raw_parts(self.ptr.as_ptr(), self.len())) } + unsafe { Some(slice::from_raw_parts(self._ptr().as_ptr(), self.len())) } } else { None } @@ -1825,7 +1829,7 @@ impl ArrayRef pub fn as_slice_mut(&mut self) -> Option<&mut [A]> { if self.is_standard_layout() { - unsafe { Some(slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len())) } + unsafe { Some(slice::from_raw_parts_mut(self._ptr().as_ptr(), self.len())) } } else { None } @@ -1839,8 +1843,8 @@ impl ArrayRef pub fn as_slice_memory_order(&self) -> Option<&[A]> { if self.is_contiguous() { - let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); - unsafe { Some(slice::from_raw_parts(self.ptr.sub(offset).as_ptr(), self.len())) } + let offset = offset_from_low_addr_ptr_to_logical_ptr(self._dim(), self._strides()); + unsafe { Some(slice::from_raw_parts(self._ptr().sub(offset).as_ptr(), self.len())) } } else { None } @@ -1862,8 +1866,8 @@ impl ArrayRef pub(crate) fn try_as_slice_memory_order_mut(&mut self) -> Result<&mut [A], &mut Self> { if self.is_contiguous() { - let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); - unsafe { Ok(slice::from_raw_parts_mut(self.ptr.sub(offset).as_ptr(), self.len())) } + let offset = offset_from_low_addr_ptr_to_logical_ptr(self._dim(), self._strides()); + unsafe { Ok(slice::from_raw_parts_mut(self._ptr().sub(offset).as_ptr(), self.len())) } } else { Err(self) } @@ -1938,9 +1942,9 @@ impl ArrayRef E: Dimension, A: Clone, { - let len = self.dim.size(); + let len = self._dim().size(); if size_of_shape_checked(&shape) != Ok(len) { - return Err(error::incompatible_shapes(&self.dim, &shape)); + return Err(error::incompatible_shapes(self._dim(), &shape)); } // Create a view if the length is 0, safe because the array and new shape is empty. @@ -1951,12 +1955,12 @@ impl ArrayRef } // Try to reshape the array as a view into the existing data - match reshape_dim(&self.dim, &self.strides, &shape, order) { + match reshape_dim(self._dim(), self._strides(), &shape, order) { Ok(to_strides) => unsafe { - return Ok(CowArray::from(ArrayView::new(self.ptr, shape, to_strides))); + return Ok(CowArray::from(ArrayView::new(*self._ptr(), shape, to_strides))); }, Err(err) if err.kind() == ErrorKind::IncompatibleShape => { - return Err(error::incompatible_shapes(&self.dim, &shape)); + return Err(error::incompatible_shapes(self._dim(), &shape)); } _otherwise => {} } @@ -2033,8 +2037,8 @@ where where E: Dimension { let shape = shape.into_dimension(); - if size_of_shape_checked(&shape) != Ok(self.layout.dim.size()) { - return Err(error::incompatible_shapes(&self.layout.dim, &shape)); + if size_of_shape_checked(&shape) != Ok(self.parts.dim.size()) { + return Err(error::incompatible_shapes(&self.parts.dim, &shape)); } // Check if contiguous, then we can change shape @@ -2078,8 +2082,8 @@ where where E: IntoDimension { let shape = shape.into_dimension(); - if size_of_shape_checked(&shape) != Ok(self.layout.dim.size()) { - return Err(error::incompatible_shapes(&self.layout.dim, &shape)); + if size_of_shape_checked(&shape) != Ok(self.parts.dim.size()) { + return Err(error::incompatible_shapes(&self.parts.dim, &shape)); } // Check if contiguous, if not => copy all, else just adapt strides unsafe { @@ -2124,9 +2128,9 @@ where A: Clone, E: Dimension, { - let len = self.dim.size(); + let len = self._dim().size(); if size_of_shape_checked(&shape) != Ok(len) { - return Err(error::incompatible_shapes(&self.dim, &shape)); + return Err(error::incompatible_shapes(self._dim(), &shape)); } // Safe because the array and new shape is empty. @@ -2137,12 +2141,12 @@ where } // Try to reshape the array's current data - match reshape_dim(&self.dim, &self.strides, &shape, order) { + match reshape_dim(self._dim(), self._strides(), &shape, order) { Ok(to_strides) => unsafe { return Ok(self.with_strides_dim(to_strides, shape)); }, Err(err) if err.kind() == ErrorKind::IncompatibleShape => { - return Err(error::incompatible_shapes(&self.dim, &shape)); + return Err(error::incompatible_shapes(self._dim(), &shape)); } _otherwise => {} } @@ -2190,10 +2194,10 @@ where E: IntoDimension, { let shape = shape.into_dimension(); - if size_of_shape_checked(&shape) != Ok(self.dim.size()) { + if size_of_shape_checked(&shape) != Ok(self._dim().size()) { panic!( "ndarray: incompatible shapes in reshape, attempted from: {:?}, to: {:?}", - self.dim.slice(), + self._dim().slice(), shape.slice() ) } @@ -2289,8 +2293,8 @@ where { // safe because new dims equivalent unsafe { - ArrayBase::from_data_ptr(self.data, self.layout.ptr) - .with_strides_dim(self.layout.strides.into_dyn(), self.layout.dim.into_dyn()) + ArrayBase::from_data_ptr(self.data, self.parts.ptr) + .with_strides_dim(self.parts.strides.into_dyn(), self.parts.dim.into_dyn()) } } @@ -2316,14 +2320,14 @@ where unsafe { if D::NDIM == D2::NDIM { // safe because D == D2 - let dim = unlimited_transmute::(self.layout.dim); - let strides = unlimited_transmute::(self.layout.strides); - return Ok(ArrayBase::from_data_ptr(self.data, self.layout.ptr).with_strides_dim(strides, dim)); + let dim = unlimited_transmute::(self.parts.dim); + let strides = unlimited_transmute::(self.parts.strides); + return Ok(ArrayBase::from_data_ptr(self.data, self.parts.ptr).with_strides_dim(strides, dim)); } else if D::NDIM.is_none() || D2::NDIM.is_none() { // one is dynamic dim // safe because dim, strides are equivalent under a different type - if let Some(dim) = D2::from_dimension(&self.layout.dim) { - if let Some(strides) = D2::from_dimension(&self.layout.strides) { + if let Some(dim) = D2::from_dimension(&self.parts.dim) { + if let Some(strides) = D2::from_dimension(&self.parts.strides) { return Ok(self.with_strides_dim(strides, dim)); } } @@ -2421,8 +2425,8 @@ impl ArrayRef let dim = dim.into_dimension(); // Note: zero strides are safe precisely because we return an read-only view - let broadcast_strides = upcast(&dim, &self.dim, &self.strides)?; - unsafe { Some(ArrayView::new(self.ptr, dim, broadcast_strides)) } + let broadcast_strides = upcast(&dim, self._dim(), self._strides())?; + unsafe { Some(ArrayView::new(*self._ptr(), dim, broadcast_strides)) } } /// For two arrays or views, find their common shape if possible and @@ -2437,8 +2441,8 @@ impl ArrayRef D: Dimension + DimMax, E: Dimension, { - let shape = co_broadcast::>::Output>(&self.dim, &other.dim)?; - let view1 = if shape.slice() == self.dim.slice() { + let shape = co_broadcast::>::Output>(self._dim(), other._dim())?; + let view1 = if shape.slice() == self._dim().slice() { self.view() .into_dimensionality::<>::Output>() .unwrap() @@ -2447,7 +2451,7 @@ impl ArrayRef } else { return Err(from_kind(ErrorKind::IncompatibleShape)); }; - let view2 = if shape.slice() == other.dim.slice() { + let view2 = if shape.slice() == other._dim().slice() { other .view() .into_dimensionality::<>::Output>() @@ -2482,8 +2486,8 @@ impl LayoutRef #[track_caller] pub fn swap_axes(&mut self, ax: usize, bx: usize) { - self.dim.slice_mut().swap(ax, bx); - self.strides.slice_mut().swap(ax, bx); + self.0.dim.slice_mut().swap(ax, bx); + self.0.strides.slice_mut().swap(ax, bx); } } @@ -2531,8 +2535,8 @@ where let mut new_dim = usage_counts; // reuse to avoid an allocation let mut new_strides = D::zeros(self.ndim()); { - let dim = self.layout.dim.slice(); - let strides = self.layout.strides.slice(); + let dim = self.parts.dim.slice(); + let strides = self.parts.strides.slice(); for (new_axis, &axis) in axes.slice().iter().enumerate() { new_dim[new_axis] = dim[axis]; new_strides[new_axis] = strides[axis]; @@ -2579,8 +2583,8 @@ where assert_eq!(*count, 1, "each axis must be listed exactly once"); } - let dim = self.layout.dim.slice_mut(); - let strides = self.layout.strides.slice_mut(); + let dim = self.parts.dim.slice_mut(); + let strides = self.parts.strides.slice_mut(); let axes = axes.slice(); // The cycle detection is done using a bitmask to track visited positions. @@ -2614,8 +2618,8 @@ where /// while retaining the same data. pub fn reversed_axes(mut self) -> ArrayBase { - self.layout.dim.slice_mut().reverse(); - self.layout.strides.slice_mut().reverse(); + self.parts.dim.slice_mut().reverse(); + self.parts.strides.slice_mut().reverse(); self } @@ -2625,8 +2629,8 @@ where /// and strides. pub fn reverse_axes(&mut self) { - self.layout.dim.slice_mut().reverse(); - self.layout.strides.slice_mut().reverse(); + self.parts.dim.slice_mut().reverse(); + self.parts.strides.slice_mut().reverse(); } } @@ -2648,13 +2652,13 @@ impl LayoutRef /// Return an iterator over the length and stride of each axis. pub fn axes(&self) -> Axes<'_, D> { - axes_of(&self.dim, &self.strides) + axes_of(self._dim(), self._strides()) } /* /// Return the axis with the least stride (by absolute value) pub fn min_stride_axis(&self) -> Axis { - self.dim.min_stride_axis(&self.strides) + self._dim().min_stride_axis(self._strides()) } */ @@ -2662,7 +2666,7 @@ impl LayoutRef /// preferring axes with len > 1. pub fn max_stride_axis(&self) -> Axis { - self.dim.max_stride_axis(&self.strides) + self._dim().max_stride_axis(self._strides()) } /// Reverse the stride of `axis`. @@ -2672,12 +2676,12 @@ impl LayoutRef pub fn invert_axis(&mut self, axis: Axis) { unsafe { - let s = self.strides.axis(axis) as Ixs; - let m = self.dim.axis(axis); + let s = self._strides().axis(axis) as Ixs; + let m = self._dim().axis(axis); if m != 0 { - self.ptr = self.ptr.offset(stride_offset(m - 1, s as Ix)); + self.0.ptr = self._ptr().offset(stride_offset(m - 1, s as Ix)); } - self.strides.set_axis(axis, (-s) as Ix); + self.0.strides.set_axis(axis, (-s) as Ix); } } @@ -2719,7 +2723,8 @@ impl LayoutRef #[track_caller] pub fn merge_axes(&mut self, take: Axis, into: Axis) -> bool { - merge_axes(&mut self.dim, &mut self.strides, take, into) + let parts = &mut self.0; + merge_axes(&mut parts.dim, &mut parts.strides, take, into) } } @@ -2755,8 +2760,8 @@ where assert!(axis.index() <= self.ndim()); // safe because a new axis of length one does not affect memory layout unsafe { - let strides = self.layout.strides.insert_axis(axis); - let dim = self.layout.dim.insert_axis(axis); + let strides = self.parts.strides.insert_axis(axis); + let dim = self.parts.dim.insert_axis(axis); self.with_strides_dim(strides, dim) } } @@ -2824,7 +2829,10 @@ impl ArrayRef { debug_assert_eq!(self.shape(), rhs.shape()); - if self.dim.strides_equivalent(&self.strides, &rhs.strides) { + if self + ._dim() + .strides_equivalent(self._strides(), rhs._strides()) + { if let Some(self_s) = self.as_slice_memory_order_mut() { if let Some(rhs_s) = rhs.as_slice_memory_order() { for (s, r) in self_s.iter_mut().zip(rhs_s) { @@ -2876,10 +2884,10 @@ impl ArrayRef E: Dimension, F: FnMut(&mut A, &B), { - if rhs.dim.ndim() == 0 { + if rhs._dim().ndim() == 0 { // Skip broadcast from 0-dim array self.zip_mut_with_elem(rhs.get_0d(), f); - } else if self.dim.ndim() == rhs.dim.ndim() && self.shape() == rhs.shape() { + } else if self._dim().ndim() == rhs._dim().ndim() && self.shape() == rhs.shape() { self.zip_mut_with_same_shape(rhs, f); } else { let rhs_broadcast = rhs.broadcast_unwrap(self.raw_dim()); @@ -2900,7 +2908,7 @@ impl ArrayRef slc.iter().fold(init, f) } else { let mut v = self.view(); - move_min_stride_axis_to_last(&mut v.layout.dim, &mut v.layout.strides); + move_min_stride_axis_to_last(&mut v.parts.dim, &mut v.parts.strides); v.into_elements_base().fold(init, f) } } @@ -2931,12 +2939,12 @@ impl ArrayRef unsafe { if let Some(slc) = self.as_slice_memory_order() { ArrayBase::from_shape_trusted_iter_unchecked( - self.dim.clone().strides(self.strides.clone()), + self._dim().clone().strides(self._strides().clone()), slc.iter(), f, ) } else { - ArrayBase::from_shape_trusted_iter_unchecked(self.dim.clone(), self.iter(), f) + ArrayBase::from_shape_trusted_iter_unchecked(self._dim().clone(), self.iter(), f) } } } @@ -2952,9 +2960,9 @@ impl ArrayRef F: FnMut(&'a mut A) -> B, A: 'a, { - let dim = self.dim.clone(); + let dim = self._dim().clone(); if self.is_contiguous() { - let strides = self.strides.clone(); + let strides = self._strides().clone(); let slc = self.as_slice_memory_order_mut().unwrap(); unsafe { ArrayBase::from_shape_trusted_iter_unchecked(dim.strides(strides), slc.iter_mut(), f) } } else { @@ -3064,7 +3072,7 @@ impl ArrayRef Ok(slc) => slc.iter_mut().for_each(f), Err(arr) => { let mut v = arr.view_mut(); - move_min_stride_axis_to_last(&mut v.layout.dim, &mut v.layout.strides); + move_min_stride_axis_to_last(&mut v.parts.dim, &mut v.parts.strides); v.into_elements_base().for_each(f); } } @@ -3148,7 +3156,7 @@ impl ArrayRef A: 'a, { if self.len_of(axis) == 0 { - let new_dim = self.dim.remove_axis(axis); + let new_dim = self._dim().remove_axis(axis); Array::from_shape_simple_fn(new_dim, move || mapping(ArrayView::from(&[]))) } else { Zip::from(self.lanes(axis)).map_collect(mapping) @@ -3173,7 +3181,7 @@ impl ArrayRef A: 'a, { if self.len_of(axis) == 0 { - let new_dim = self.dim.remove_axis(axis); + let new_dim = self._dim().remove_axis(axis); Array::from_shape_simple_fn(new_dim, move || mapping(ArrayViewMut::from(&mut []))) } else { Zip::from(self.lanes_mut(axis)).map_collect(mapping) diff --git a/src/impl_owned_array.rs b/src/impl_owned_array.rs index 023e9ebb4..277b156b8 100644 --- a/src/impl_owned_array.rs +++ b/src/impl_owned_array.rs @@ -45,7 +45,7 @@ impl Array // (This is necessary because the element in the array might not be // the first element in the `Vec`, such as if the array was created // by `array![1, 2, 3, 4].slice_move(s![2])`.) - let first = self.ptr.as_ptr() as usize; + let first = self.parts.ptr.as_ptr() as usize; let base = self.data.as_ptr() as usize; let index = (first - base) / size; debug_assert_eq!((first - base) % size, 0); @@ -69,7 +69,7 @@ where D: Dimension return None; } if std::mem::size_of::() == 0 { - Some(dimension::offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides)) + Some(dimension::offset_from_low_addr_ptr_to_logical_ptr(&self.parts.dim, &self.parts.strides)) } else { let offset = unsafe { self.as_ptr().offset_from(self.data.as_ptr()) }; debug_assert!(offset >= 0); @@ -476,8 +476,8 @@ where D: Dimension } else { dim.slice_mut()[..=growing_axis.index()].rotate_right(1); new_array = Self::uninit(dim); - new_array.dim.slice_mut()[..=growing_axis.index()].rotate_left(1); - new_array.strides.slice_mut()[..=growing_axis.index()].rotate_left(1); + new_array.parts.dim.slice_mut()[..=growing_axis.index()].rotate_left(1); + new_array.parts.strides.slice_mut()[..=growing_axis.index()].rotate_left(1); } // self -> old_self. @@ -631,7 +631,7 @@ where D: Dimension // either the dimension increment is zero, or there is an existing // zero in another axis in self. debug_assert_eq!(self.len(), new_len); - self.dim = res_dim; + self.parts.dim = res_dim; return Ok(()); } @@ -701,11 +701,11 @@ where D: Dimension } } }); - let mut strides = self.strides.clone(); + let mut strides = self.parts.strides.clone(); strides[axis.index()] = new_stride as usize; strides } else { - self.strides.clone() + self.parts.strides.clone() }; // grow backing storage and update head ptr @@ -746,7 +746,7 @@ where D: Dimension sort_axes_in_default_order_tandem(&mut tail_view, &mut array); debug_assert!(tail_view.is_standard_layout(), "not std layout dim: {:?}, strides: {:?}", - tail_view.shape(), tail_view.strides()); + tail_view.shape(), RawArrayViewMut::strides(&tail_view)); } // Keep track of currently filled length of `self.data` and update it @@ -785,8 +785,8 @@ where D: Dimension drop(data_length_guard); // update array dimension - self.strides = strides; - self.dim = res_dim; + self.parts.strides = strides; + self.parts.dim = res_dim; } // multiple assertions after pointer & dimension update debug_assert_eq!(self.data.len(), self.len()); @@ -849,7 +849,7 @@ where D: Dimension 0 }; debug_assert!(data_to_array_offset >= 0); - self.layout.ptr = self + self.parts.ptr = self .data .reserve(len_to_append) .offset(data_to_array_offset); @@ -880,7 +880,7 @@ pub(crate) unsafe fn drop_unreachable_raw( } sort_axes_in_default_order(&mut self_); // with uninverted axes this is now the element with lowest address - let array_memory_head_ptr = self_.layout.ptr; + let array_memory_head_ptr = self_.parts.ptr; let data_end_ptr = data_ptr.add(data_len); debug_assert!(data_ptr <= array_memory_head_ptr); debug_assert!(array_memory_head_ptr <= data_end_ptr); @@ -897,19 +897,19 @@ pub(crate) unsafe fn drop_unreachable_raw( // As an optimization, the innermost axis is removed if it has stride 1, because // we then have a long stretch of contiguous elements we can skip as one. let inner_lane_len; - if self_.ndim() > 1 && self_.layout.strides.last_elem() == 1 { - self_.layout.dim.slice_mut().rotate_right(1); - self_.layout.strides.slice_mut().rotate_right(1); - inner_lane_len = self_.layout.dim[0]; - self_.layout.dim[0] = 1; - self_.layout.strides[0] = 1; + if self_.ndim() > 1 && self_.parts.strides.last_elem() == 1 { + self_.parts.dim.slice_mut().rotate_right(1); + self_.parts.strides.slice_mut().rotate_right(1); + inner_lane_len = self_.parts.dim[0]; + self_.parts.dim[0] = 1; + self_.parts.strides[0] = 1; } else { inner_lane_len = 1; } // iter is a raw pointer iterator traversing the array in memory order now with the // sorted axes. - let mut iter = Baseiter::new(self_.layout.ptr, self_.layout.dim, self_.layout.strides); + let mut iter = Baseiter::new(self_.parts.ptr, self_.parts.dim, self_.parts.strides); let mut dropped_elements = 0; let mut last_ptr = data_ptr; @@ -948,7 +948,7 @@ where if a.ndim() <= 1 { return; } - sort_axes1_impl(&mut a.layout.dim, &mut a.layout.strides); + sort_axes1_impl(&mut a.parts.dim, &mut a.parts.strides); } fn sort_axes1_impl(adim: &mut D, astrides: &mut D) @@ -988,7 +988,7 @@ where if a.ndim() <= 1 { return; } - sort_axes2_impl(&mut a.layout.dim, &mut a.layout.strides, &mut b.layout.dim, &mut b.layout.strides); + sort_axes2_impl(&mut a.parts.dim, &mut a.parts.strides, &mut b.parts.dim, &mut b.parts.strides); } fn sort_axes2_impl(adim: &mut D, astrides: &mut D, bdim: &mut D, bstrides: &mut D) diff --git a/src/impl_raw_views.rs b/src/impl_raw_views.rs index 5bb2a0e42..2423b9343 100644 --- a/src/impl_raw_views.rs +++ b/src/impl_raw_views.rs @@ -98,10 +98,10 @@ where D: Dimension pub unsafe fn deref_into_view<'a>(self) -> ArrayView<'a, A, D> { debug_assert!( - is_aligned(self.layout.ptr.as_ptr()), + is_aligned(self.parts.ptr.as_ptr()), "The pointer must be aligned." ); - ArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) + ArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } /// Split the array view along `axis` and return one array pointer strictly @@ -113,23 +113,23 @@ where D: Dimension pub fn split_at(self, axis: Axis, index: Ix) -> (Self, Self) { assert!(index <= self.len_of(axis)); - let left_ptr = self.layout.ptr.as_ptr(); + let left_ptr = self.parts.ptr.as_ptr(); let right_ptr = if index == self.len_of(axis) { - self.layout.ptr.as_ptr() + self.parts.ptr.as_ptr() } else { - let offset = stride_offset(index, self.layout.strides.axis(axis)); + let offset = stride_offset(index, self.parts.strides.axis(axis)); // The `.offset()` is safe due to the guarantees of `RawData`. - unsafe { self.layout.ptr.as_ptr().offset(offset) } + unsafe { self.parts.ptr.as_ptr().offset(offset) } }; - let mut dim_left = self.layout.dim.clone(); + let mut dim_left = self.parts.dim.clone(); dim_left.set_axis(axis, index); - let left = unsafe { Self::new_(left_ptr, dim_left, self.layout.strides.clone()) }; + let left = unsafe { Self::new_(left_ptr, dim_left, self.parts.strides.clone()) }; - let mut dim_right = self.layout.dim; + let mut dim_right = self.parts.dim; let right_len = dim_right.axis(axis) - index; dim_right.set_axis(axis, right_len); - let right = unsafe { Self::new_(right_ptr, dim_right, self.layout.strides) }; + let right = unsafe { Self::new_(right_ptr, dim_right, self.parts.strides) }; (left, right) } @@ -152,8 +152,8 @@ where D: Dimension mem::size_of::(), "size mismatch in raw view cast" ); - let ptr = self.layout.ptr.cast::(); - unsafe { RawArrayView::new(ptr, self.layout.dim, self.layout.strides) } + let ptr = self.parts.ptr.cast::(); + unsafe { RawArrayView::new(ptr, self.parts.dim, self.parts.strides) } } } @@ -172,11 +172,11 @@ where D: Dimension ); assert_eq!(mem::align_of::>(), mem::align_of::()); - let dim = self.layout.dim.clone(); + let dim = self.parts.dim.clone(); // Double the strides. In the zero-sized element case and for axes of // length <= 1, we leave the strides as-is to avoid possible overflow. - let mut strides = self.layout.strides.clone(); + let mut strides = self.parts.strides.clone(); if mem::size_of::() != 0 { for ax in 0..strides.ndim() { if dim[ax] > 1 { @@ -185,7 +185,7 @@ where D: Dimension } } - let ptr_re: *mut T = self.layout.ptr.as_ptr().cast(); + let ptr_re: *mut T = self.parts.ptr.as_ptr().cast(); let ptr_im: *mut T = if self.is_empty() { // In the empty case, we can just reuse the existing pointer since // it won't be dereferenced anyway. It is not safe to offset by @@ -308,7 +308,7 @@ where D: Dimension #[inline] pub(crate) fn into_raw_view(self) -> RawArrayView { - unsafe { RawArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { RawArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } /// Converts to a read-only view of the array. @@ -323,10 +323,10 @@ where D: Dimension pub unsafe fn deref_into_view<'a>(self) -> ArrayView<'a, A, D> { debug_assert!( - is_aligned(self.layout.ptr.as_ptr()), + is_aligned(self.parts.ptr.as_ptr()), "The pointer must be aligned." ); - ArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) + ArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } /// Converts to a mutable view of the array. @@ -341,10 +341,10 @@ where D: Dimension pub unsafe fn deref_into_view_mut<'a>(self) -> ArrayViewMut<'a, A, D> { debug_assert!( - is_aligned(self.layout.ptr.as_ptr()), + is_aligned(self.parts.ptr.as_ptr()), "The pointer must be aligned." ); - ArrayViewMut::new(self.layout.ptr, self.layout.dim, self.layout.strides) + ArrayViewMut::new(self.parts.ptr, self.parts.dim, self.parts.strides) } /// Split the array view along `axis` and return one array pointer strictly @@ -358,8 +358,8 @@ where D: Dimension let (left, right) = self.into_raw_view().split_at(axis, index); unsafe { ( - Self::new(left.layout.ptr, left.layout.dim, left.layout.strides), - Self::new(right.layout.ptr, right.layout.dim, right.layout.strides), + Self::new(left.parts.ptr, left.parts.dim, left.parts.strides), + Self::new(right.parts.ptr, right.parts.dim, right.parts.strides), ) } } @@ -382,8 +382,8 @@ where D: Dimension mem::size_of::(), "size mismatch in raw view cast" ); - let ptr = self.layout.ptr.cast::(); - unsafe { RawArrayViewMut::new(ptr, self.layout.dim, self.layout.strides) } + let ptr = self.parts.ptr.cast::(); + unsafe { RawArrayViewMut::new(ptr, self.parts.dim, self.parts.strides) } } } @@ -397,8 +397,8 @@ where D: Dimension let Complex { re, im } = self.into_raw_view().split_complex(); unsafe { Complex { - re: RawArrayViewMut::new(re.layout.ptr, re.layout.dim, re.layout.strides), - im: RawArrayViewMut::new(im.layout.ptr, im.layout.dim, im.layout.strides), + re: RawArrayViewMut::new(re.parts.ptr, re.parts.dim, re.parts.strides), + im: RawArrayViewMut::new(im.parts.ptr, im.parts.dim, im.parts.strides), } } } diff --git a/src/impl_ref_types.rs b/src/impl_ref_types.rs index d93a996bf..bfdfa27f9 100644 --- a/src/impl_ref_types.rs +++ b/src/impl_ref_types.rs @@ -35,7 +35,20 @@ use core::{ ops::{Deref, DerefMut}, }; -use crate::{Array, ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef}; +use crate::{ + Array, + ArrayBase, + ArrayPartsSized, + ArrayPartsUnsized, + ArrayRef, + Data, + DataMut, + Dimension, + LayoutRef, + RawData, + RawDataMut, + RawRef, +}; // D1: &ArrayBase -> &ArrayRef when data is safe to read impl Deref for ArrayBase @@ -50,7 +63,9 @@ where S: Data // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &*(&self.layout as *const LayoutRef).cast::>() } + let parts: &ArrayPartsUnsized = &self.parts; + let ptr = (parts as *const ArrayPartsUnsized) as *const ArrayRef; + unsafe { &*ptr } } } @@ -68,7 +83,9 @@ where // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &mut *(&mut self.layout as *mut LayoutRef).cast::>() } + let parts: &mut ArrayPartsUnsized = &mut self.parts; + let ptr = (parts as *mut ArrayPartsUnsized) as *mut ArrayRef; + unsafe { &mut *ptr } } } @@ -84,7 +101,7 @@ impl Deref for ArrayRef // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &*(self as *const ArrayRef).cast::>() } + unsafe { &*((self as *const ArrayRef) as *const RawRef) } } } @@ -98,7 +115,7 @@ impl DerefMut for ArrayRef // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &mut *(self as *mut ArrayRef).cast::>() } + unsafe { &mut *((self as *mut ArrayRef) as *mut RawRef) } } } @@ -133,7 +150,9 @@ where S: RawData // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &*(&self.layout as *const LayoutRef).cast::>() } + let parts: &ArrayPartsUnsized = &self.parts; + let ptr = (parts as *const ArrayPartsUnsized) as *const RawRef; + unsafe { &*ptr } } } @@ -148,7 +167,9 @@ where S: RawDataMut // - It is "dereferencable" because it comes from a reference // - For the same reason, it is initialized // - The cast is valid because ArrayRef uses #[repr(transparent)] - unsafe { &mut *(&mut self.layout as *mut LayoutRef).cast::>() } + let parts: &mut ArrayPartsUnsized = &mut self.parts; + let ptr = (parts as *mut ArrayPartsUnsized) as *mut RawRef; + unsafe { &mut *ptr } } } @@ -158,7 +179,9 @@ where S: RawData { fn as_ref(&self) -> &LayoutRef { - &self.layout + let parts: &ArrayPartsUnsized = &self.parts; + let ptr = (parts as *const ArrayPartsUnsized) as *const LayoutRef; + unsafe { &*ptr } } } @@ -168,7 +191,9 @@ where S: RawData { fn as_mut(&mut self) -> &mut LayoutRef { - &mut self.layout + let parts: &mut ArrayPartsUnsized = &mut self.parts; + let ptr = (parts as *mut ArrayPartsUnsized) as *mut LayoutRef; + unsafe { &mut *ptr } } } @@ -269,7 +294,7 @@ impl AsMut> for LayoutRef /// impossible to read the data behind the pointer from a LayoutRef (this /// is a safety invariant that *must* be maintained), and therefore we can /// Clone and Copy as desired. -impl Clone for LayoutRef +impl Clone for ArrayPartsSized { fn clone(&self) -> Self { @@ -277,11 +302,12 @@ impl Clone for LayoutRef dim: self.dim.clone(), strides: self.strides.clone(), ptr: self.ptr, + _dst_control: [0; 0], } } } -impl Copy for LayoutRef {} +impl Copy for ArrayPartsSized {} impl Borrow> for ArrayBase where S: RawData @@ -368,3 +394,17 @@ where S: RawData self.as_mut() } } + +/// Tests that a mem::swap can't compile by putting it into a doctest +/// +/// ```compile_fail +/// let mut x = Array1::from_vec(vec![0, 1, 2]); +/// { +/// let mut y = Array1::from_vec(vec![4, 5, 6]); +/// let x_ref = x.as_layout_ref_mut(); +/// let y_ref = y.as_layout_ref_mut(); +/// core::mem::swap(x_ref, y_ref); +/// } +/// ``` +#[allow(dead_code)] +fn test_no_swap_via_doctests() {} diff --git a/src/impl_special_element_types.rs b/src/impl_special_element_types.rs index 42b524bc2..8b525e314 100644 --- a/src/impl_special_element_types.rs +++ b/src/impl_special_element_types.rs @@ -9,7 +9,7 @@ use std::mem::MaybeUninit; use crate::imp_prelude::*; -use crate::LayoutRef; +use crate::ArrayParts; use crate::RawDataSubst; /// Methods specific to arrays with `MaybeUninit` elements. @@ -36,7 +36,13 @@ where { let ArrayBase { data, - layout: LayoutRef { ptr, dim, strides }, + parts: + ArrayParts { + ptr, + dim, + strides, + _dst_control: [], + }, } = self; // "transmute" from storage of MaybeUninit to storage of A diff --git a/src/impl_views/constructors.rs b/src/impl_views/constructors.rs index 29b7c13d7..dcf6527ec 100644 --- a/src/impl_views/constructors.rs +++ b/src/impl_views/constructors.rs @@ -225,7 +225,7 @@ where D: Dimension pub fn reborrow<'b>(self) -> ArrayViewMut<'b, A, D> where 'a: 'b { - unsafe { ArrayViewMut::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { ArrayViewMut::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } } diff --git a/src/impl_views/conversions.rs b/src/impl_views/conversions.rs index 5bc5f9ad6..54d7ed207 100644 --- a/src/impl_views/conversions.rs +++ b/src/impl_views/conversions.rs @@ -29,7 +29,7 @@ where D: Dimension pub fn reborrow<'b>(self) -> ArrayView<'b, A, D> where 'a: 'b { - unsafe { ArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { ArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } /// Return the array’s data as a slice, if it is contiguous and in standard order. @@ -40,7 +40,7 @@ where D: Dimension pub fn to_slice(&self) -> Option<&'a [A]> { if self.is_standard_layout() { - unsafe { Some(slice::from_raw_parts(self.ptr.as_ptr(), self.len())) } + unsafe { Some(slice::from_raw_parts(self.parts.ptr.as_ptr(), self.len())) } } else { None } @@ -55,8 +55,8 @@ where D: Dimension pub fn to_slice_memory_order(&self) -> Option<&'a [A]> { if self.is_contiguous() { - let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); - unsafe { Some(slice::from_raw_parts(self.ptr.sub(offset).as_ptr(), self.len())) } + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.parts.dim, &self.parts.strides); + unsafe { Some(slice::from_raw_parts(self.parts.ptr.sub(offset).as_ptr(), self.len())) } } else { None } @@ -66,7 +66,7 @@ where D: Dimension #[inline] pub(crate) fn into_raw_view(self) -> RawArrayView { - unsafe { RawArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { RawArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } } @@ -199,7 +199,7 @@ where D: Dimension #[inline] pub(crate) fn into_base_iter(self) -> Baseiter { - unsafe { Baseiter::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { Baseiter::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } } @@ -209,7 +209,7 @@ where D: Dimension #[inline] pub(crate) fn into_base_iter(self) -> Baseiter { - unsafe { Baseiter::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { Baseiter::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } } @@ -220,7 +220,7 @@ where D: Dimension #[inline] pub(crate) fn into_base_iter(self) -> Baseiter { - unsafe { Baseiter::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { Baseiter::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } #[inline] @@ -276,19 +276,19 @@ where D: Dimension // Convert into a read-only view pub(crate) fn into_view(self) -> ArrayView<'a, A, D> { - unsafe { ArrayView::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { ArrayView::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } /// Converts to a mutable raw array view. pub(crate) fn into_raw_view_mut(self) -> RawArrayViewMut { - unsafe { RawArrayViewMut::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { RawArrayViewMut::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } #[inline] pub(crate) fn into_base_iter(self) -> Baseiter { - unsafe { Baseiter::new(self.layout.ptr, self.layout.dim, self.layout.strides) } + unsafe { Baseiter::new(self.parts.ptr, self.parts.dim, self.parts.strides) } } #[inline] @@ -302,7 +302,7 @@ where D: Dimension pub(crate) fn try_into_slice(self) -> Result<&'a mut [A], Self> { if self.is_standard_layout() { - unsafe { Ok(slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len())) } + unsafe { Ok(slice::from_raw_parts_mut(self.parts.ptr.as_ptr(), self.len())) } } else { Err(self) } @@ -313,8 +313,8 @@ where D: Dimension fn try_into_slice_memory_order(self) -> Result<&'a mut [A], Self> { if self.is_contiguous() { - let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); - unsafe { Ok(slice::from_raw_parts_mut(self.ptr.sub(offset).as_ptr(), self.len())) } + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.parts.dim, &self.parts.strides); + unsafe { Ok(slice::from_raw_parts_mut(self.parts.ptr.sub(offset).as_ptr(), self.len())) } } else { Err(self) } diff --git a/src/impl_views/indexing.rs b/src/impl_views/indexing.rs index 2879e7416..feadbd296 100644 --- a/src/impl_views/indexing.rs +++ b/src/impl_views/indexing.rs @@ -145,7 +145,9 @@ where unsafe fn uget(self, index: I) -> &'a A { debug_bounds_check!(self, index); - &*self.as_ptr().offset(index.index_unchecked(&self.strides)) + &*self + .as_ptr() + .offset(index.index_unchecked(&self.parts.strides)) } } @@ -213,6 +215,6 @@ where debug_bounds_check!(self, index); &mut *self .as_mut_ptr() - .offset(index.index_unchecked(&self.strides)) + .offset(index.index_unchecked(&self.parts.strides)) } } diff --git a/src/iterators/chunks.rs b/src/iterators/chunks.rs index 4dd99f002..178ead7e0 100644 --- a/src/iterators/chunks.rs +++ b/src/iterators/chunks.rs @@ -59,10 +59,10 @@ impl<'a, A, D: Dimension> ExactChunks<'a, A, D> a.shape() ); for i in 0..a.ndim() { - a.layout.dim[i] /= chunk[i]; + a.parts.dim[i] /= chunk[i]; } - let inner_strides = a.layout.strides.clone(); - a.layout.strides *= &chunk; + let inner_strides = a.parts.strides.clone(); + a.parts.strides *= &chunk; ExactChunks { base: a, @@ -158,10 +158,10 @@ impl<'a, A, D: Dimension> ExactChunksMut<'a, A, D> a.shape() ); for i in 0..a.ndim() { - a.layout.dim[i] /= chunk[i]; + a.parts.dim[i] /= chunk[i]; } - let inner_strides = a.layout.strides.clone(); - a.layout.strides *= &chunk; + let inner_strides = a.parts.strides.clone(); + a.parts.strides *= &chunk; ExactChunksMut { base: a, diff --git a/src/iterators/into_iter.rs b/src/iterators/into_iter.rs index b51315a0f..cacafd2f2 100644 --- a/src/iterators/into_iter.rs +++ b/src/iterators/into_iter.rs @@ -36,12 +36,12 @@ where D: Dimension pub(crate) fn new(array: Array) -> Self { unsafe { - let array_head_ptr = array.ptr; + let array_head_ptr = array.parts.ptr; let mut array_data = array.data; let data_len = array_data.release_all_elements(); - debug_assert!(data_len >= array.layout.dim.size()); - let has_unreachable_elements = array.layout.dim.size() != data_len; - let inner = Baseiter::new(array_head_ptr, array.layout.dim, array.layout.strides); + debug_assert!(data_len >= array.parts.dim.size()); + let has_unreachable_elements = array.parts.dim.size() != data_len; + let inner = Baseiter::new(array_head_ptr, array.parts.dim, array.parts.strides); IntoIter { array_data, diff --git a/src/iterators/lanes.rs b/src/iterators/lanes.rs index 0f9678872..9fd39607b 100644 --- a/src/iterators/lanes.rs +++ b/src/iterators/lanes.rs @@ -46,8 +46,8 @@ impl<'a, A, D: Dimension> Lanes<'a, A, D> v.try_remove_axis(Axis(0)) } else { let i = axis.index(); - len = v.dim[i]; - stride = v.strides[i] as isize; + len = v.parts.dim[i]; + stride = v.parts.strides[i] as isize; v.try_remove_axis(axis) }; Lanes { @@ -115,8 +115,8 @@ impl<'a, A, D: Dimension> LanesMut<'a, A, D> v.try_remove_axis(Axis(0)) } else { let i = axis.index(); - len = v.dim[i]; - stride = v.strides[i] as isize; + len = v.parts.dim[i]; + stride = v.parts.strides[i] as isize; v.try_remove_axis(axis) }; LanesMut { diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index 55a9920a8..f7892a8c9 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -880,9 +880,9 @@ impl AxisIterCore index: 0, end: v.len_of(axis), stride: v.stride_of(axis), - inner_dim: v.dim.remove_axis(axis), - inner_strides: v.strides.remove_axis(axis), - ptr: v.ptr.as_ptr(), + inner_dim: v.parts.dim.remove_axis(axis), + inner_strides: v.parts.strides.remove_axis(axis), + ptr: v.parts.ptr.as_ptr(), } } @@ -1366,10 +1366,10 @@ fn chunk_iter_parts(v: ArrayView<'_, A, D>, axis: Axis, size: u }; let axis = axis.index(); - let mut inner_dim = v.dim.clone(); + let mut inner_dim = v.parts.dim.clone(); inner_dim[axis] = size; - let mut partial_chunk_dim = v.layout.dim; + let mut partial_chunk_dim = v.parts.dim; partial_chunk_dim[axis] = chunk_remainder; let partial_chunk_index = n_whole_chunks; @@ -1378,8 +1378,8 @@ fn chunk_iter_parts(v: ArrayView<'_, A, D>, axis: Axis, size: u end: iter_len, stride, inner_dim, - inner_strides: v.layout.strides, - ptr: v.layout.ptr.as_ptr(), + inner_strides: v.parts.strides, + ptr: v.parts.ptr.as_ptr(), }; (iter, partial_chunk_index, partial_chunk_dim) diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs index f3442c0af..e6fccce46 100644 --- a/src/iterators/windows.rs +++ b/src/iterators/windows.rs @@ -39,7 +39,7 @@ impl<'a, A, D: Dimension> Windows<'a, A, D> let window = window_size.into_dimension(); let strides = axis_strides.into_dimension(); - let window_strides = a.strides.clone(); + let window_strides = a.parts.strides.clone(); let base = build_base(a, window.clone(), strides); Windows { @@ -143,7 +143,7 @@ impl<'a, A, D: Dimension> AxisWindows<'a, A, D> { pub(crate) fn new_with_stride(a: ArrayView<'a, A, D>, axis: Axis, window_size: usize, stride_size: usize) -> Self { - let window_strides = a.strides.clone(); + let window_strides = a.parts.strides.clone(); let axis_idx = axis.index(); let mut window = a.raw_dim(); diff --git a/src/lib.rs b/src/lib.rs index e11a8a5ff..9f2c53d79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,12 +131,14 @@ extern crate cblas_sys; #[cfg(docsrs)] pub mod doc; +use alloc::fmt::Debug; #[cfg(target_has_atomic = "ptr")] use alloc::sync::Arc; #[cfg(not(target_has_atomic = "ptr"))] use portable_atomic_util::Arc; +use core::ptr::NonNull; use std::marker::PhantomData; pub use crate::dimension::dim::*; @@ -1299,7 +1301,41 @@ where S: RawData /// buffer; if borrowed, contains the lifetime and mutability.) data: S, /// The dimension, strides, and pointer to inside of `data` - layout: LayoutRef, + parts: ArrayPartsSized, +} + +/// A possibly-unsized container for array parts. +/// +/// This type only exists to enable holding the array parts in a single +/// type, which needs to be sized inside of `ArrayBase` and unsized inside +/// of the reference types. +#[derive(Debug)] +struct ArrayParts +{ + /// A non-null pointer into the buffer held by `data`; may point anywhere + /// in its range. If `S: Data`, this pointer must be aligned. + ptr: NonNull, + /// The lengths of the axes. + dim: D, + /// The element count stride per axis. To be parsed as `isize`. + strides: D, + _dst_control: T, +} + +type ArrayPartsSized = ArrayParts; +type ArrayPartsUnsized = ArrayParts; + +impl ArrayPartsSized +{ + const fn new(ptr: NonNull, dim: D, strides: D) -> ArrayPartsSized + { + Self { + ptr, + dim, + strides, + _dst_control: [], + } + } } /// A reference to the layout of an *n*-dimensional array. @@ -1401,16 +1437,28 @@ where S: RawData // which alter the layout / shape / strides of an array must also // alter the offset of the pointer. This is allowed, as it does not // cause a pointer deref. -#[derive(Debug)] -pub struct LayoutRef +#[repr(transparent)] +pub struct LayoutRef(ArrayPartsUnsized); + +impl LayoutRef { - /// A non-null pointer into the buffer held by `data`; may point anywhere - /// in its range. If `S: Data`, this pointer must be aligned. - ptr: std::ptr::NonNull, - /// The lengths of the axes. - dim: D, - /// The element count stride per axis. To be parsed as `isize`. - strides: D, + /// Get a reference to the data pointer. + fn _ptr(&self) -> &NonNull + { + &self.0.ptr + } + + /// Get a reference to the array's dimension. + fn _dim(&self) -> &D + { + &self.0.dim + } + + /// Get a reference to the array's strides. + fn _strides(&self) -> &D + { + &self.0.strides + } } /// A reference to an *n*-dimensional array whose data is safe to read and write. @@ -1757,7 +1805,7 @@ impl ArrayRef match self.broadcast(dim.clone()) { Some(it) => it, - None => broadcast_panic(&self.dim, &dim), + None => broadcast_panic(self._dim(), &dim), } } @@ -1769,10 +1817,10 @@ impl ArrayRef { let dim = dim.into_dimension(); debug_assert_eq!(self.shape(), dim.slice()); - let ptr = self.ptr; + let ptr = self._ptr(); let mut strides = dim.clone(); - strides.slice_mut().copy_from_slice(self.strides.slice()); - unsafe { ArrayView::new(ptr, dim, strides) } + strides.slice_mut().copy_from_slice(self._strides().slice()); + unsafe { ArrayView::new(*ptr, dim, strides) } } } @@ -1784,8 +1832,8 @@ where /// Remove array axis `axis` and return the result. fn try_remove_axis(self, axis: Axis) -> ArrayBase { - let d = self.layout.dim.try_remove_axis(axis); - let s = self.layout.strides.try_remove_axis(axis); + let d = self.parts.dim.try_remove_axis(axis); + let s = self.parts.strides.try_remove_axis(axis); // safe because new dimension, strides allow access to a subset of old data unsafe { self.with_strides_dim(s, d) } } diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index 1e4eb80ee..14c82ff4d 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -65,7 +65,7 @@ impl ArrayRef /// *Note:* If enabled, uses blas `dot` for elements of `f32, f64` when memory /// layout allows. #[track_caller] - pub fn dot(&self, rhs: &Rhs) -> >::Output + pub fn dot(&self, rhs: &Rhs) -> >::Output where Self: Dot { Dot::dot(self, rhs) @@ -110,9 +110,9 @@ impl ArrayRef if blas_compat_1d::<$ty, _>(self) && blas_compat_1d::<$ty, _>(rhs) { unsafe { let (lhs_ptr, n, incx) = - blas_1d_params(self.ptr.as_ptr(), self.len(), self.strides()[0]); + blas_1d_params(self._ptr().as_ptr(), self.len(), self.strides()[0]); let (rhs_ptr, _, incy) = - blas_1d_params(rhs.ptr.as_ptr(), rhs.len(), rhs.strides()[0]); + blas_1d_params(rhs._ptr().as_ptr(), rhs.len(), rhs.strides()[0]); let ret = blas_sys::$func( n, lhs_ptr as *const $ty, @@ -157,7 +157,7 @@ unsafe fn blas_1d_params(ptr: *const A, len: usize, stride: isize) -> (*const /// /// For two-dimensional arrays, the dot method computes the matrix /// multiplication. -pub trait Dot +pub trait Dot { /// The result of the operation. /// @@ -295,7 +295,7 @@ impl ArrayRef /// ); /// ``` #[track_caller] - pub fn dot(&self, rhs: &Rhs) -> >::Output + pub fn dot(&self, rhs: &Rhs) -> >::Output where Self: Dot { Dot::dot(self, rhs) @@ -471,12 +471,12 @@ where A: LinalgScalar n as blas_index, // n, cols of Op(b) k as blas_index, // k, cols of Op(a) gemm_scalar_cast!($ty, alpha), // alpha - a.ptr.as_ptr() as *const _, // a + a._ptr().as_ptr() as *const _, // a lda, // lda - b.ptr.as_ptr() as *const _, // b + b._ptr().as_ptr() as *const _, // b ldb, // ldb gemm_scalar_cast!($ty, beta), // beta - c.ptr.as_ptr() as *mut _, // c + c._ptr().as_ptr() as *mut _, // c ldc, // ldc ); } @@ -694,10 +694,10 @@ unsafe fn general_mat_vec_mul_impl( let cblas_layout = layout.to_cblas_layout(); // Low addr in memory pointers required for x, y - let x_offset = offset_from_low_addr_ptr_to_logical_ptr(&x.dim, &x.strides); - let x_ptr = x.ptr.as_ptr().sub(x_offset); - let y_offset = offset_from_low_addr_ptr_to_logical_ptr(&y.layout.dim, &y.layout.strides); - let y_ptr = y.layout.ptr.as_ptr().sub(y_offset); + let x_offset = offset_from_low_addr_ptr_to_logical_ptr(x._dim(), x._strides()); + let x_ptr = x._ptr().as_ptr().sub(x_offset); + let y_offset = offset_from_low_addr_ptr_to_logical_ptr(&y.parts.dim, &y.parts.strides); + let y_ptr = y.parts.ptr.as_ptr().sub(y_offset); let x_stride = x.strides()[0] as blas_index; let y_stride = y.strides()[0] as blas_index; @@ -708,7 +708,7 @@ unsafe fn general_mat_vec_mul_impl( m as blas_index, // m, rows of Op(a) k as blas_index, // n, cols of Op(a) cast_as(&alpha), // alpha - a.ptr.as_ptr() as *const _, // a + a._ptr().as_ptr() as *const _, // a a_stride, // lda x_ptr as *const _, // x x_stride, @@ -909,9 +909,9 @@ fn is_blas_2d(dim: &Ix2, stride: &Ix2, order: BlasOrder) -> bool #[cfg(feature = "blas")] fn get_blas_compatible_layout(a: &ArrayRef) -> Option { - if is_blas_2d(&a.dim, &a.strides, BlasOrder::C) { + if is_blas_2d(a._dim(), a._strides(), BlasOrder::C) { Some(BlasOrder::C) - } else if is_blas_2d(&a.dim, &a.strides, BlasOrder::F) { + } else if is_blas_2d(a._dim(), a._strides(), BlasOrder::F) { Some(BlasOrder::F) } else { None @@ -952,7 +952,7 @@ where if !same_type::() { return false; } - is_blas_2d(&a.dim, &a.strides, BlasOrder::C) + is_blas_2d(a._dim(), a._strides(), BlasOrder::C) } #[cfg(test)] @@ -965,7 +965,7 @@ where if !same_type::() { return false; } - is_blas_2d(&a.dim, &a.strides, BlasOrder::F) + is_blas_2d(a._dim(), a._strides(), BlasOrder::F) } #[cfg(test)] diff --git a/src/macro_utils.rs b/src/macro_utils.rs index 75360de37..34c700e65 100644 --- a/src/macro_utils.rs +++ b/src/macro_utils.rs @@ -61,7 +61,7 @@ macro_rules! expand_if { #[cfg(debug_assertions)] macro_rules! debug_bounds_check { ($self_:ident, $index:expr) => { - if $index.index_checked(&$self_.dim, &$self_.strides).is_none() { + if $index.index_checked(&$self_._dim(), &$self_._strides()).is_none() { panic!( "ndarray: index {:?} is out of bounds for array of shape {:?}", $index, @@ -75,3 +75,21 @@ macro_rules! debug_bounds_check { macro_rules! debug_bounds_check { ($self_:ident, $index:expr) => {}; } + +#[cfg(debug_assertions)] +macro_rules! debug_bounds_check_ref { + ($self_:ident, $index:expr) => { + if $index.index_checked(&$self_._dim(), &$self_._strides()).is_none() { + panic!( + "ndarray: index {:?} is out of bounds for array of shape {:?}", + $index, + $self_.shape() + ); + } + }; +} + +#[cfg(not(debug_assertions))] +macro_rules! debug_bounds_check_ref { + ($self_:ident, $index:expr) => {}; +} diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index ae82a482a..9709a5254 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -272,7 +272,7 @@ where D: Dimension A: Clone + Zero + Add, D: RemoveAxis, { - let min_stride_axis = self.dim.min_stride_axis(&self.strides); + let min_stride_axis = self._dim().min_stride_axis(self._strides()); if axis == min_stride_axis { crate::Zip::from(self.lanes(axis)).map_collect(|lane| lane.sum()) } else { @@ -309,7 +309,7 @@ where D: Dimension A: Clone + One + Mul, D: RemoveAxis, { - let min_stride_axis = self.dim.min_stride_axis(&self.strides); + let min_stride_axis = self._dim().min_stride_axis(self._strides()); if axis == min_stride_axis { crate::Zip::from(self.lanes(axis)).map_collect(|lane| lane.product()) } else { @@ -414,8 +414,8 @@ where D: Dimension the axis", ); let dof = n - ddof; - let mut mean = Array::::zeros(self.dim.remove_axis(axis)); - let mut sum_sq = Array::::zeros(self.dim.remove_axis(axis)); + let mut mean = Array::::zeros(self._dim().remove_axis(axis)); + let mut sum_sq = Array::::zeros(self._dim().remove_axis(axis)); for (i, subview) in self.axis_iter(axis).enumerate() { let count = A::from_usize(i + 1).expect("Converting index to `A` must not fail."); azip!((mean in &mut mean, sum_sq in &mut sum_sq, &x in &subview) { diff --git a/src/tri.rs b/src/tri.rs index 6e3b90b5b..9f15db71a 100644 --- a/src/tri.rs +++ b/src/tri.rs @@ -59,7 +59,7 @@ where // C-order array check prevents infinite recursion in edge cases like [[1]]. // k-size check prevents underflow when k == isize::MIN let n = self.ndim(); - if is_layout_f(&self.dim, &self.strides) && !is_layout_c(&self.dim, &self.strides) && k > isize::MIN { + if is_layout_f(self._dim(), self._strides()) && !is_layout_c(self._dim(), self._strides()) && k > isize::MIN { let mut x = self.view(); x.swap_axes(n - 2, n - 1); let mut tril = x.tril(-k); @@ -124,7 +124,7 @@ where // C-order array check prevents infinite recursion in edge cases like [[1]]. // k-size check prevents underflow when k == isize::MIN let n = self.ndim(); - if is_layout_f(&self.dim, &self.strides) && !is_layout_c(&self.dim, &self.strides) && k > isize::MIN { + if is_layout_f(self._dim(), self._strides()) && !is_layout_c(self._dim(), self._strides()) && k > isize::MIN { let mut x = self.view(); x.swap_axes(n - 2, n - 1); let mut triu = x.triu(-k); @@ -169,10 +169,10 @@ mod tests { let x = Array2::::ones((3, 3).f()); let res = x.triu(0); - assert!(dimension::is_layout_f(&res.dim, &res.strides)); + assert!(dimension::is_layout_f(&res.parts.dim, &res.parts.strides)); let res = x.tril(0); - assert!(dimension::is_layout_f(&res.dim, &res.strides)); + assert!(dimension::is_layout_f(&res.parts.dim, &res.parts.strides)); } #[test] diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 640a74d1b..668eac093 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -81,7 +81,7 @@ where D: Dimension { pub(crate) fn layout_impl(&self) -> Layout { - array_layout(&self.dim, &self.strides) + array_layout(self._dim(), self._strides()) } } @@ -95,7 +95,7 @@ where { #[allow(clippy::needless_borrow)] let res: ArrayView<'_, A, E::Dim> = (*self).broadcast_unwrap(shape.into_dimension()); - unsafe { ArrayView::new(res.layout.ptr, res.layout.dim, res.layout.strides) } + unsafe { ArrayView::new(res.parts.ptr, res.parts.dim, res.parts.strides) } } private_impl! {} } diff --git a/src/zip/ndproducer.rs b/src/zip/ndproducer.rs index de9d66979..fe666e81e 100644 --- a/src/zip/ndproducer.rs +++ b/src/zip/ndproducer.rs @@ -274,7 +274,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> fn equal_dim(&self, dim: &Self::Dim) -> bool { - self.dim.equal(dim) + self._dim().equal(dim) } fn as_ptr(&self) -> *mut A @@ -294,7 +294,9 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { - self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) + self._ptr() + .as_ptr() + .offset(i.index_unchecked(self._strides())) } fn stride_of(&self, axis: Axis) -> isize @@ -330,7 +332,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> fn equal_dim(&self, dim: &Self::Dim) -> bool { - self.dim.equal(dim) + self._dim().equal(dim) } fn as_ptr(&self) -> *mut A @@ -350,7 +352,9 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { - self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) + self._ptr() + .as_ptr() + .offset(i.index_unchecked(self._strides())) } fn stride_of(&self, axis: Axis) -> isize @@ -386,7 +390,7 @@ impl NdProducer for RawArrayView fn equal_dim(&self, dim: &Self::Dim) -> bool { - self.layout.dim.equal(dim) + self.parts.dim.equal(dim) } fn as_ptr(&self) -> *const A @@ -406,10 +410,10 @@ impl NdProducer for RawArrayView unsafe fn uget_ptr(&self, i: &Self::Dim) -> *const A { - self.layout + self.parts .ptr .as_ptr() - .offset(i.index_unchecked(&self.layout.strides)) + .offset(i.index_unchecked(&self.parts.strides)) } fn stride_of(&self, axis: Axis) -> isize @@ -445,7 +449,7 @@ impl NdProducer for RawArrayViewMut fn equal_dim(&self, dim: &Self::Dim) -> bool { - self.layout.dim.equal(dim) + self.parts.dim.equal(dim) } fn as_ptr(&self) -> *mut A @@ -465,10 +469,10 @@ impl NdProducer for RawArrayViewMut unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { - self.layout + self.parts .ptr .as_ptr() - .offset(i.index_unchecked(&self.layout.strides)) + .offset(i.index_unchecked(&self.parts.strides)) } fn stride_of(&self, axis: Axis) -> isize From 7c56e84f04e3f0f29267b952fc252699cd70391a Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 13:50:36 -0500 Subject: [PATCH 27/54] Update the docs and examples for the new `Sized` implementation (#1535) --- examples/functions_and_traits.rs | 227 +++++++++++++++++-------------- src/lib.rs | 6 +- 2 files changed, 126 insertions(+), 107 deletions(-) diff --git a/examples/functions_and_traits.rs b/examples/functions_and_traits.rs index 56ec1f2e2..9710a9ff5 100644 --- a/examples/functions_and_traits.rs +++ b/examples/functions_and_traits.rs @@ -7,120 +7,165 @@ //! 2. [`ArrayRef`], which represents a read-safe, uniquely-owned look at an array. //! 3. [`RawRef`], which represents a read-unsafe, possibly-shared look at an array. //! 4. [`LayoutRef`], which represents a look at an array's underlying structure, -//! but does not allow data reading of any kind. +//! but does not allow reading data of any kind. //! //! Below, we illustrate how to write functions and traits for most variants of these types. -use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef}; +use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawRef}; -/// Take an array with the most basic requirements. +/// First, the newest pattern: this function accepts arrays whose data are safe to +/// dereference and uniquely held. /// -/// This function takes its data as owning. It is very rare that a user will need to specifically -/// take a reference to an `ArrayBase`, rather than to one of the other four types. -#[rustfmt::skip] -fn takes_base_raw(arr: ArrayBase) -> ArrayBase +/// This is probably the most common pattern for users. +/// Once we have an array reference, we can go to [`RawRef`] and [`LayoutRef`] very easily. +fn takes_arrref(arr: &ArrayRef) { - // These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsRef` - takes_rawref(arr.as_ref()); // Caller uses `.as_ref` - takes_rawref_asref(&arr); // Implementor uses `.as_ref` - takes_layout(arr.as_ref()); // Caller uses `.as_ref` - takes_layout_asref(&arr); // Implementor uses `.as_ref` - - arr + // Since `ArrayRef` implements `Deref` to `RawRef`, we can pass `arr` directly to a function + // that takes `RawRef`. Similarly, since `RawRef` implements `Deref` to `LayoutRef`, we can pass + // `arr` directly to a function that takes `LayoutRef`. + takes_rawref(arr); // &ArrayRef -> &RawRef + takes_layout(arr); // &ArrayRef -> &RawRef -> &LayoutRef + + // We can also pass `arr` to functions that accept `RawRef` and `LayoutRef` via `AsRef`. + // These alternative function signatures are important for other types, but we see that when + // we have an `ArrayRef`, we can call them very simply. + takes_rawref_asref(arr); // &ArrayRef -> &RawRef + takes_layout_asref(arr); // &ArrayRef -> &LayoutRef } -/// Similar to above, but allow us to read the underlying data. -#[rustfmt::skip] -fn takes_base_raw_mut(mut arr: ArrayBase) -> ArrayBase +/// Now we want any array whose data is safe to mutate. +/// +/// Importantly, any array passed to this function is guaranteed to uniquely point to its data. +/// As a result, passing a shared array to this function will silently un-share the array. +/// So, ***users should only accept `&mut ArrayRef` when they want to mutate data***. +/// If they just want to mutate shape and strides, use `&mut LayoutRef` or `&AsMut`. +#[allow(dead_code)] +fn takes_arrref_mut(arr: &mut ArrayRef) { - // These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsMut` - takes_rawref_mut(arr.as_mut()); // Caller uses `.as_mut` - takes_rawref_asmut(&mut arr); // Implementor uses `.as_mut` - takes_layout_mut(arr.as_mut()); // Caller uses `.as_mut` - takes_layout_asmut(&mut arr); // Implementor uses `.as_mut` + // We can do everything we did with a `&ArrayRef` + takes_arrref(arr); - arr + // Similarly, we can pass this to functions that accept mutable references + // to our other array reference types. These first two happen via `Deref`... + takes_rawref_mut(arr); + takes_layout_mut(arr); + + // ... and these two happen via `AsRef`. + takes_rawref_asmut(arr); + takes_rawref_asmut(arr); } -/// Now take an array whose data is safe to read. +/// Now let's go back and look at the way to write functions prior to 0.17: using `ArrayBase`. +/// +/// This function signature says three things: +/// 1. Let me take a read only reference (that's the `&`) +/// 2. Of an array whose data is safe to dereference (that's the `S: Data`) +/// 3. And whose data is read-only (also `S: Data`) +/// +/// Let's see what we can do with this array: #[allow(dead_code)] -fn takes_base(mut arr: ArrayBase) -> ArrayBase +fn takes_base(arr: &ArrayBase) { - // Raw call - arr = takes_base_raw(arr); + // First off: we can pass it to functions that accept `&ArrayRef`. + // + // This is always "cheap", in the sense that even if `arr` is an + // `ArcArray` that shares its data, using this call will not un-share that data. + takes_arrref(arr); - // No need for AsRef, since data is safe - takes_arrref(&arr); + // We can also pass it to functions that accept `RawRef` and `LayoutRef` + // in the usual two ways: takes_rawref(&arr); - takes_rawref_asref(&arr); takes_layout(&arr); + // + takes_rawref_asref(&arr); takes_layout_asref(&arr); +} - arr +/// Now, let's take a mutable reference to an `ArrayBase` - but let's keep `S: Data`, such +/// that we are allowed to change the _layout_ of the array, but not its data. +fn takes_base_mut(arr: &mut ArrayBase) +{ + // Of course we can call everything we did with a immutable reference: + takes_base(arr); + + // However, we _can't_ call a function that takes `&mut ArrayRef`: + // this would require mutable data access, which `S: Data` does not provide. + // + // takes_arrref_mut(arr); + // rustc: cannot borrow data in dereference of `ArrayBase` as mutable + // + // Nor can we call a function that takes `&mut RawRef` + // takes_rawref_mut(arr); + + // We can, however, call functions that take `AsMut`, + // since `LayoutRef` does not provide read access to the data: + takes_layout_mut(arr.as_layout_ref_mut()); + // + takes_layout_asmut(arr); } -/// Now, an array whose data is safe to read and that we can mutate. +/// Finally, let's look at a mutable reference to an `ArrayBase` with `S: DataMut`. /// -/// Notice that we include now a trait bound on `D: Dimension`; this is necessary in order -/// for the `ArrayBase` to dereference to an `ArrayRef` (or to any of the other types). +/// Note that we require a constraint of `D: Dimension` to dereference to `&mut ArrayRef`. #[allow(dead_code)] -fn takes_base_mut(mut arr: ArrayBase) -> ArrayBase +fn takes_base_data_mut(arr: &mut ArrayBase) { - // Raw call - arr = takes_base_raw_mut(arr); + // Of course, everything we can do with just `S: Data`: + takes_base_mut(arr); + + // But also, we can now call functions that take `&mut ArrayRef`. + // + // NOTE: If `arr` is actually an `ArcArray` with shared data, this + // will un-share the data. This can be a potentially costly operation. + takes_arrref_mut(arr); +} - // No need for AsMut, since data is safe - takes_arrref_mut(&mut arr); - takes_rawref_mut(&mut arr); - takes_rawref_asmut(&mut arr); - takes_layout_mut(&mut arr); - takes_layout_asmut(&mut arr); +/// Let's now look at writing functions for the new `LayoutRef` type. We'll do this for both +/// immutable and mutable references, and we'll see how there are two different ways to accept +/// these types. +/// +/// These functions can only read/modify an array's shape or strides, +/// such as checking dimensionality or slicing, should take `LayoutRef`. +/// +/// Our first way is to accept an immutable reference to `LayoutRef`: +#[allow(dead_code)] +fn takes_layout(_arr: &LayoutRef) {} - arr -} +/// We can also directly take a mutable reference to `LayoutRef`. +#[allow(dead_code)] +fn takes_layout_mut(_arr: &mut LayoutRef) {} -/// Now for new stuff: we want to read (but not alter) any array whose data is safe to read. +/// However, the preferred way to write these functions is by accepting +/// generics using `AsRef`. /// -/// This is probably the most common functionality that one would want to write. -/// As we'll see below, calling this function is very simple for `ArrayBase`. -fn takes_arrref(arr: &ArrayRef) +/// For immutable access, writing with `AsRef` has the same benefit as usual: +/// callers have nicer ergonomics, since they can just pass any type +/// without having to call `.as_ref` or `.as_layout_ref`. +#[allow(dead_code)] +fn takes_layout_asref(_arr: &T) +where T: AsRef> + ?Sized { - // No need for AsRef, since data is safe - takes_rawref(arr); - takes_rawref_asref(arr); - takes_layout(arr); - takes_layout_asref(arr); } -/// Now we want any array whose data is safe to mutate. -/// -/// **Importantly**, any array passed to this function is guaranteed to uniquely point to its data. -/// As a result, passing a shared array to this function will **silently** un-share the array. +/// For mutable access, there is an additional reason to write with `AsMut`: +/// it prevents callers who are passing in `ArcArray` or other shared array types +/// from accidentally unsharing the data through a deref chain: +/// `&mut ArcArray --(unshare)--> &mut ArrayRef -> &mut RawRef -> &mut LayoutRef`. #[allow(dead_code)] -fn takes_arrref_mut(arr: &mut ArrayRef) +fn takes_layout_asmut(_arr: &mut T) +where T: AsMut> + ?Sized { - // Immutable call - takes_arrref(arr); - - // No need for AsMut, since data is safe - takes_rawref_mut(arr); - takes_rawref_asmut(arr); - takes_layout_mut(arr); - takes_rawref_asmut(arr); } -/// Now, we no longer care about whether we can safely read data. +/// Finally, we have `RawRef`, where we can access and mutate the array's data, but only unsafely. +/// This is important for, e.g., dealing with [`std::mem::MaybeUninit`]. /// /// This is probably the rarest type to deal with, since `LayoutRef` can access and modify an array's /// shape and strides, and even do in-place slicing. As a result, `RawRef` is only for functionality /// that requires unsafe data access, something that `LayoutRef` can't do. /// -/// Writing functions and traits that deal with `RawRef`s and `LayoutRef`s can be done two ways: -/// 1. Directly on the types; calling these functions on arrays whose data are not known to be safe -/// to dereference (i.e., raw array views or `ArrayBase`) must explicitly call `.as_ref()`. -/// 2. Via a generic with `: AsRef>`; doing this will allow direct calling for all `ArrayBase` and -/// `ArrayRef` instances. -/// We'll demonstrate #1 here for both immutable and mutable references, then #2 directly below. +/// Like `LayoutRef`, writing functions with `RawRef` can be done in a few ways. +/// We start with a direct, immutable reference #[allow(dead_code)] fn takes_rawref(arr: &RawRef) { @@ -128,7 +173,7 @@ fn takes_rawref(arr: &RawRef) takes_layout_asref(arr); } -/// Mutable, directly take `RawRef` +/// We can also directly take a mutable reference. #[allow(dead_code)] fn takes_rawref_mut(arr: &mut RawRef) { @@ -136,7 +181,8 @@ fn takes_rawref_mut(arr: &mut RawRef) takes_layout_asmut(arr); } -/// Immutable, take a generic that implements `AsRef` to `RawRef` +/// However, like before, the preferred way is to write with `AsRef`, +/// for the same reasons as for `LayoutRef`: #[allow(dead_code)] fn takes_rawref_asref(_arr: &T) where T: AsRef> + ?Sized @@ -145,7 +191,7 @@ where T: AsRef> + ?Sized takes_layout_asref(_arr.as_ref()); } -/// Mutable, take a generic that implements `AsMut` to `RawRef` +/// Finally, mutably: #[allow(dead_code)] fn takes_rawref_asmut(_arr: &mut T) where T: AsMut> + ?Sized @@ -154,31 +200,4 @@ where T: AsMut> + ?Sized takes_layout_asmut(_arr.as_mut()); } -/// Finally, there's `LayoutRef`: this type provides read and write access to an array's *structure*, but not its *data*. -/// -/// Practically, this means that functions that only read/modify an array's shape or strides, -/// such as checking dimensionality or slicing, should take `LayoutRef`. -/// -/// Like `RawRef`, functions can be written either directly on `LayoutRef` or as generics with `: AsRef>>`. -#[allow(dead_code)] -fn takes_layout(_arr: &LayoutRef) {} - -/// Mutable, directly take `LayoutRef` -#[allow(dead_code)] -fn takes_layout_mut(_arr: &mut LayoutRef) {} - -/// Immutable, take a generic that implements `AsRef` to `LayoutRef` -#[allow(dead_code)] -fn takes_layout_asref(_arr: &T) -where T: AsRef> + ?Sized -{ -} - -/// Mutable, take a generic that implements `AsMut` to `LayoutRef` -#[allow(dead_code)] -fn takes_layout_asmut(_arr: &mut T) -where T: AsMut> + ?Sized -{ -} - fn main() {} diff --git a/src/lib.rs b/src/lib.rs index 9f2c53d79..baa62ca5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1353,7 +1353,7 @@ impl ArrayPartsSized /// use ndarray::{LayoutRef2, array}; /// /// fn aspect_ratio(layout: &T) -> (usize, usize) -/// where T: AsRef> +/// where T: AsRef> + ?Sized /// { /// let layout = layout.as_ref(); /// (layout.ncols(), layout.nrows()) @@ -1380,7 +1380,7 @@ impl ArrayPartsSized /// } /// /// impl Ratioable for T -/// where T: AsRef> + AsMut> +/// where T: AsRef> + AsMut> + ?Sized /// { /// fn aspect_ratio(&self) -> (usize, usize) /// { @@ -1420,7 +1420,7 @@ impl ArrayPartsSized /// expensive) guarantee that the data is uniquely held (see [`ArrayRef`] /// for more information). /// -/// To help users avoid this error cost, functions that operate on `LayoutRef`s +/// To help users avoid this cost, functions that operate on `LayoutRef`s /// should take their parameters as a generic type `T: AsRef>`, /// as the above examples show. This aids the caller in two ways: they can pass /// their arrays by reference (`&arr`) instead of explicitly calling `as_ref`, From dc331a7416e5de32b40b417a5397620f8e918056 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 14:14:40 -0500 Subject: [PATCH 28/54] Fix clippy lint `cast_slice_from_raw_parts` (#1536) --- src/partial.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/partial.rs b/src/partial.rs index 4509e77dc..1a1679ec5 100644 --- a/src/partial.rs +++ b/src/partial.rs @@ -93,7 +93,7 @@ impl Drop for Partial { if !self.ptr.is_null() { unsafe { - ptr::drop_in_place(alloc::slice::from_raw_parts_mut(self.ptr, self.len)); + ptr::drop_in_place(core::ptr::slice_from_raw_parts_mut(self.ptr, self.len)); } } } From 1579da4faaf25cf3375b3201164c1a03fbf0cc2a Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 17:02:46 -0500 Subject: [PATCH 29/54] Document + ?Sized in a few more places (#1537) From 85e712f65c714888adbd9405e35aed8edbe107b4 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 17:15:55 -0500 Subject: [PATCH 30/54] Add a changelog for 0.17.1 (#1538) --- RELEASES.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 1f78a4ce7..cd335fb86 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,32 @@ -Version 0.17.0 (2025-10-14) +Version 0.17.1 (2025-11-02) +=========================== +Version 0.17.1 provides a patch to fix the originally-unsound implementation of the new array reference types. + +The reference types are now all unsized. +Practically speaking, this has one major implication: writing functions and traits that accept `RawRef` and `LayoutRef` will now need a `+ ?Sized` bound to work ergonomically with `ArrayRef`. +For example, the release notes for 0.17.0 said +> #### Reading / Writing Shape: `LayoutRef` +> LayoutRef lets functions view or modify shape/stride information without touching data. +> This replaces verbose signatures like: +> ```rust +> fn alter_view(a: &mut ArrayBase) +> where S: Data; +> ``` +> Use AsRef / AsMut for best compatibility: +> ```rust +> fn alter_shape(a: &mut T) +> where T: AsMut>; +> ``` +However, these functions now need an additional bound to allow for callers to pass in `&ArrayRef` types: +```rust +fn alter_shape(a: &mut T) +where T: AsMut> + ?Sized; // Added bound here +``` + +A huge thank you to Sarah Quiñones ([@sarah-quinones](https://github.com/sarah-quinones)) for catching the original unsound bug and helping to fix it. +She does truly excellent work with [`faer-rs`](https://codeberg.org/sarah-quinones/faer); check it out! + +Version 0.17.0 (2025-10-14) [YANKED] =========================== Version 0.17.0 introduces a new **array reference type** — the preferred way to write functions and extension traits in `ndarray`. This release is fully backwards-compatible but represents a major usability improvement. From 66dc0e16a5323408102a8f7c1e5b70183f1a3634 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sun, 2 Nov 2025 17:17:05 -0500 Subject: [PATCH 31/54] chore: Release --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b82cbe288..53d568a32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,7 +457,7 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.17.0" +version = "0.17.1" dependencies = [ "approx", "cblas-sys", diff --git a/Cargo.toml b/Cargo.toml index 0223226db..64f18ae34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ndarray" -version = "0.17.0" +version = "0.17.1" edition = "2021" rust-version = "1.64" authors = [ From 903cb23426eafeaff4c97767e0da4c7199524f30 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 21:22:21 -0500 Subject: [PATCH 32/54] Extend `ndarray-rand` to be able to randomly sample from `ArrayRef` (#1540) Prior to ndarray 0.17, the RandomExt trait exposed by ndarray-rand contained methods for both creating new arrays randomly whole-cloth (random_using) and sampling from existing arrays (sample_axis_using). With the introduction of reference types in ndarray 0.17, users should be able to sample from ArrayRef instances as well. We choose to expose an additional extension trait, RandomRefExt, that provides this functionality. We keep the methods on the old trait for backwards compatibility, but collapse the implementation and documentation to the new trait to maintain a single source of truth. --- ndarray-rand/src/lib.rs | 77 ++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/ndarray-rand/src/lib.rs b/ndarray-rand/src/lib.rs index 8ee2cda75..d155695aa 100644 --- a/ndarray-rand/src/lib.rs +++ b/ndarray-rand/src/lib.rs @@ -29,12 +29,14 @@ //! that the items are not compatible (e.g. that a type doesn't implement a //! necessary trait). +#![warn(missing_docs)] + use crate::rand::distr::{Distribution, Uniform}; use crate::rand::rngs::SmallRng; use crate::rand::seq::index; use crate::rand::{rng, Rng, SeedableRng}; -use ndarray::{Array, Axis, RemoveAxis, ShapeBuilder}; +use ndarray::{Array, ArrayRef, Axis, RemoveAxis, ShapeBuilder}; use ndarray::{ArrayBase, Data, DataOwned, Dimension, RawData}; #[cfg(feature = "quickcheck")] use quickcheck::{Arbitrary, Gen}; @@ -51,18 +53,15 @@ pub mod rand_distr pub use rand_distr::*; } -/// Constructors for n-dimensional arrays with random elements. -/// -/// This trait extends ndarray’s `ArrayBase` and can not be implemented -/// for other types. +/// Extension trait for constructing n-dimensional arrays with random elements. /// /// The default RNG is a fast automatically seeded rng (currently -/// [`rand::rngs::SmallRng`], seeded from [`rand::thread_rng`]). +/// [`rand::rngs::SmallRng`], seeded from [`rand::rng`]). /// /// Note that `SmallRng` is cheap to initialize and fast, but it may generate /// low-quality random numbers, and reproducibility is not guaranteed. See its /// documentation for information. You can select a different RNG with -/// [`.random_using()`](Self::random_using). +/// [`.random_using()`](RandomExt::random_using). pub trait RandomExt where S: RawData, @@ -124,6 +123,40 @@ where S: DataOwned, Sh: ShapeBuilder; + /// Sample `n_samples` lanes slicing along `axis` using the default RNG. + /// + /// See [`RandomRefExt::sample_axis`] for additional information. + fn sample_axis(&self, axis: Axis, n_samples: usize, strategy: SamplingStrategy) -> Array + where + A: Copy, + S: Data, + D: RemoveAxis; + + /// Sample `n_samples` lanes slicing along `axis` using the specified RNG `rng`. + /// + /// See [`RandomRefExt::sample_axis_using`] for additional information. + fn sample_axis_using( + &self, axis: Axis, n_samples: usize, strategy: SamplingStrategy, rng: &mut R, + ) -> Array + where + R: Rng + ?Sized, + A: Copy, + S: Data, + D: RemoveAxis; +} + +/// Extension trait for sampling from [`ArrayRef`] with random elements. +/// +/// The default RNG is a fast, automatically seeded rng (currently +/// [`rand::rngs::SmallRng`], seeded from [`rand::rng`]). +/// +/// Note that `SmallRng` is cheap to initialize and fast, but it may generate +/// low-quality random numbers, and reproducibility is not guaranteed. See its +/// documentation for information. You can select a different RNG with +/// [`.sample_axis_using()`](RandomRefExt::sample_axis_using). +pub trait RandomRefExt +where D: Dimension +{ /// Sample `n_samples` lanes slicing along `axis` using the default RNG. /// /// If `strategy==SamplingStrategy::WithoutReplacement`, each lane can only be sampled once. @@ -168,7 +201,6 @@ where fn sample_axis(&self, axis: Axis, n_samples: usize, strategy: SamplingStrategy) -> Array where A: Copy, - S: Data, D: RemoveAxis; /// Sample `n_samples` lanes slicing along `axis` using the specified RNG `rng`. @@ -225,7 +257,6 @@ where where R: Rng + ?Sized, A: Copy, - S: Data, D: RemoveAxis; } @@ -259,7 +290,7 @@ where S: Data, D: RemoveAxis, { - self.sample_axis_using(axis, n_samples, strategy, &mut get_rng()) + (**self).sample_axis(axis, n_samples, strategy) } fn sample_axis_using(&self, axis: Axis, n_samples: usize, strategy: SamplingStrategy, rng: &mut R) -> Array @@ -268,6 +299,27 @@ where A: Copy, S: Data, D: RemoveAxis, + { + (**self).sample_axis_using(axis, n_samples, strategy, rng) + } +} + +impl RandomRefExt for ArrayRef +where D: Dimension +{ + fn sample_axis(&self, axis: Axis, n_samples: usize, strategy: SamplingStrategy) -> Array + where + A: Copy, + D: RemoveAxis, + { + self.sample_axis_using(axis, n_samples, strategy, &mut get_rng()) + } + + fn sample_axis_using(&self, axis: Axis, n_samples: usize, strategy: SamplingStrategy, rng: &mut R) -> Array + where + R: Rng + ?Sized, + A: Copy, + D: RemoveAxis, { let indices: Vec<_> = match strategy { SamplingStrategy::WithReplacement => { @@ -284,9 +336,10 @@ where /// if lanes from the original array should only be sampled once (*without replacement*) or /// multiple times (*with replacement*). /// -/// [`sample_axis`]: RandomExt::sample_axis -/// [`sample_axis_using`]: RandomExt::sample_axis_using +/// [`sample_axis`]: RandomRefExt::sample_axis +/// [`sample_axis_using`]: RandomRefExt::sample_axis_using #[derive(Debug, Clone)] +#[allow(missing_docs)] pub enum SamplingStrategy { WithReplacement, From 306addaa8380f814502be03ebcf208babef67c2e Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 2 Nov 2025 21:26:15 -0500 Subject: [PATCH 33/54] Add release log for ndarray-rand 0.16.0 (#1541) --- ndarray-rand/RELEASES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ndarray-rand/RELEASES.md b/ndarray-rand/RELEASES.md index cff9acd96..d4e1dde2e 100644 --- a/ndarray-rand/RELEASES.md +++ b/ndarray-rand/RELEASES.md @@ -1,6 +1,12 @@ Recent Changes -------------- +- 0.16.0 + + - Require ndarray 0.17.1 + - Bump `rand` to 0.9.0 and `rand_distr` to 0.5.0 + - Add an additional extension trait, `RandomRefExt`, to allow sampling from `ndarray::ArrayRef` instances + - 0.15.0 - Require ndarray 0.16 From 0e1d04ba7dd81fad83783a043b601df5e6a7f9da Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Mon, 3 Nov 2025 19:41:23 -0500 Subject: [PATCH 34/54] chore: Release --- Cargo.lock | 2 +- ndarray-rand/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53d568a32..bbc041a49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "ndarray-rand" -version = "0.15.0" +version = "0.16.0" dependencies = [ "ndarray", "quickcheck", diff --git a/ndarray-rand/Cargo.toml b/ndarray-rand/Cargo.toml index 3cafb552e..223dcdfc9 100644 --- a/ndarray-rand/Cargo.toml +++ b/ndarray-rand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ndarray-rand" -version = "0.15.0" +version = "0.16.0" edition = "2018" authors = ["bluss"] license = "MIT OR Apache-2.0" From 8ff477f6ef4350599effc2b8fbb984e63ad7ad9f Mon Sep 17 00:00:00 2001 From: tinyfoolish Date: Fri, 14 Nov 2025 02:36:54 +0800 Subject: [PATCH 35/54] chore: fix some typos in comments (#1547) Signed-off-by: tinyfoolish --- examples/type_conversion.rs | 2 +- src/impl_owned_array.rs | 8 ++++---- src/impl_ref_types.rs | 2 +- src/lib.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/type_conversion.rs b/examples/type_conversion.rs index a419af740..cfcd7e564 100644 --- a/examples/type_conversion.rs +++ b/examples/type_conversion.rs @@ -11,7 +11,7 @@ fn main() { // Converting an array from one datatype to another is implemented with the // `ArrayBase::mapv()` function. We pass a closure that is applied to each - // element independently. This allows for more control and flexiblity in + // element independently. This allows for more control and flexibility in // converting types. // // Below, we illustrate four different approaches for the actual conversion diff --git a/src/impl_owned_array.rs b/src/impl_owned_array.rs index 277b156b8..fb06f9656 100644 --- a/src/impl_owned_array.rs +++ b/src/impl_owned_array.rs @@ -176,7 +176,7 @@ impl Array /// along the "growing axis". However, if the memory layout needs adjusting, the array must /// reallocate and move memory. /// - /// The operation leaves the existing data in place and is most efficent if one of these is + /// The operation leaves the existing data in place and is most efficient if one of these is /// true: /// /// - The axis being appended to is the longest stride axis, i.e the array is in row major @@ -221,7 +221,7 @@ impl Array /// along the "growing axis". However, if the memory layout needs adjusting, the array must /// reallocate and move memory. /// - /// The operation leaves the existing data in place and is most efficent if one of these is + /// The operation leaves the existing data in place and is most efficient if one of these is /// true: /// /// - The axis being appended to is the longest stride axis, i.e the array is in column major @@ -510,7 +510,7 @@ where D: Dimension /// along the "growing axis". However, if the memory layout needs adjusting, the array must /// reallocate and move memory. /// - /// The operation leaves the existing data in place and is most efficent if `axis` is a + /// The operation leaves the existing data in place and is most efficient if `axis` is a /// "growing axis" for the array, i.e. one of these is true: /// /// - The axis is the longest stride axis, for example the 0th axis in a C-layout or the @@ -566,7 +566,7 @@ where D: Dimension /// along the "growing axis". However, if the memory layout needs adjusting, the array must /// reallocate and move memory. /// - /// The operation leaves the existing data in place and is most efficent if `axis` is a + /// The operation leaves the existing data in place and is most efficient if `axis` is a /// "growing axis" for the array, i.e. one of these is true: /// /// - The axis is the longest stride axis, for example the 0th axis in a C-layout or the diff --git a/src/impl_ref_types.rs b/src/impl_ref_types.rs index bfdfa27f9..108ac68bf 100644 --- a/src/impl_ref_types.rs +++ b/src/impl_ref_types.rs @@ -16,7 +16,7 @@ //! Because raw views do not meet `S: Data`, they cannot dereference to `ArrayRef`; furthermore, //! technical limitations of Rust's compiler means that `ArrayBase` cannot have multiple `Deref` implementations. //! In addition, shared-data arrays do not want to go down the `Deref` path to get to methods on `RawRef` -//! or `LayoutRef`, since that would unecessarily ensure their uniqueness. +//! or `LayoutRef`, since that would unnecessarily ensure their uniqueness. //! //! To mitigate these problems, `ndarray` also provides `AsRef` and `AsMut` implementations as follows: //! 1. `ArrayBase` implements `AsRef` to `RawRef` and `LayoutRef` when `S: RawData` diff --git a/src/lib.rs b/src/lib.rs index baa62ca5b..6a5ea8280 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1585,7 +1585,7 @@ pub type ArcArray = ArrayBase, D>; /// /// + [Constructor Methods for Owned Arrays](ArrayBase#constructor-methods-for-owned-arrays) /// + [Methods For All Array Types](ArrayBase#methods-for-all-array-types) -/// + Dimensionality-specific type alises +/// + Dimensionality-specific type aliases /// [`Array1`], /// [`Array2`], /// [`Array3`], ..., From f18c67e98badfba8ad89462d4c158c0c6e3214ea Mon Sep 17 00:00:00 2001 From: gaumut Date: Wed, 10 Dec 2025 15:40:40 +0100 Subject: [PATCH 36/54] Implement Sync for ArrayParts (#1552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement Sync for ArrayParts * Implement Send for ArrayRef --------- Co-authored-by: Jean Commère --- src/arraytraits.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/arraytraits.rs b/src/arraytraits.rs index da87e3a58..498da0f9c 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -433,6 +433,10 @@ where { } +unsafe impl Sync for ArrayRef where A: Sync {} + +unsafe impl Send for ArrayRef where A: Send {} + #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] // Use version number so we can add a packed format later. From e2c76e83eb4d64923b9e1ae78f6cafb725a72345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Wieczoreck?= Date: Tue, 16 Dec 2025 17:36:02 +0100 Subject: [PATCH 37/54] Only include files that are necessary for crates.io (#1553) --- Cargo.toml | 9 ++++++++- ndarray-rand/Cargo.toml | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 64f18ae34..efc035f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,14 @@ description = "An n-dimensional array for general elements and for numerics. Lig keywords = ["array", "data-structure", "multidimensional", "matrix", "blas"] categories = ["data-structures", "science"] -exclude = ["docgen/images/*"] +include = [ + "/src/**/*.rs", + "LICENSE-MIT", + "LICENSE-APACHE", + "RELEASES.md", + "README.rst", + "README-quick-start.md" +] resolver = "2" [lib] diff --git a/ndarray-rand/Cargo.toml b/ndarray-rand/Cargo.toml index 223dcdfc9..b7169f304 100644 --- a/ndarray-rand/Cargo.toml +++ b/ndarray-rand/Cargo.toml @@ -13,6 +13,14 @@ description = "Constructors for randomized arrays. `rand` integration for `ndarr keywords = ["multidimensional", "matrix", "rand", "ndarray"] +include = [ + "/src/**/*.rs", + "LICENSE-MIT", + "LICENSE-APACHE", + "RELEASES.md", + "README.rst" +] + [dependencies] ndarray = { workspace = true } From dab3be885344fa74aed368767651493b244c21bb Mon Sep 17 00:00:00 2001 From: Varchas Gopalaswamy <2359219+varchasgopalaswamy@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:30:10 -0500 Subject: [PATCH 38/54] Add type aliases for higher-dimensional ArcArrays (#1561) --- src/aliases.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/aliases.rs b/src/aliases.rs index 7f897304b..7dc6fe8e4 100644 --- a/src/aliases.rs +++ b/src/aliases.rs @@ -195,3 +195,13 @@ pub type ArrayViewMutD<'a, A> = ArrayViewMut<'a, A, IxDyn>; pub type ArcArray1 = ArcArray; /// two-dimensional shared ownership array pub type ArcArray2 = ArcArray; +/// three-dimensional shared ownership array +pub type ArcArray3 = ArcArray; +/// four-dimensional shared ownership array +pub type ArcArray4 = ArcArray; +/// five-dimensional shared ownership array +pub type ArcArray5 = ArcArray; +/// six-dimensional shared ownership array +pub type ArcArray6 = ArcArray; +/// dynamic-dimensional shared ownership array +pub type ArcArrayD = ArcArray; From e039158c5a09ddd111676565013d53b4d3d0fc6a Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 4 Jan 2026 07:28:05 -0800 Subject: [PATCH 39/54] Add additional commits to ignore on git blame (#1562) All of these are formatting, linting, etc fixes. --- .git-blame-ignore-revs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 6d0b5bdb0..acea88f02 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,15 @@ # rustfmt codebase (gh-1375) d07f5f33800e5240e7edb02bdbc4815ab30ef37e + +# rustfmt codebase (gh-635) +eb82c93f0147df38e061597221ece3627f119a60 + +# cargo fix --edition-idioms (gh-666) +483200bccfb903a31a3befbb737d4a907862f8dc + +# clippy linting fixes (gh-1171) +c66d408d44eff0e35de2719d3a6d9a9dad95dce1 +d9cf511e494c32507608a399257a72aaa89e8873 + +# lifetime elisions and clippy complaints (gh-1458) +9c703ac8a7f86dce8b0b5949731b2bf364230851 From 13a896372175d94215852f4197bacbcb75f918bb Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 4 Jan 2026 10:28:49 -0500 Subject: [PATCH 40/54] Add PartialEq implementations between ArrayRef and ArrayBase (#1557) This will allow users to ergonomically compare the two types without over-verbose dereferences. Closes #1549 --- src/arraytraits.rs | 109 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/src/arraytraits.rs b/src/arraytraits.rs index 498da0f9c..08397bf9c 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -207,7 +207,6 @@ where /// Return `true` if the array shapes and all elements of `self` and /// `rhs` are equal. Return `false` otherwise. -#[allow(clippy::unconditional_recursion)] // false positive impl PartialEq<&ArrayBase> for ArrayBase where A: PartialEq, @@ -223,7 +222,6 @@ where /// Return `true` if the array shapes and all elements of `self` and /// `rhs` are equal. Return `false` otherwise. -#[allow(clippy::unconditional_recursion)] // false positive impl PartialEq> for &ArrayBase where A: PartialEq, @@ -236,7 +234,6 @@ where **self == *rhs } } - impl Eq for ArrayBase where D: Dimension, @@ -245,6 +242,78 @@ where { } +impl PartialEq> for ArrayBase +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &ArrayRef) -> bool + { + **self == other + } +} + +impl PartialEq<&ArrayRef> for ArrayBase +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &&ArrayRef) -> bool + { + **self == *other + } +} + +impl PartialEq> for &ArrayBase +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &ArrayRef) -> bool + { + **self == other + } +} + +impl PartialEq> for ArrayRef +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &ArrayBase) -> bool + { + self == **other + } +} + +impl PartialEq<&ArrayBase> for ArrayRef +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &&ArrayBase) -> bool + { + self == ***other + } +} + +impl PartialEq> for &ArrayRef +where + S: Data, + A: PartialEq, + D: Dimension, +{ + fn eq(&self, other: &ArrayBase) -> bool + { + *self == **other + } +} + impl From> for ArrayBase where S: DataOwned { @@ -647,3 +716,37 @@ where ArrayBase::default(D::default()) } } + +#[cfg(test)] +mod tests +{ + use crate::array; + use alloc::vec; + + #[test] + fn test_eq_traits() + { + let a = array![1, 2, 3]; + let a_ref = &*a; + let b = array![1, 2, 3]; + let b_ref = &*b; + + assert_eq!(a, b); + assert_eq!(a, &b); + assert_eq!(&a, b); + assert_eq!(&a, &b); + + assert_eq!(a_ref, b_ref); + assert_eq!(&a_ref, b_ref); + assert_eq!(a_ref, &b_ref); + assert_eq!(&a_ref, &b_ref); + + assert_eq!(a_ref, b); + assert_eq!(a_ref, &b); + assert_eq!(&a_ref, &b); + + assert_eq!(a, b_ref); + assert_eq!(&a, b_ref); + assert_eq!(&a, &b_ref); + } +} From d573745d6adee1e96aca8a6d9f47cd48316d331b Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 4 Jan 2026 11:27:19 -0500 Subject: [PATCH 41/54] Rename Layout to LayoutBitset (#1563) In preparation for reworking the inner dimension types (see https://github.com/rust-ndarray/ndarray/issues/1506), we'd like to prepare the name `Layout` to be used for new traits and types. Although the type is largely internal (with hidden documentation), we opt for a conservative deprecation approach. In the future, we should consider making `LayoutBitset` entirely internal, but this would require larger changes to the codebase. --- src/indexes.rs | 8 ++-- src/iterators/chunks.rs | 2 +- src/iterators/lanes.rs | 2 +- src/iterators/macros.rs | 2 +- src/iterators/mod.rs | 8 ++-- src/iterators/windows.rs | 4 +- src/layout/layoutfmt.rs | 4 +- src/layout/mod.rs | 89 ++++++++++++++++++----------------- src/lib.rs | 3 +- src/parallel/send_producer.rs | 4 +- src/zip/mod.rs | 32 ++++++------- src/zip/ndproducer.rs | 12 ++--- 12 files changed, 88 insertions(+), 82 deletions(-) diff --git a/src/indexes.rs b/src/indexes.rs index 0fa2b50fb..a73c7fcb4 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -10,7 +10,7 @@ use crate::dimension::IntoDimension; use crate::split_at::SplitAt; use crate::zip::Offset; use crate::Axis; -use crate::Layout; +use crate::LayoutBitset; use crate::NdProducer; use crate::{ArrayBase, Data}; @@ -193,12 +193,12 @@ impl NdProducer for Indices IndexPtr { index: self.start } } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { if self.dim.ndim() <= 1 { - Layout::one_dimensional() + LayoutBitset::one_dimensional() } else { - Layout::none() + LayoutBitset::none() } } diff --git a/src/iterators/chunks.rs b/src/iterators/chunks.rs index 178ead7e0..aff194d53 100644 --- a/src/iterators/chunks.rs +++ b/src/iterators/chunks.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use crate::imp_prelude::*; use crate::Baseiter; use crate::IntoDimension; -use crate::{Layout, NdProducer}; +use crate::{LayoutBitset, NdProducer}; impl_ndproducer! { ['a, A, D: Dimension] diff --git a/src/iterators/lanes.rs b/src/iterators/lanes.rs index 9fd39607b..2f5e14774 100644 --- a/src/iterators/lanes.rs +++ b/src/iterators/lanes.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::LanesIter; use super::LanesIterMut; use crate::imp_prelude::*; -use crate::{Layout, NdProducer}; +use crate::{LayoutBitset, NdProducer}; impl_ndproducer! { ['a, A, D: Dimension] diff --git a/src/iterators/macros.rs b/src/iterators/macros.rs index 78697ec25..041d09021 100644 --- a/src/iterators/macros.rs +++ b/src/iterators/macros.rs @@ -67,7 +67,7 @@ impl<$($typarm)*> NdProducer for $fulltype { self.$base.raw_dim() } - fn layout(&self) -> Layout { + fn layout(&self) -> LayoutBitset { self.$base.layout() } diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index f7892a8c9..85ef78c8c 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -1191,9 +1191,9 @@ impl NdProducer for AxisIter<'_, A, D> type Ptr = *mut A; type Stride = isize; - fn layout(&self) -> crate::Layout + fn layout(&self) -> crate::LayoutBitset { - crate::Layout::one_dimensional() + crate::LayoutBitset::one_dimensional() } fn raw_dim(&self) -> Self::Dim @@ -1250,9 +1250,9 @@ impl NdProducer for AxisIterMut<'_, A, D> type Ptr = *mut A; type Stride = isize; - fn layout(&self) -> crate::Layout + fn layout(&self) -> crate::LayoutBitset { - crate::Layout::one_dimensional() + crate::LayoutBitset::one_dimensional() } fn raw_dim(&self) -> Self::Dim diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs index e6fccce46..f64b9fd4d 100644 --- a/src/iterators/windows.rs +++ b/src/iterators/windows.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::Baseiter; use crate::imp_prelude::*; use crate::IntoDimension; -use crate::Layout; +use crate::LayoutBitset; use crate::NdProducer; use crate::Slice; @@ -176,7 +176,7 @@ impl<'a, A, D: Dimension> NdProducer for AxisWindows<'a, A, D> Ix1(self.base.raw_dim()[self.axis_idx]) } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { self.base.layout() } diff --git a/src/layout/layoutfmt.rs b/src/layout/layoutfmt.rs index f20f0caaa..f4cf3396f 100644 --- a/src/layout/layoutfmt.rs +++ b/src/layout/layoutfmt.rs @@ -6,13 +6,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::Layout; +use super::LayoutBitset; const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"]; use std::fmt; -impl fmt::Debug for Layout +impl fmt::Debug for LayoutBitset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 36853848e..4c9833e23 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -6,9 +6,14 @@ mod layoutfmt; #[doc(hidden)] /// Memory layout description #[derive(Copy, Clone)] -pub struct Layout(u32); +pub struct LayoutBitset(u32); -impl Layout +#[deprecated(since = "0.18.0", note = "Layout has been renamed to LayoutBitset")] +#[allow(dead_code)] +/// Memory layout description, deprecated. See [`LayoutBitset`] instead. +pub type Layout = LayoutBitset; + +impl LayoutBitset { pub(crate) const CORDER: u32 = 0b01; pub(crate) const FORDER: u32 = 0b10; @@ -23,52 +28,52 @@ impl Layout /// Return layout common to both inputs #[inline(always)] - pub(crate) fn intersect(self, other: Layout) -> Layout + pub(crate) fn intersect(self, other: LayoutBitset) -> LayoutBitset { - Layout(self.0 & other.0) + LayoutBitset(self.0 & other.0) } /// Return a layout that simultaneously "is" what both of the inputs are #[inline(always)] - pub(crate) fn also(self, other: Layout) -> Layout + pub(crate) fn also(self, other: LayoutBitset) -> LayoutBitset { - Layout(self.0 | other.0) + LayoutBitset(self.0 | other.0) } #[inline(always)] - pub(crate) fn one_dimensional() -> Layout + pub(crate) fn one_dimensional() -> LayoutBitset { - Layout::c().also(Layout::f()) + LayoutBitset::c().also(LayoutBitset::f()) } #[inline(always)] - pub(crate) fn c() -> Layout + pub(crate) fn c() -> LayoutBitset { - Layout(Layout::CORDER | Layout::CPREFER) + LayoutBitset(LayoutBitset::CORDER | LayoutBitset::CPREFER) } #[inline(always)] - pub(crate) fn f() -> Layout + pub(crate) fn f() -> LayoutBitset { - Layout(Layout::FORDER | Layout::FPREFER) + LayoutBitset(LayoutBitset::FORDER | LayoutBitset::FPREFER) } #[inline(always)] - pub(crate) fn cpref() -> Layout + pub(crate) fn cpref() -> LayoutBitset { - Layout(Layout::CPREFER) + LayoutBitset(LayoutBitset::CPREFER) } #[inline(always)] - pub(crate) fn fpref() -> Layout + pub(crate) fn fpref() -> LayoutBitset { - Layout(Layout::FPREFER) + LayoutBitset(LayoutBitset::FPREFER) } #[inline(always)] - pub(crate) fn none() -> Layout + pub(crate) fn none() -> LayoutBitset { - Layout(0) + LayoutBitset(0) } /// A simple "score" method which scores positive for preferring C-order, negative for F-order @@ -76,8 +81,8 @@ impl Layout #[inline] pub(crate) fn tendency(self) -> i32 { - (self.is(Layout::CORDER) as i32 - self.is(Layout::FORDER) as i32) - + (self.is(Layout::CPREFER) as i32 - self.is(Layout::FPREFER) as i32) + (self.is(LayoutBitset::CORDER) as i32 - self.is(LayoutBitset::FORDER) as i32) + + (self.is(LayoutBitset::CPREFER) as i32 - self.is(LayoutBitset::FPREFER) as i32) } } @@ -96,7 +101,7 @@ mod tests ($mat:expr, $($layout:ident),*) => {{ let layout = $mat.view().layout(); $( - assert!(layout.is(Layout::$layout), + assert!(layout.is(LayoutBitset::$layout), "Assertion failed: array {:?} is not layout {}", $mat, stringify!($layout)); @@ -108,7 +113,7 @@ mod tests ($mat:expr, $($layout:ident),*) => {{ let layout = $mat.view().layout(); $( - assert!(!layout.is(Layout::$layout), + assert!(!layout.is(LayoutBitset::$layout), "Assertion failed: array {:?} show not have layout {}", $mat, stringify!($layout)); @@ -123,10 +128,10 @@ mod tests let b = M::zeros((5, 5).f()); let ac = a.view().layout(); let af = b.view().layout(); - assert!(ac.is(Layout::CORDER) && ac.is(Layout::CPREFER)); - assert!(!ac.is(Layout::FORDER) && !ac.is(Layout::FPREFER)); - assert!(!af.is(Layout::CORDER) && !af.is(Layout::CPREFER)); - assert!(af.is(Layout::FORDER) && af.is(Layout::FPREFER)); + assert!(ac.is(LayoutBitset::CORDER) && ac.is(LayoutBitset::CPREFER)); + assert!(!ac.is(LayoutBitset::FORDER) && !ac.is(LayoutBitset::FPREFER)); + assert!(!af.is(LayoutBitset::CORDER) && !af.is(LayoutBitset::CPREFER)); + assert!(af.is(LayoutBitset::FORDER) && af.is(LayoutBitset::FPREFER)); } #[test] @@ -167,10 +172,10 @@ mod tests let v1 = a.slice(s![1.., ..]).layout(); let v2 = a.slice(s![.., 1..]).layout(); - assert!(v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); - assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); - assert!(!v2.is(Layout::CORDER) && v2.is(Layout::CPREFER)); - assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); + assert!(v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); } let b = M::zeros((5, 5).f()); @@ -179,10 +184,10 @@ mod tests let v1 = b.slice(s![1.., ..]).layout(); let v2 = b.slice(s![.., 1..]).layout(); - assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); - assert!(!v1.is(Layout::FORDER) && v1.is(Layout::FPREFER)); - assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); - assert!(v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); + assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); } } @@ -223,10 +228,10 @@ mod tests let v1 = a.slice(s![..;2, ..]).layout(); let v2 = a.slice(s![.., ..;2]).layout(); - assert!(!v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); - assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); - assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); - assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); + assert!(!v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); } let b = M::zeros((5, 5).f()); @@ -234,10 +239,10 @@ mod tests let v1 = b.slice(s![..;2, ..]).layout(); let v2 = b.slice(s![.., ..;2]).layout(); - assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); - assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); - assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); - assert!(!v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); + assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); } } } diff --git a/src/lib.rs b/src/lib.rs index 6a5ea8280..4e92d3000 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,7 +224,8 @@ mod dimension; pub use crate::zip::{FoldWhile, IntoNdProducer, NdProducer, Zip}; -pub use crate::layout::Layout; +#[allow(deprecated)] +pub use crate::layout::{Layout, LayoutBitset}; /// Implementation's prelude. Common types used everywhere. mod imp_prelude diff --git a/src/parallel/send_producer.rs b/src/parallel/send_producer.rs index ecfb77af0..ebf973091 100644 --- a/src/parallel/send_producer.rs +++ b/src/parallel/send_producer.rs @@ -1,5 +1,5 @@ use crate::imp_prelude::*; -use crate::{Layout, NdProducer}; +use crate::{LayoutBitset, NdProducer}; use std::ops::{Deref, DerefMut}; /// An NdProducer that is unconditionally `Send`. @@ -66,7 +66,7 @@ where P: NdProducer } #[inline(always)] - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { self.inner.layout() } diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 668eac093..16555721d 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -17,7 +17,7 @@ use crate::imp_prelude::*; use crate::partial::Partial; use crate::AssignElem; use crate::IntoDimension; -use crate::Layout; +use crate::LayoutBitset; use crate::dimension; use crate::indexes::{indices, Indices}; @@ -51,35 +51,35 @@ where E: IntoDimension } /// Compute `Layout` hints for array shape dim, strides -fn array_layout(dim: &D, strides: &D) -> Layout +fn array_layout(dim: &D, strides: &D) -> LayoutBitset { let n = dim.ndim(); if dimension::is_layout_c(dim, strides) { // effectively one-dimensional => C and F layout compatible if n <= 1 || dim.slice().iter().filter(|&&len| len > 1).count() <= 1 { - Layout::one_dimensional() + LayoutBitset::one_dimensional() } else { - Layout::c() + LayoutBitset::c() } } else if n > 1 && dimension::is_layout_f(dim, strides) { - Layout::f() + LayoutBitset::f() } else if n > 1 { if dim[0] > 1 && strides[0] == 1 { - Layout::fpref() + LayoutBitset::fpref() } else if dim[n - 1] > 1 && strides[n - 1] == 1 { - Layout::cpref() + LayoutBitset::cpref() } else { - Layout::none() + LayoutBitset::none() } } else { - Layout::none() + LayoutBitset::none() } } impl LayoutRef where D: Dimension { - pub(crate) fn layout_impl(&self) -> Layout + pub(crate) fn layout_impl(&self) -> LayoutBitset { array_layout(self._dim(), self._strides()) } @@ -194,7 +194,7 @@ pub struct Zip { parts: Parts, dimension: D, - layout: Layout, + layout: LayoutBitset, /// The sum of the layout tendencies of the parts; /// positive for c- and negative for f-layout preference. layout_tendency: i32, @@ -277,7 +277,7 @@ where D: Dimension fn prefer_f(&self) -> bool { - !self.layout.is(Layout::CORDER) && (self.layout.is(Layout::FORDER) || self.layout_tendency < 0) + !self.layout.is(LayoutBitset::CORDER) && (self.layout.is(LayoutBitset::FORDER) || self.layout_tendency < 0) } /// Return an *approximation* to the max stride axis; if @@ -313,7 +313,7 @@ where D: Dimension { if self.dimension.ndim() == 0 { function(acc, unsafe { self.parts.as_ref(self.parts.as_ptr()) }) - } else if self.layout.is(Layout::CORDER | Layout::FORDER) { + } else if self.layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER) { self.for_each_core_contiguous(acc, function) } else { self.for_each_core_strided(acc, function) @@ -325,7 +325,7 @@ where D: Dimension F: FnMut(Acc, P::Item) -> FoldWhile, P: ZippableTuple, { - debug_assert!(self.layout.is(Layout::CORDER | Layout::FORDER)); + debug_assert!(self.layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER)); let size = self.dimension.size(); let ptrs = self.parts.as_ptr(); let inner_strides = self.parts.contiguous_stride(); @@ -442,7 +442,7 @@ where #[inline] pub(crate) fn debug_assert_c_order(self) -> Self { - debug_assert!(self.layout.is(Layout::CORDER) || self.layout_tendency >= 0 || + debug_assert!(self.layout.is(LayoutBitset::CORDER) || self.layout_tendency >= 0 || self.dimension.slice().iter().filter(|&&d| d > 1).count() <= 1, "Assertion failed: traversal is not c-order or 1D for \ layout {:?}, tendency {}, dimension {:?}", @@ -841,7 +841,7 @@ macro_rules! map_impl { // debug assert that the output is contiguous in the memory layout we need if cfg!(debug_assertions) { let out_layout = output.layout(); - assert!(out_layout.is(Layout::CORDER | Layout::FORDER)); + assert!(out_layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER)); assert!( (self.layout_tendency <= 0 && out_layout.tendency() <= 0) || (self.layout_tendency >= 0 && out_layout.tendency() >= 0), diff --git a/src/zip/ndproducer.rs b/src/zip/ndproducer.rs index fe666e81e..f06497c29 100644 --- a/src/zip/ndproducer.rs +++ b/src/zip/ndproducer.rs @@ -1,6 +1,6 @@ use crate::imp_prelude::*; use crate::ArrayRef; -use crate::Layout; +use crate::LayoutBitset; use crate::NdIndex; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -74,7 +74,7 @@ pub trait NdProducer type Stride: Copy; #[doc(hidden)] - fn layout(&self) -> Layout; + fn layout(&self) -> LayoutBitset; /// Return the shape of the producer. fn raw_dim(&self) -> Self::Dim; #[doc(hidden)] @@ -282,7 +282,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> (**self).as_ptr() as _ } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { self.layout_impl() } @@ -340,7 +340,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> (**self).as_ptr() as _ } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { self.layout_impl() } @@ -398,7 +398,7 @@ impl NdProducer for RawArrayView self.as_ptr() as _ } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { AsRef::>::as_ref(self).layout_impl() } @@ -457,7 +457,7 @@ impl NdProducer for RawArrayViewMut self.as_ptr() as _ } - fn layout(&self) -> Layout + fn layout(&self) -> LayoutBitset { AsRef::>::as_ref(self).layout_impl() } From 6e02f15c7f9fb6a02b3abfa0b2e82e294c2d2878 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 4 Jan 2026 11:34:54 -0500 Subject: [PATCH 42/54] Configure docs for feature gates globally (#1565) In #1479 we used `cfg_attr(docsrs, doc(cfg(feature = "...")))` to add feature-gating information to the documentation. However, this required us to "remember" this at every call site. It turns out that there is a `doc(auto_cfg)` setting that we can use globally to do this for us automatically. --- src/array_approx.rs | 2 -- src/arraytraits.rs | 1 - src/error.rs | 1 - src/impl_constructors.rs | 4 ---- src/lib.rs | 5 +---- src/linalg_traits.rs | 3 --- src/numeric/impl_float_maths.rs | 1 - src/numeric/impl_numeric.rs | 4 ---- src/partial.rs | 3 --- 9 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/array_approx.rs b/src/array_approx.rs index 958f6f6ba..93d1bd0ac 100644 --- a/src/array_approx.rs +++ b/src/array_approx.rs @@ -1,5 +1,4 @@ #[cfg(feature = "approx")] -#[cfg_attr(docsrs, doc(cfg(feature = "approx")))] mod approx_methods { use crate::imp_prelude::*; @@ -244,5 +243,4 @@ macro_rules! impl_approx_traits { } #[cfg(feature = "approx")] -#[cfg_attr(docsrs, doc(cfg(feature = "approx")))] impl_approx_traits!(approx, "**Requires crate feature `\"approx\"`.**"); diff --git a/src/arraytraits.rs b/src/arraytraits.rs index 08397bf9c..8b214ac9d 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -507,7 +507,6 @@ unsafe impl Sync for ArrayRef where A: Sync {} unsafe impl Send for ArrayRef where A: Send {} #[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] // Use version number so we can add a packed format later. pub const ARRAY_FORMAT_VERSION: u8 = 1u8; diff --git a/src/error.rs b/src/error.rs index e19c32075..eb7395ad8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -81,7 +81,6 @@ impl PartialEq for ShapeError } #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ShapeError {} impl fmt::Display for ShapeError diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index ded1bbf79..ba01e2ca3 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -99,7 +99,6 @@ where S: DataOwned /// assert!(array == arr1(&[0.0, 0.25, 0.5, 0.75, 1.0])) /// ``` #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn linspace(start: A, end: A, n: usize) -> Self where A: Float { @@ -118,7 +117,6 @@ where S: DataOwned /// assert!(array == arr1(&[0., 1., 2., 3., 4.])) /// ``` #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn range(start: A, end: A, step: A) -> Self where A: Float { @@ -147,7 +145,6 @@ where S: DataOwned /// # } /// ``` #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn logspace(base: A, start: A, end: A, n: usize) -> Self where A: Float { @@ -182,7 +179,6 @@ where S: DataOwned /// # example().unwrap(); /// ``` #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn geomspace(start: A, end: A, n: usize) -> Option where A: Float { diff --git a/src/lib.rs b/src/lib.rs index 4e92d3000..2b9b656e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ #![cfg_attr(not(feature = "std"), no_std)] // Enable the doc_cfg nightly feature for including feature gate flags in the documentation #![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, doc(auto_cfg))] #![warn(missing_docs)] //! The `ndarray` crate provides an *n*-dimensional container for general elements @@ -158,7 +159,6 @@ use crate::iterators::{ElementsBase, ElementsBaseMut}; pub use crate::arraytraits::AsArray; pub use crate::linalg_traits::LinalgScalar; #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use crate::linalg_traits::NdFloat; pub use crate::stacking::{concatenate, stack}; @@ -201,11 +201,9 @@ mod layout; mod linalg_traits; mod linspace; #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use crate::linspace::{linspace, range, Linspace}; mod logspace; #[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use crate::logspace::{logspace, Logspace}; mod math_cell; mod numeric_util; @@ -1842,7 +1840,6 @@ where // parallel methods #[cfg(feature = "rayon")] -#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] pub mod parallel; mod impl_1d; diff --git a/src/linalg_traits.rs b/src/linalg_traits.rs index ec1aebbe7..e77235839 100644 --- a/src/linalg_traits.rs +++ b/src/linalg_traits.rs @@ -42,7 +42,6 @@ impl LinalgScalar for T where T: 'static + Copy + Zero + One + Add ArrayRef where A: 'static + Float, diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index 9709a5254..90e9b6ec9 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -177,7 +177,6 @@ where D: Dimension /// ``` #[track_caller] #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn var(&self, ddof: A) -> A where A: Float + FromPrimitive { @@ -243,7 +242,6 @@ where D: Dimension /// ``` #[track_caller] #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn std(&self, ddof: A) -> A where A: Float + FromPrimitive { @@ -400,7 +398,6 @@ where D: Dimension /// ``` #[track_caller] #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn var_axis(&self, axis: Axis, ddof: A) -> Array where A: Float + FromPrimitive, @@ -471,7 +468,6 @@ where D: Dimension /// ``` #[track_caller] #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn std_axis(&self, axis: Axis, ddof: A) -> Array where A: Float + FromPrimitive, diff --git a/src/partial.rs b/src/partial.rs index 1a1679ec5..dbaa0e105 100644 --- a/src/partial.rs +++ b/src/partial.rs @@ -37,7 +37,6 @@ impl Partial } #[cfg(feature = "rayon")] - #[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] pub(crate) fn stub() -> Self { Self { @@ -47,7 +46,6 @@ impl Partial } #[cfg(feature = "rayon")] - #[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] pub(crate) fn is_stub(&self) -> bool { self.ptr.is_null() @@ -62,7 +60,6 @@ impl Partial } #[cfg(feature = "rayon")] - #[cfg_attr(docsrs, doc(cfg(feature = "rayon")))] /// Merge if they are in order (left to right) and contiguous. /// Skips merge if T does not need drop. pub(crate) fn try_merge(mut left: Self, right: Self) -> Self From 953f2e9e856139f637feb3c7b321b5bb3fcae533 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sun, 4 Jan 2026 12:04:16 -0500 Subject: [PATCH 43/54] Move LayoutBitset to its own module (#1567) This is a follow-on to #1563; it probably should have been the same PR. In additon to reserving the name `Layout`, we'd also like to clean up the `src/layout` module so that we can pack it full of new features for #1506. So we collapse everything that's currently there into `layout::bitset`, and add a `pub use` declaration to re-export it. As a side note, I'd eventually like `layout` to act as a template for how we handle modules: minimal module-level code and clear module-level documentation. I've made #1566 to track that library-wide. --- src/layout/bitset.rs | 271 ++++++++++++++++++++++++++++++++++++++++ src/layout/layoutfmt.rs | 32 ----- src/layout/mod.rs | 250 +----------------------------------- 3 files changed, 274 insertions(+), 279 deletions(-) create mode 100644 src/layout/bitset.rs delete mode 100644 src/layout/layoutfmt.rs diff --git a/src/layout/bitset.rs b/src/layout/bitset.rs new file mode 100644 index 000000000..4b087ad35 --- /dev/null +++ b/src/layout/bitset.rs @@ -0,0 +1,271 @@ +//! Compact representations of array layouts. + +use alloc::fmt; + +// Layout is a bitset used for internal layout description of +// arrays, producers and sets of producers. +// The type is public but users don't interact with it. +#[doc(hidden)] +/// Memory layout description +#[derive(Copy, Clone)] +pub struct LayoutBitset(pub(super) u32); + +#[deprecated(since = "0.18.0", note = "Layout has been renamed to LayoutBitset")] +#[allow(dead_code)] +/// Memory layout description, deprecated. See [`LayoutBitset`] instead. +pub type Layout = LayoutBitset; + +impl LayoutBitset +{ + pub(crate) const CORDER: u32 = 0b01; + pub(crate) const FORDER: u32 = 0b10; + pub(crate) const CPREFER: u32 = 0b0100; + pub(crate) const FPREFER: u32 = 0b1000; + + #[inline(always)] + pub(crate) fn is(self, flag: u32) -> bool + { + self.0 & flag != 0 + } + + /// Return layout common to both inputs + #[inline(always)] + pub(crate) fn intersect(self, other: LayoutBitset) -> LayoutBitset + { + LayoutBitset(self.0 & other.0) + } + + /// Return a layout that simultaneously "is" what both of the inputs are + #[inline(always)] + pub(crate) fn also(self, other: LayoutBitset) -> LayoutBitset + { + LayoutBitset(self.0 | other.0) + } + + #[inline(always)] + pub(crate) fn one_dimensional() -> LayoutBitset + { + LayoutBitset::c().also(LayoutBitset::f()) + } + + #[inline(always)] + pub(crate) fn c() -> LayoutBitset + { + LayoutBitset(LayoutBitset::CORDER | LayoutBitset::CPREFER) + } + + #[inline(always)] + pub(crate) fn f() -> LayoutBitset + { + LayoutBitset(LayoutBitset::FORDER | LayoutBitset::FPREFER) + } + + #[inline(always)] + pub(crate) fn cpref() -> LayoutBitset + { + LayoutBitset(LayoutBitset::CPREFER) + } + + #[inline(always)] + pub(crate) fn fpref() -> LayoutBitset + { + LayoutBitset(LayoutBitset::FPREFER) + } + + #[inline(always)] + pub(crate) fn none() -> LayoutBitset + { + LayoutBitset(0) + } + + /// A simple "score" method which scores positive for preferring C-order, negative for F-order + /// Subject to change when we can describe other layouts + #[inline] + pub(crate) fn tendency(self) -> i32 + { + (self.is(LayoutBitset::CORDER) as i32 - self.is(LayoutBitset::FORDER) as i32) + + (self.is(LayoutBitset::CPREFER) as i32 - self.is(LayoutBitset::FPREFER) as i32) + } +} + +const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"]; + +impl fmt::Debug for LayoutBitset +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + if self.0 == 0 { + write!(f, "Custom")? + } else { + (0..32).filter(|&i| self.is(1 << i)).try_fold((), |_, i| { + if let Some(name) = LAYOUT_NAMES.get(i) { + write!(f, "{}", name) + } else { + write!(f, "{:#x}", i) + } + })?; + }; + write!(f, " ({:#x})", self.0) + } +} + +#[cfg(test)] +mod tests +{ + use super::*; + use crate::imp_prelude::*; + use crate::NdProducer; + + type M = Array2; + type M1 = Array1; + type M0 = Array0; + + macro_rules! assert_layouts { + ($mat:expr, $($layout:ident),*) => {{ + let layout = $mat.view().layout(); + $( + assert!(layout.is(LayoutBitset::$layout), + "Assertion failed: array {:?} is not layout {}", + $mat, + stringify!($layout)); + )* + }}; + } + + macro_rules! assert_not_layouts { + ($mat:expr, $($layout:ident),*) => {{ + let layout = $mat.view().layout(); + $( + assert!(!layout.is(LayoutBitset::$layout), + "Assertion failed: array {:?} show not have layout {}", + $mat, + stringify!($layout)); + )* + }}; + } + + #[test] + fn contig_layouts() + { + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + let ac = a.view().layout(); + let af = b.view().layout(); + assert!(ac.is(LayoutBitset::CORDER) && ac.is(LayoutBitset::CPREFER)); + assert!(!ac.is(LayoutBitset::FORDER) && !ac.is(LayoutBitset::FPREFER)); + assert!(!af.is(LayoutBitset::CORDER) && !af.is(LayoutBitset::CPREFER)); + assert!(af.is(LayoutBitset::FORDER) && af.is(LayoutBitset::FPREFER)); + } + + #[test] + fn contig_cf_layouts() + { + let a = M::zeros((5, 1)); + let b = M::zeros((1, 5).f()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); + + let a = M1::zeros(5); + let b = M1::zeros(5.f()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); + + let a = M0::zeros(()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + let arow = a.slice(s![..1, ..]); + let bcol = b.slice(s![.., ..1]); + assert_layouts!(arow, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, CPREFER, FORDER, FPREFER); + + let acol = a.slice(s![.., ..1]); + let brow = b.slice(s![..1, ..]); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + } + + #[test] + fn stride_layouts() + { + let a = M::zeros((5, 5)); + + { + let v1 = a.slice(s![1.., ..]).layout(); + let v2 = a.slice(s![.., 1..]).layout(); + + assert!(v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); + } + + let b = M::zeros((5, 5).f()); + + { + let v1 = b.slice(s![1.., ..]).layout(); + let v2 = b.slice(s![.., 1..]).layout(); + + assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); + } + } + + #[test] + fn no_layouts() + { + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + + // 2D row/column matrixes + let arow = a.slice(s![0..1, ..]); + let acol = a.slice(s![.., 0..1]); + let brow = b.slice(s![0..1, ..]); + let bcol = b.slice(s![.., 0..1]); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + + // 2D row/column matrixes - now made with insert axis + for &axis in &[Axis(0), Axis(1)] { + let arow = a.slice(s![0, ..]).insert_axis(axis); + let acol = a.slice(s![.., 0]).insert_axis(axis); + let brow = b.slice(s![0, ..]).insert_axis(axis); + let bcol = b.slice(s![.., 0]).insert_axis(axis); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + } + } + + #[test] + fn skip_layouts() + { + let a = M::zeros((5, 5)); + { + let v1 = a.slice(s![..;2, ..]).layout(); + let v2 = a.slice(s![.., ..;2]).layout(); + + assert!(!v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); + } + + let b = M::zeros((5, 5).f()); + { + let v1 = b.slice(s![..;2, ..]).layout(); + let v2 = b.slice(s![.., ..;2]).layout(); + + assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); + assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); + assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); + assert!(!v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); + } + } +} diff --git a/src/layout/layoutfmt.rs b/src/layout/layoutfmt.rs deleted file mode 100644 index f4cf3396f..000000000 --- a/src/layout/layoutfmt.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 bluss and ndarray developers. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::LayoutBitset; - -const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"]; - -use std::fmt; - -impl fmt::Debug for LayoutBitset -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result - { - if self.0 == 0 { - write!(f, "Custom")? - } else { - (0..32).filter(|&i| self.is(1 << i)).try_fold((), |_, i| { - if let Some(name) = LAYOUT_NAMES.get(i) { - write!(f, "{}", name) - } else { - write!(f, "{:#x}", i) - } - })?; - }; - write!(f, " ({:#x})", self.0) - } -} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 4c9833e23..7f549ebb2 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,248 +1,4 @@ -mod layoutfmt; +mod bitset; -// Layout is a bitset used for internal layout description of -// arrays, producers and sets of producers. -// The type is public but users don't interact with it. -#[doc(hidden)] -/// Memory layout description -#[derive(Copy, Clone)] -pub struct LayoutBitset(u32); - -#[deprecated(since = "0.18.0", note = "Layout has been renamed to LayoutBitset")] -#[allow(dead_code)] -/// Memory layout description, deprecated. See [`LayoutBitset`] instead. -pub type Layout = LayoutBitset; - -impl LayoutBitset -{ - pub(crate) const CORDER: u32 = 0b01; - pub(crate) const FORDER: u32 = 0b10; - pub(crate) const CPREFER: u32 = 0b0100; - pub(crate) const FPREFER: u32 = 0b1000; - - #[inline(always)] - pub(crate) fn is(self, flag: u32) -> bool - { - self.0 & flag != 0 - } - - /// Return layout common to both inputs - #[inline(always)] - pub(crate) fn intersect(self, other: LayoutBitset) -> LayoutBitset - { - LayoutBitset(self.0 & other.0) - } - - /// Return a layout that simultaneously "is" what both of the inputs are - #[inline(always)] - pub(crate) fn also(self, other: LayoutBitset) -> LayoutBitset - { - LayoutBitset(self.0 | other.0) - } - - #[inline(always)] - pub(crate) fn one_dimensional() -> LayoutBitset - { - LayoutBitset::c().also(LayoutBitset::f()) - } - - #[inline(always)] - pub(crate) fn c() -> LayoutBitset - { - LayoutBitset(LayoutBitset::CORDER | LayoutBitset::CPREFER) - } - - #[inline(always)] - pub(crate) fn f() -> LayoutBitset - { - LayoutBitset(LayoutBitset::FORDER | LayoutBitset::FPREFER) - } - - #[inline(always)] - pub(crate) fn cpref() -> LayoutBitset - { - LayoutBitset(LayoutBitset::CPREFER) - } - - #[inline(always)] - pub(crate) fn fpref() -> LayoutBitset - { - LayoutBitset(LayoutBitset::FPREFER) - } - - #[inline(always)] - pub(crate) fn none() -> LayoutBitset - { - LayoutBitset(0) - } - - /// A simple "score" method which scores positive for preferring C-order, negative for F-order - /// Subject to change when we can describe other layouts - #[inline] - pub(crate) fn tendency(self) -> i32 - { - (self.is(LayoutBitset::CORDER) as i32 - self.is(LayoutBitset::FORDER) as i32) - + (self.is(LayoutBitset::CPREFER) as i32 - self.is(LayoutBitset::FPREFER) as i32) - } -} - -#[cfg(test)] -mod tests -{ - use super::*; - use crate::imp_prelude::*; - use crate::NdProducer; - - type M = Array2; - type M1 = Array1; - type M0 = Array0; - - macro_rules! assert_layouts { - ($mat:expr, $($layout:ident),*) => {{ - let layout = $mat.view().layout(); - $( - assert!(layout.is(LayoutBitset::$layout), - "Assertion failed: array {:?} is not layout {}", - $mat, - stringify!($layout)); - )* - }}; - } - - macro_rules! assert_not_layouts { - ($mat:expr, $($layout:ident),*) => {{ - let layout = $mat.view().layout(); - $( - assert!(!layout.is(LayoutBitset::$layout), - "Assertion failed: array {:?} show not have layout {}", - $mat, - stringify!($layout)); - )* - }}; - } - - #[test] - fn contig_layouts() - { - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - let ac = a.view().layout(); - let af = b.view().layout(); - assert!(ac.is(LayoutBitset::CORDER) && ac.is(LayoutBitset::CPREFER)); - assert!(!ac.is(LayoutBitset::FORDER) && !ac.is(LayoutBitset::FPREFER)); - assert!(!af.is(LayoutBitset::CORDER) && !af.is(LayoutBitset::CPREFER)); - assert!(af.is(LayoutBitset::FORDER) && af.is(LayoutBitset::FPREFER)); - } - - #[test] - fn contig_cf_layouts() - { - let a = M::zeros((5, 1)); - let b = M::zeros((1, 5).f()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); - - let a = M1::zeros(5); - let b = M1::zeros(5.f()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); - - let a = M0::zeros(()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - let arow = a.slice(s![..1, ..]); - let bcol = b.slice(s![.., ..1]); - assert_layouts!(arow, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, CPREFER, FORDER, FPREFER); - - let acol = a.slice(s![.., ..1]); - let brow = b.slice(s![..1, ..]); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - } - - #[test] - fn stride_layouts() - { - let a = M::zeros((5, 5)); - - { - let v1 = a.slice(s![1.., ..]).layout(); - let v2 = a.slice(s![.., 1..]).layout(); - - assert!(v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); - } - - let b = M::zeros((5, 5).f()); - - { - let v1 = b.slice(s![1.., ..]).layout(); - let v2 = b.slice(s![.., 1..]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); - } - } - - #[test] - fn no_layouts() - { - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - - // 2D row/column matrixes - let arow = a.slice(s![0..1, ..]); - let acol = a.slice(s![.., 0..1]); - let brow = b.slice(s![0..1, ..]); - let bcol = b.slice(s![.., 0..1]); - assert_layouts!(arow, CORDER, FORDER); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, FORDER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - - // 2D row/column matrixes - now made with insert axis - for &axis in &[Axis(0), Axis(1)] { - let arow = a.slice(s![0, ..]).insert_axis(axis); - let acol = a.slice(s![.., 0]).insert_axis(axis); - let brow = b.slice(s![0, ..]).insert_axis(axis); - let bcol = b.slice(s![.., 0]).insert_axis(axis); - assert_layouts!(arow, CORDER, FORDER); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, FORDER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - } - } - - #[test] - fn skip_layouts() - { - let a = M::zeros((5, 5)); - { - let v1 = a.slice(s![..;2, ..]).layout(); - let v2 = a.slice(s![.., ..;2]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); - } - - let b = M::zeros((5, 5).f()); - { - let v1 = b.slice(s![..;2, ..]).layout(); - let v2 = b.slice(s![.., ..;2]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); - } - } -} +#[allow(deprecated)] +pub use bitset::{Layout, LayoutBitset}; From 8adf5d81b395751191c7fa046ec257e157255a14 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sat, 10 Jan 2026 10:47:48 -0500 Subject: [PATCH 44/54] Revert #1563 and #1567 to prepare for patch release (#1570) Both #1563 and #1567 were done in preparation for new code to address #1506. However, those PRs introduce deprecations, which will require a minor version bump to adhere to SemVer. Instead of having them on master, we have created the refactor branch where they will continue to live. --- src/indexes.rs | 8 +- src/iterators/chunks.rs | 2 +- src/iterators/lanes.rs | 2 +- src/iterators/macros.rs | 2 +- src/iterators/mod.rs | 8 +- src/iterators/windows.rs | 4 +- src/layout/bitset.rs | 271 ---------------------------------- src/layout/layoutfmt.rs | 32 ++++ src/layout/mod.rs | 245 +++++++++++++++++++++++++++++- src/lib.rs | 3 +- src/parallel/send_producer.rs | 4 +- src/zip/mod.rs | 32 ++-- src/zip/ndproducer.rs | 12 +- 13 files changed, 312 insertions(+), 313 deletions(-) delete mode 100644 src/layout/bitset.rs create mode 100644 src/layout/layoutfmt.rs diff --git a/src/indexes.rs b/src/indexes.rs index a73c7fcb4..0fa2b50fb 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -10,7 +10,7 @@ use crate::dimension::IntoDimension; use crate::split_at::SplitAt; use crate::zip::Offset; use crate::Axis; -use crate::LayoutBitset; +use crate::Layout; use crate::NdProducer; use crate::{ArrayBase, Data}; @@ -193,12 +193,12 @@ impl NdProducer for Indices IndexPtr { index: self.start } } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { if self.dim.ndim() <= 1 { - LayoutBitset::one_dimensional() + Layout::one_dimensional() } else { - LayoutBitset::none() + Layout::none() } } diff --git a/src/iterators/chunks.rs b/src/iterators/chunks.rs index aff194d53..178ead7e0 100644 --- a/src/iterators/chunks.rs +++ b/src/iterators/chunks.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use crate::imp_prelude::*; use crate::Baseiter; use crate::IntoDimension; -use crate::{LayoutBitset, NdProducer}; +use crate::{Layout, NdProducer}; impl_ndproducer! { ['a, A, D: Dimension] diff --git a/src/iterators/lanes.rs b/src/iterators/lanes.rs index 2f5e14774..9fd39607b 100644 --- a/src/iterators/lanes.rs +++ b/src/iterators/lanes.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::LanesIter; use super::LanesIterMut; use crate::imp_prelude::*; -use crate::{LayoutBitset, NdProducer}; +use crate::{Layout, NdProducer}; impl_ndproducer! { ['a, A, D: Dimension] diff --git a/src/iterators/macros.rs b/src/iterators/macros.rs index 041d09021..78697ec25 100644 --- a/src/iterators/macros.rs +++ b/src/iterators/macros.rs @@ -67,7 +67,7 @@ impl<$($typarm)*> NdProducer for $fulltype { self.$base.raw_dim() } - fn layout(&self) -> LayoutBitset { + fn layout(&self) -> Layout { self.$base.layout() } diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index 85ef78c8c..f7892a8c9 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -1191,9 +1191,9 @@ impl NdProducer for AxisIter<'_, A, D> type Ptr = *mut A; type Stride = isize; - fn layout(&self) -> crate::LayoutBitset + fn layout(&self) -> crate::Layout { - crate::LayoutBitset::one_dimensional() + crate::Layout::one_dimensional() } fn raw_dim(&self) -> Self::Dim @@ -1250,9 +1250,9 @@ impl NdProducer for AxisIterMut<'_, A, D> type Ptr = *mut A; type Stride = isize; - fn layout(&self) -> crate::LayoutBitset + fn layout(&self) -> crate::Layout { - crate::LayoutBitset::one_dimensional() + crate::Layout::one_dimensional() } fn raw_dim(&self) -> Self::Dim diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs index f64b9fd4d..e6fccce46 100644 --- a/src/iterators/windows.rs +++ b/src/iterators/windows.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::Baseiter; use crate::imp_prelude::*; use crate::IntoDimension; -use crate::LayoutBitset; +use crate::Layout; use crate::NdProducer; use crate::Slice; @@ -176,7 +176,7 @@ impl<'a, A, D: Dimension> NdProducer for AxisWindows<'a, A, D> Ix1(self.base.raw_dim()[self.axis_idx]) } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { self.base.layout() } diff --git a/src/layout/bitset.rs b/src/layout/bitset.rs deleted file mode 100644 index 4b087ad35..000000000 --- a/src/layout/bitset.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! Compact representations of array layouts. - -use alloc::fmt; - -// Layout is a bitset used for internal layout description of -// arrays, producers and sets of producers. -// The type is public but users don't interact with it. -#[doc(hidden)] -/// Memory layout description -#[derive(Copy, Clone)] -pub struct LayoutBitset(pub(super) u32); - -#[deprecated(since = "0.18.0", note = "Layout has been renamed to LayoutBitset")] -#[allow(dead_code)] -/// Memory layout description, deprecated. See [`LayoutBitset`] instead. -pub type Layout = LayoutBitset; - -impl LayoutBitset -{ - pub(crate) const CORDER: u32 = 0b01; - pub(crate) const FORDER: u32 = 0b10; - pub(crate) const CPREFER: u32 = 0b0100; - pub(crate) const FPREFER: u32 = 0b1000; - - #[inline(always)] - pub(crate) fn is(self, flag: u32) -> bool - { - self.0 & flag != 0 - } - - /// Return layout common to both inputs - #[inline(always)] - pub(crate) fn intersect(self, other: LayoutBitset) -> LayoutBitset - { - LayoutBitset(self.0 & other.0) - } - - /// Return a layout that simultaneously "is" what both of the inputs are - #[inline(always)] - pub(crate) fn also(self, other: LayoutBitset) -> LayoutBitset - { - LayoutBitset(self.0 | other.0) - } - - #[inline(always)] - pub(crate) fn one_dimensional() -> LayoutBitset - { - LayoutBitset::c().also(LayoutBitset::f()) - } - - #[inline(always)] - pub(crate) fn c() -> LayoutBitset - { - LayoutBitset(LayoutBitset::CORDER | LayoutBitset::CPREFER) - } - - #[inline(always)] - pub(crate) fn f() -> LayoutBitset - { - LayoutBitset(LayoutBitset::FORDER | LayoutBitset::FPREFER) - } - - #[inline(always)] - pub(crate) fn cpref() -> LayoutBitset - { - LayoutBitset(LayoutBitset::CPREFER) - } - - #[inline(always)] - pub(crate) fn fpref() -> LayoutBitset - { - LayoutBitset(LayoutBitset::FPREFER) - } - - #[inline(always)] - pub(crate) fn none() -> LayoutBitset - { - LayoutBitset(0) - } - - /// A simple "score" method which scores positive for preferring C-order, negative for F-order - /// Subject to change when we can describe other layouts - #[inline] - pub(crate) fn tendency(self) -> i32 - { - (self.is(LayoutBitset::CORDER) as i32 - self.is(LayoutBitset::FORDER) as i32) - + (self.is(LayoutBitset::CPREFER) as i32 - self.is(LayoutBitset::FPREFER) as i32) - } -} - -const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"]; - -impl fmt::Debug for LayoutBitset -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result - { - if self.0 == 0 { - write!(f, "Custom")? - } else { - (0..32).filter(|&i| self.is(1 << i)).try_fold((), |_, i| { - if let Some(name) = LAYOUT_NAMES.get(i) { - write!(f, "{}", name) - } else { - write!(f, "{:#x}", i) - } - })?; - }; - write!(f, " ({:#x})", self.0) - } -} - -#[cfg(test)] -mod tests -{ - use super::*; - use crate::imp_prelude::*; - use crate::NdProducer; - - type M = Array2; - type M1 = Array1; - type M0 = Array0; - - macro_rules! assert_layouts { - ($mat:expr, $($layout:ident),*) => {{ - let layout = $mat.view().layout(); - $( - assert!(layout.is(LayoutBitset::$layout), - "Assertion failed: array {:?} is not layout {}", - $mat, - stringify!($layout)); - )* - }}; - } - - macro_rules! assert_not_layouts { - ($mat:expr, $($layout:ident),*) => {{ - let layout = $mat.view().layout(); - $( - assert!(!layout.is(LayoutBitset::$layout), - "Assertion failed: array {:?} show not have layout {}", - $mat, - stringify!($layout)); - )* - }}; - } - - #[test] - fn contig_layouts() - { - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - let ac = a.view().layout(); - let af = b.view().layout(); - assert!(ac.is(LayoutBitset::CORDER) && ac.is(LayoutBitset::CPREFER)); - assert!(!ac.is(LayoutBitset::FORDER) && !ac.is(LayoutBitset::FPREFER)); - assert!(!af.is(LayoutBitset::CORDER) && !af.is(LayoutBitset::CPREFER)); - assert!(af.is(LayoutBitset::FORDER) && af.is(LayoutBitset::FPREFER)); - } - - #[test] - fn contig_cf_layouts() - { - let a = M::zeros((5, 1)); - let b = M::zeros((1, 5).f()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); - - let a = M1::zeros(5); - let b = M1::zeros(5.f()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); - - let a = M0::zeros(()); - assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); - - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - let arow = a.slice(s![..1, ..]); - let bcol = b.slice(s![.., ..1]); - assert_layouts!(arow, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, CPREFER, FORDER, FPREFER); - - let acol = a.slice(s![.., ..1]); - let brow = b.slice(s![..1, ..]); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - } - - #[test] - fn stride_layouts() - { - let a = M::zeros((5, 5)); - - { - let v1 = a.slice(s![1.., ..]).layout(); - let v2 = a.slice(s![.., 1..]).layout(); - - assert!(v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); - } - - let b = M::zeros((5, 5).f()); - - { - let v1 = b.slice(s![1.., ..]).layout(); - let v2 = b.slice(s![.., 1..]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); - } - } - - #[test] - fn no_layouts() - { - let a = M::zeros((5, 5)); - let b = M::zeros((5, 5).f()); - - // 2D row/column matrixes - let arow = a.slice(s![0..1, ..]); - let acol = a.slice(s![.., 0..1]); - let brow = b.slice(s![0..1, ..]); - let bcol = b.slice(s![.., 0..1]); - assert_layouts!(arow, CORDER, FORDER); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, FORDER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - - // 2D row/column matrixes - now made with insert axis - for &axis in &[Axis(0), Axis(1)] { - let arow = a.slice(s![0, ..]).insert_axis(axis); - let acol = a.slice(s![.., 0]).insert_axis(axis); - let brow = b.slice(s![0, ..]).insert_axis(axis); - let bcol = b.slice(s![.., 0]).insert_axis(axis); - assert_layouts!(arow, CORDER, FORDER); - assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); - assert_layouts!(bcol, CORDER, FORDER); - assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); - } - } - - #[test] - fn skip_layouts() - { - let a = M::zeros((5, 5)); - { - let v1 = a.slice(s![..;2, ..]).layout(); - let v2 = a.slice(s![.., ..;2]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && !v2.is(LayoutBitset::FPREFER)); - } - - let b = M::zeros((5, 5).f()); - { - let v1 = b.slice(s![..;2, ..]).layout(); - let v2 = b.slice(s![.., ..;2]).layout(); - - assert!(!v1.is(LayoutBitset::CORDER) && !v1.is(LayoutBitset::CPREFER)); - assert!(!v1.is(LayoutBitset::FORDER) && !v1.is(LayoutBitset::FPREFER)); - assert!(!v2.is(LayoutBitset::CORDER) && !v2.is(LayoutBitset::CPREFER)); - assert!(!v2.is(LayoutBitset::FORDER) && v2.is(LayoutBitset::FPREFER)); - } - } -} diff --git a/src/layout/layoutfmt.rs b/src/layout/layoutfmt.rs new file mode 100644 index 000000000..f20f0caaa --- /dev/null +++ b/src/layout/layoutfmt.rs @@ -0,0 +1,32 @@ +// Copyright 2017 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::Layout; + +const LAYOUT_NAMES: &[&str] = &["C", "F", "c", "f"]; + +use std::fmt; + +impl fmt::Debug for Layout +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + if self.0 == 0 { + write!(f, "Custom")? + } else { + (0..32).filter(|&i| self.is(1 << i)).try_fold((), |_, i| { + if let Some(name) = LAYOUT_NAMES.get(i) { + write!(f, "{}", name) + } else { + write!(f, "{:#x}", i) + } + })?; + }; + write!(f, " ({:#x})", self.0) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7f549ebb2..36853848e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,4 +1,243 @@ -mod bitset; +mod layoutfmt; -#[allow(deprecated)] -pub use bitset::{Layout, LayoutBitset}; +// Layout is a bitset used for internal layout description of +// arrays, producers and sets of producers. +// The type is public but users don't interact with it. +#[doc(hidden)] +/// Memory layout description +#[derive(Copy, Clone)] +pub struct Layout(u32); + +impl Layout +{ + pub(crate) const CORDER: u32 = 0b01; + pub(crate) const FORDER: u32 = 0b10; + pub(crate) const CPREFER: u32 = 0b0100; + pub(crate) const FPREFER: u32 = 0b1000; + + #[inline(always)] + pub(crate) fn is(self, flag: u32) -> bool + { + self.0 & flag != 0 + } + + /// Return layout common to both inputs + #[inline(always)] + pub(crate) fn intersect(self, other: Layout) -> Layout + { + Layout(self.0 & other.0) + } + + /// Return a layout that simultaneously "is" what both of the inputs are + #[inline(always)] + pub(crate) fn also(self, other: Layout) -> Layout + { + Layout(self.0 | other.0) + } + + #[inline(always)] + pub(crate) fn one_dimensional() -> Layout + { + Layout::c().also(Layout::f()) + } + + #[inline(always)] + pub(crate) fn c() -> Layout + { + Layout(Layout::CORDER | Layout::CPREFER) + } + + #[inline(always)] + pub(crate) fn f() -> Layout + { + Layout(Layout::FORDER | Layout::FPREFER) + } + + #[inline(always)] + pub(crate) fn cpref() -> Layout + { + Layout(Layout::CPREFER) + } + + #[inline(always)] + pub(crate) fn fpref() -> Layout + { + Layout(Layout::FPREFER) + } + + #[inline(always)] + pub(crate) fn none() -> Layout + { + Layout(0) + } + + /// A simple "score" method which scores positive for preferring C-order, negative for F-order + /// Subject to change when we can describe other layouts + #[inline] + pub(crate) fn tendency(self) -> i32 + { + (self.is(Layout::CORDER) as i32 - self.is(Layout::FORDER) as i32) + + (self.is(Layout::CPREFER) as i32 - self.is(Layout::FPREFER) as i32) + } +} + +#[cfg(test)] +mod tests +{ + use super::*; + use crate::imp_prelude::*; + use crate::NdProducer; + + type M = Array2; + type M1 = Array1; + type M0 = Array0; + + macro_rules! assert_layouts { + ($mat:expr, $($layout:ident),*) => {{ + let layout = $mat.view().layout(); + $( + assert!(layout.is(Layout::$layout), + "Assertion failed: array {:?} is not layout {}", + $mat, + stringify!($layout)); + )* + }}; + } + + macro_rules! assert_not_layouts { + ($mat:expr, $($layout:ident),*) => {{ + let layout = $mat.view().layout(); + $( + assert!(!layout.is(Layout::$layout), + "Assertion failed: array {:?} show not have layout {}", + $mat, + stringify!($layout)); + )* + }}; + } + + #[test] + fn contig_layouts() + { + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + let ac = a.view().layout(); + let af = b.view().layout(); + assert!(ac.is(Layout::CORDER) && ac.is(Layout::CPREFER)); + assert!(!ac.is(Layout::FORDER) && !ac.is(Layout::FPREFER)); + assert!(!af.is(Layout::CORDER) && !af.is(Layout::CPREFER)); + assert!(af.is(Layout::FORDER) && af.is(Layout::FPREFER)); + } + + #[test] + fn contig_cf_layouts() + { + let a = M::zeros((5, 1)); + let b = M::zeros((1, 5).f()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); + + let a = M1::zeros(5); + let b = M1::zeros(5.f()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(b, CORDER, CPREFER, FORDER, FPREFER); + + let a = M0::zeros(()); + assert_layouts!(a, CORDER, CPREFER, FORDER, FPREFER); + + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + let arow = a.slice(s![..1, ..]); + let bcol = b.slice(s![.., ..1]); + assert_layouts!(arow, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, CPREFER, FORDER, FPREFER); + + let acol = a.slice(s![.., ..1]); + let brow = b.slice(s![..1, ..]); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + } + + #[test] + fn stride_layouts() + { + let a = M::zeros((5, 5)); + + { + let v1 = a.slice(s![1.., ..]).layout(); + let v2 = a.slice(s![.., 1..]).layout(); + + assert!(v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); + } + + let b = M::zeros((5, 5).f()); + + { + let v1 = b.slice(s![1.., ..]).layout(); + let v2 = b.slice(s![.., 1..]).layout(); + + assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); + } + } + + #[test] + fn no_layouts() + { + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + + // 2D row/column matrixes + let arow = a.slice(s![0..1, ..]); + let acol = a.slice(s![.., 0..1]); + let brow = b.slice(s![0..1, ..]); + let bcol = b.slice(s![.., 0..1]); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + + // 2D row/column matrixes - now made with insert axis + for &axis in &[Axis(0), Axis(1)] { + let arow = a.slice(s![0, ..]).insert_axis(axis); + let acol = a.slice(s![.., 0]).insert_axis(axis); + let brow = b.slice(s![0, ..]).insert_axis(axis); + let bcol = b.slice(s![.., 0]).insert_axis(axis); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + } + } + + #[test] + fn skip_layouts() + { + let a = M::zeros((5, 5)); + { + let v1 = a.slice(s![..;2, ..]).layout(); + let v2 = a.slice(s![.., ..;2]).layout(); + + assert!(!v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); + } + + let b = M::zeros((5, 5).f()); + { + let v1 = b.slice(s![..;2, ..]).layout(); + let v2 = b.slice(s![.., ..;2]).layout(); + + assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2b9b656e3..41e5ca350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,8 +222,7 @@ mod dimension; pub use crate::zip::{FoldWhile, IntoNdProducer, NdProducer, Zip}; -#[allow(deprecated)] -pub use crate::layout::{Layout, LayoutBitset}; +pub use crate::layout::Layout; /// Implementation's prelude. Common types used everywhere. mod imp_prelude diff --git a/src/parallel/send_producer.rs b/src/parallel/send_producer.rs index ebf973091..ecfb77af0 100644 --- a/src/parallel/send_producer.rs +++ b/src/parallel/send_producer.rs @@ -1,5 +1,5 @@ use crate::imp_prelude::*; -use crate::{LayoutBitset, NdProducer}; +use crate::{Layout, NdProducer}; use std::ops::{Deref, DerefMut}; /// An NdProducer that is unconditionally `Send`. @@ -66,7 +66,7 @@ where P: NdProducer } #[inline(always)] - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { self.inner.layout() } diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 16555721d..668eac093 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -17,7 +17,7 @@ use crate::imp_prelude::*; use crate::partial::Partial; use crate::AssignElem; use crate::IntoDimension; -use crate::LayoutBitset; +use crate::Layout; use crate::dimension; use crate::indexes::{indices, Indices}; @@ -51,35 +51,35 @@ where E: IntoDimension } /// Compute `Layout` hints for array shape dim, strides -fn array_layout(dim: &D, strides: &D) -> LayoutBitset +fn array_layout(dim: &D, strides: &D) -> Layout { let n = dim.ndim(); if dimension::is_layout_c(dim, strides) { // effectively one-dimensional => C and F layout compatible if n <= 1 || dim.slice().iter().filter(|&&len| len > 1).count() <= 1 { - LayoutBitset::one_dimensional() + Layout::one_dimensional() } else { - LayoutBitset::c() + Layout::c() } } else if n > 1 && dimension::is_layout_f(dim, strides) { - LayoutBitset::f() + Layout::f() } else if n > 1 { if dim[0] > 1 && strides[0] == 1 { - LayoutBitset::fpref() + Layout::fpref() } else if dim[n - 1] > 1 && strides[n - 1] == 1 { - LayoutBitset::cpref() + Layout::cpref() } else { - LayoutBitset::none() + Layout::none() } } else { - LayoutBitset::none() + Layout::none() } } impl LayoutRef where D: Dimension { - pub(crate) fn layout_impl(&self) -> LayoutBitset + pub(crate) fn layout_impl(&self) -> Layout { array_layout(self._dim(), self._strides()) } @@ -194,7 +194,7 @@ pub struct Zip { parts: Parts, dimension: D, - layout: LayoutBitset, + layout: Layout, /// The sum of the layout tendencies of the parts; /// positive for c- and negative for f-layout preference. layout_tendency: i32, @@ -277,7 +277,7 @@ where D: Dimension fn prefer_f(&self) -> bool { - !self.layout.is(LayoutBitset::CORDER) && (self.layout.is(LayoutBitset::FORDER) || self.layout_tendency < 0) + !self.layout.is(Layout::CORDER) && (self.layout.is(Layout::FORDER) || self.layout_tendency < 0) } /// Return an *approximation* to the max stride axis; if @@ -313,7 +313,7 @@ where D: Dimension { if self.dimension.ndim() == 0 { function(acc, unsafe { self.parts.as_ref(self.parts.as_ptr()) }) - } else if self.layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER) { + } else if self.layout.is(Layout::CORDER | Layout::FORDER) { self.for_each_core_contiguous(acc, function) } else { self.for_each_core_strided(acc, function) @@ -325,7 +325,7 @@ where D: Dimension F: FnMut(Acc, P::Item) -> FoldWhile, P: ZippableTuple, { - debug_assert!(self.layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER)); + debug_assert!(self.layout.is(Layout::CORDER | Layout::FORDER)); let size = self.dimension.size(); let ptrs = self.parts.as_ptr(); let inner_strides = self.parts.contiguous_stride(); @@ -442,7 +442,7 @@ where #[inline] pub(crate) fn debug_assert_c_order(self) -> Self { - debug_assert!(self.layout.is(LayoutBitset::CORDER) || self.layout_tendency >= 0 || + debug_assert!(self.layout.is(Layout::CORDER) || self.layout_tendency >= 0 || self.dimension.slice().iter().filter(|&&d| d > 1).count() <= 1, "Assertion failed: traversal is not c-order or 1D for \ layout {:?}, tendency {}, dimension {:?}", @@ -841,7 +841,7 @@ macro_rules! map_impl { // debug assert that the output is contiguous in the memory layout we need if cfg!(debug_assertions) { let out_layout = output.layout(); - assert!(out_layout.is(LayoutBitset::CORDER | LayoutBitset::FORDER)); + assert!(out_layout.is(Layout::CORDER | Layout::FORDER)); assert!( (self.layout_tendency <= 0 && out_layout.tendency() <= 0) || (self.layout_tendency >= 0 && out_layout.tendency() >= 0), diff --git a/src/zip/ndproducer.rs b/src/zip/ndproducer.rs index f06497c29..fe666e81e 100644 --- a/src/zip/ndproducer.rs +++ b/src/zip/ndproducer.rs @@ -1,6 +1,6 @@ use crate::imp_prelude::*; use crate::ArrayRef; -use crate::LayoutBitset; +use crate::Layout; use crate::NdIndex; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -74,7 +74,7 @@ pub trait NdProducer type Stride: Copy; #[doc(hidden)] - fn layout(&self) -> LayoutBitset; + fn layout(&self) -> Layout; /// Return the shape of the producer. fn raw_dim(&self) -> Self::Dim; #[doc(hidden)] @@ -282,7 +282,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> (**self).as_ptr() as _ } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { self.layout_impl() } @@ -340,7 +340,7 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> (**self).as_ptr() as _ } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { self.layout_impl() } @@ -398,7 +398,7 @@ impl NdProducer for RawArrayView self.as_ptr() as _ } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { AsRef::>::as_ref(self).layout_impl() } @@ -457,7 +457,7 @@ impl NdProducer for RawArrayViewMut self.as_ptr() as _ } - fn layout(&self) -> LayoutBitset + fn layout(&self) -> Layout { AsRef::>::as_ref(self).layout_impl() } From 6fd0a9df5ce4edd1f23845ad1fe717da3b5db7f0 Mon Sep 17 00:00:00 2001 From: Alex <45256796+RPG-Alex@users.noreply.github.com> Date: Sun, 11 Jan 2026 01:25:54 +0800 Subject: [PATCH 45/54] Clean up clippy allows and unnecessary borrows (#1571) --- benches/bench1.rs | 4 +--- benches/construct.rs | 4 +--- benches/gemv_gemm.rs | 4 +--- benches/higher-order.rs | 4 +--- benches/iter.rs | 6 ++---- benches/par_rayon.rs | 4 ++-- examples/axis_ops.rs | 4 +--- examples/bounds_check_elim.rs | 5 +---- examples/functions_and_traits.rs | 4 ++-- examples/life.rs | 4 +--- examples/type_conversion.rs | 2 +- examples/zip_many.rs | 4 +--- ndarray-rand/tests/tests.rs | 2 +- src/dimension/broadcast.rs | 2 +- src/dimension/mod.rs | 8 ++++---- src/linalg/impl_linalg.rs | 6 +++--- src/tri.rs | 2 -- tests/append.rs | 8 ++++---- tests/array-construct.rs | 4 +--- tests/array.rs | 13 ++++++------- tests/azip.rs | 13 +++---------- tests/into-ixdyn.rs | 3 +-- tests/iterator_chunks.rs | 3 +-- tests/iterators.rs | 6 +++--- tests/ix0.rs | 3 +-- tests/ixdyn.rs | 3 +-- tests/numeric.rs | 11 +++++------ tests/oper.rs | 28 ++++++++++++++-------------- tests/s.rs | 4 +--- tests/windows.rs | 4 +--- 30 files changed, 66 insertions(+), 106 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index c07b8e3d9..ea527cd35 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,8 +1,6 @@ #![feature(test)] #![allow(unused_imports)] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] extern crate test; diff --git a/benches/construct.rs b/benches/construct.rs index 380d87799..71a4fb905 100644 --- a/benches/construct.rs +++ b/benches/construct.rs @@ -1,7 +1,5 @@ #![feature(test)] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] extern crate test; use test::Bencher; diff --git a/benches/gemv_gemm.rs b/benches/gemv_gemm.rs index 2d1642623..ccd987250 100644 --- a/benches/gemv_gemm.rs +++ b/benches/gemv_gemm.rs @@ -1,7 +1,5 @@ #![feature(test)] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] extern crate test; use test::Bencher; diff --git a/benches/higher-order.rs b/benches/higher-order.rs index 1b4e8340c..5eb009566 100644 --- a/benches/higher-order.rs +++ b/benches/higher-order.rs @@ -1,7 +1,5 @@ #![feature(test)] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] extern crate test; use test::black_box; use test::Bencher; diff --git a/benches/iter.rs b/benches/iter.rs index 154ee4eaf..bc483c8c2 100644 --- a/benches/iter.rs +++ b/benches/iter.rs @@ -1,7 +1,5 @@ #![feature(test)] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] extern crate test; use rawpointer::PointerExt; @@ -416,7 +414,7 @@ fn iter_axis_chunks_5_iter_sum(bench: &mut Bencher) pub fn zip_mut_with(data: &Array3, out: &mut Array3) { - out.zip_mut_with(&data, |o, &i| { + out.zip_mut_with(data, |o, &i| { *o = i; }); } diff --git a/benches/par_rayon.rs b/benches/par_rayon.rs index 1301ae75a..95b514278 100644 --- a/benches/par_rayon.rs +++ b/benches/par_rayon.rs @@ -54,7 +54,7 @@ fn fastexp(x: f64) -> f64 fn map_fastexp_regular(bench: &mut Bencher) { let mut a = Array2::::zeros((FASTEXP, FASTEXP)); - bench.iter(|| a.mapv_inplace(|x| fastexp(x))); + bench.iter(|| a.mapv_inplace(fastexp)); } #[bench] @@ -72,7 +72,7 @@ fn map_fastexp_cut(bench: &mut Bencher) { let mut a = Array2::::zeros((FASTEXP, FASTEXP)); let mut a = a.slice_mut(s![.., ..-1]); - bench.iter(|| a.mapv_inplace(|x| fastexp(x))); + bench.iter(|| a.mapv_inplace(fastexp)); } #[bench] diff --git a/examples/axis_ops.rs b/examples/axis_ops.rs index 7f80a637f..0469747f3 100644 --- a/examples/axis_ops.rs +++ b/examples/axis_ops.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::prelude::*; diff --git a/examples/bounds_check_elim.rs b/examples/bounds_check_elim.rs index f1a91cca0..ef20e9ad2 100644 --- a/examples/bounds_check_elim.rs +++ b/examples/bounds_check_elim.rs @@ -1,7 +1,5 @@ #![crate_type = "lib"] -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] // Test cases for bounds check elimination @@ -57,7 +55,6 @@ pub fn test1d_single_mut(a: &mut Array1, i: usize) -> f64 #[no_mangle] pub fn test1d_len_of(a: &Array1) -> f64 { - let a = a; let mut sum = 0.; for i in 0..a.len_of(Axis(0)) { sum += a[i]; diff --git a/examples/functions_and_traits.rs b/examples/functions_and_traits.rs index 9710a9ff5..7091a5e17 100644 --- a/examples/functions_and_traits.rs +++ b/examples/functions_and_traits.rs @@ -74,8 +74,8 @@ fn takes_base(arr: &ArrayBase) // We can also pass it to functions that accept `RawRef` and `LayoutRef` // in the usual two ways: - takes_rawref(&arr); - takes_layout(&arr); + takes_rawref(arr); + takes_layout(arr); // takes_rawref_asref(&arr); takes_layout_asref(&arr); diff --git a/examples/life.rs b/examples/life.rs index 7db384678..a521f34c4 100644 --- a/examples/life.rs +++ b/examples/life.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::prelude::*; diff --git a/examples/type_conversion.rs b/examples/type_conversion.rs index cfcd7e564..722991d48 100644 --- a/examples/type_conversion.rs +++ b/examples/type_conversion.rs @@ -34,7 +34,7 @@ fn main() // can be guaranteed to be lossless at compile time. This is the safest // approach. let a_u8: Array = array![[1, 2, 3], [4, 5, 6]]; - let a_f32 = a_u8.mapv(|element| f32::from(element)); + let a_f32 = a_u8.mapv(f32::from); assert_abs_diff_eq!(a_f32, array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); // Fallible, lossless conversion with `TryFrom` diff --git a/examples/zip_many.rs b/examples/zip_many.rs index 57d66a956..944194130 100644 --- a/examples/zip_many.rs +++ b/examples/zip_many.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::prelude::*; use ndarray::Zip; diff --git a/ndarray-rand/tests/tests.rs b/ndarray-rand/tests/tests.rs index 5d322551a..b1a80e5e6 100644 --- a/ndarray-rand/tests/tests.rs +++ b/ndarray-rand/tests/tests.rs @@ -128,7 +128,7 @@ fn sampling_works(a: &Array2, strategy: SamplingStrategy, axis: Axis, n_sam // Check if, when sliced along `axis`, there is at least one lane in `a` equal to `b` fn is_subset(a: &Array2, b: &ArrayView1, axis: Axis) -> bool { - a.axis_iter(axis).any(|lane| &lane == b) + a.axis_iter(axis).any(|lane| lane == b) } #[test] diff --git a/src/dimension/broadcast.rs b/src/dimension/broadcast.rs index 9e7474388..8fb445cb8 100644 --- a/src/dimension/broadcast.rs +++ b/src/dimension/broadcast.rs @@ -111,7 +111,7 @@ mod tests D1: Dimension + DimMax, D2: Dimension, { - let d = co_broadcast::>::Output>(&d1, d2); + let d = co_broadcast::>::Output>(d1, d2); assert_eq!(d, r); } test_co(&Dim([2, 3]), &Dim([4, 1, 3]), Ok(Dim([4, 2, 3]))); diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs index 4ada0b127..68f625115 100644 --- a/src/dimension/mod.rs +++ b/src/dimension/mod.rs @@ -850,17 +850,17 @@ mod test #[test] fn max_abs_offset_check_overflow_examples() { - let dim = (1, ::std::isize::MAX as usize, 1).into_dimension(); + let dim = (1, isize::MAX as usize, 1).into_dimension(); let strides = (1, 1, 1).into_dimension(); max_abs_offset_check_overflow::(&dim, &strides).unwrap(); - let dim = (1, ::std::isize::MAX as usize, 2).into_dimension(); + let dim = (1, isize::MAX as usize, 2).into_dimension(); let strides = (1, 1, 1).into_dimension(); max_abs_offset_check_overflow::(&dim, &strides).unwrap_err(); let dim = (0, 2, 2).into_dimension(); - let strides = (1, ::std::isize::MAX as usize, 1).into_dimension(); + let strides = (1, isize::MAX as usize, 1).into_dimension(); max_abs_offset_check_overflow::(&dim, &strides).unwrap_err(); let dim = (0, 2, 2).into_dimension(); - let strides = (1, ::std::isize::MAX as usize / 4, 1).into_dimension(); + let strides = (1, isize::MAX as usize / 4, 1).into_dimension(); max_abs_offset_check_overflow::(&dim, &strides).unwrap_err(); } diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index 14c82ff4d..7dfe98578 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -435,12 +435,12 @@ where A: LinalgScalar { let cblas_layout = c_layout.to_cblas_layout(); let a_trans = a_layout.to_cblas_transpose_for(cblas_layout); - let lda = blas_stride(&a, a_layout); + let lda = blas_stride(a, a_layout); let b_trans = b_layout.to_cblas_transpose_for(cblas_layout); - let ldb = blas_stride(&b, b_layout); + let ldb = blas_stride(b, b_layout); - let ldc = blas_stride(&c, c_layout); + let ldc = blas_stride(c, c_layout); macro_rules! gemm_scalar_cast { (f32, $var:ident) => { diff --git a/src/tri.rs b/src/tri.rs index 9f15db71a..1e10c3b6a 100644 --- a/src/tri.rs +++ b/src/tri.rs @@ -159,8 +159,6 @@ where #[cfg(test)] mod tests { - use core::isize; - use crate::{array, dimension, Array0, Array1, Array2, Array3, ShapeBuilder}; use alloc::vec; diff --git a/tests/append.rs b/tests/append.rs index cf5397de1..40beb0f92 100644 --- a/tests/append.rs +++ b/tests/append.rs @@ -255,14 +255,14 @@ fn append_array_3d() let aa = array![[[51, 52], [53, 54]], [[55, 56], [57, 58]]]; let av = aa.view(); println!("Send {:?} to append", av); - a.append(Axis(0), av.clone()).unwrap(); + a.append(Axis(0), av).unwrap(); a.swap_axes(0, 1); let aa = array![[[71, 72], [73, 74]], [[75, 76], [77, 78]]]; let mut av = aa.view(); av.swap_axes(0, 1); println!("Send {:?} to append", av); - a.append(Axis(1), av.clone()).unwrap(); + a.append(Axis(1), av).unwrap(); println!("{:?}", a); let aa = array![[[81, 82], [83, 84]], [[85, 86], [87, 88]]]; let mut av = aa.view(); @@ -304,7 +304,7 @@ fn test_append_2d() println!("{:?}", a); assert_eq!(a.shape(), &[8, 4]); for (i, row) in a.rows().into_iter().enumerate() { - let ones = i < 3 || i >= 5; + let ones = !(3..5).contains(&i); assert!(row.iter().all(|&x| x == ones as i32 as f64), "failed on lane {}", i); } @@ -319,7 +319,7 @@ fn test_append_2d() assert_eq!(a.shape(), &[4, 8]); for (i, row) in a.columns().into_iter().enumerate() { - let ones = i < 3 || i >= 5; + let ones = !(3..5).contains(&i); assert!(row.iter().all(|&x| x == ones as i32 as f64), "failed on lane {}", i); } } diff --git a/tests/array-construct.rs b/tests/array-construct.rs index 9f8418467..ec8cedf3f 100644 --- a/tests/array-construct.rs +++ b/tests/array-construct.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use defmac::defmac; use ndarray::arr3; diff --git a/tests/array.rs b/tests/array.rs index 6512043b2..512227bb6 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use approx::assert_relative_eq; @@ -1006,7 +1005,7 @@ fn iter_size_hint() fn zero_axes() { let mut a = arr1::(&[]); - for _ in a.iter() { + if let Some(_) = a.iter().next() { panic!(); } a.map(|_| panic!()); @@ -1112,12 +1111,12 @@ fn as_slice_memory_order_mut_contiguous_cowarray() #[test] fn to_slice_memory_order() { - for shape in vec![[2, 0, 3, 5], [2, 1, 3, 5], [2, 4, 3, 5]] { + for shape in [[2, 0, 3, 5], [2, 1, 3, 5], [2, 4, 3, 5]] { let data: Vec = (0..shape.iter().product()).collect(); let mut orig = Array1::from(data.clone()) .into_shape_with_order(shape) .unwrap(); - for perm in vec![[0, 1, 2, 3], [0, 2, 1, 3], [2, 0, 1, 3]] { + for perm in [[0, 1, 2, 3], [0, 2, 1, 3], [2, 0, 1, 3]] { let mut a = orig.view_mut().permuted_axes(perm); assert_eq!(a.as_slice_memory_order().unwrap(), &data); assert_eq!(a.as_slice_memory_order_mut().unwrap(), &data); @@ -2173,7 +2172,7 @@ fn test_contiguous_neg_strides() assert_eq!(f, arr3(&[[[11], [9]], [[10], [8]]])); assert!(f.as_slice_memory_order().is_some()); - let mut g = b.clone(); + let mut g = b; g.collapse_axis(Axis(1), 0); assert_eq!(g, arr3(&[[[11, 7, 3]], [[10, 6, 2]]])); assert!(g.as_slice_memory_order().is_none()); @@ -2568,7 +2567,7 @@ mod array_cow_tests fn run_with_various_layouts(mut f: impl FnMut(Array2)) { - for all in vec![ + for all in [ Array2::from_shape_vec((7, 8), (0..7 * 8).collect()).unwrap(), Array2::from_shape_vec((7, 8).f(), (0..7 * 8).collect()).unwrap(), ] { diff --git a/tests/azip.rs b/tests/azip.rs index 9d8bebab7..d10476207 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use ndarray::prelude::*; @@ -138,17 +137,11 @@ fn test_zip_collect_drop() { fn a_is_f(self) -> bool { - match self { - Config::CC | Config::CF => false, - _ => true, - } + !matches!(self, Config::CC | Config::CF) } fn b_is_f(self) -> bool { - match self { - Config::CC => false, - _ => true, - } + !matches!(self, Config::CC) } } diff --git a/tests/into-ixdyn.rs b/tests/into-ixdyn.rs index 6e7bf9607..410ce92b5 100644 --- a/tests/into-ixdyn.rs +++ b/tests/into-ixdyn.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use ndarray::prelude::*; diff --git a/tests/iterator_chunks.rs b/tests/iterator_chunks.rs index c16d8bc81..d46482937 100644 --- a/tests/iterator_chunks.rs +++ b/tests/iterator_chunks.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use ndarray::prelude::*; diff --git a/tests/iterators.rs b/tests/iterators.rs index 9890e05a7..e8933a823 100644 --- a/tests/iterators.rs +++ b/tests/iterators.rs @@ -29,7 +29,7 @@ fn double_ended() assert_eq!(it.next(), Some(0)); assert_eq!(it.next_back(), Some(7)); assert_eq!(it.next(), Some(1)); - assert_eq!(it.rev().last(), Some(2)); + assert_eq!(it.rev().next_back(), Some(2)); assert_equal(aview1(&[1, 2, 3]), &[1, 2, 3]); assert_equal(aview1(&[1, 2, 3]).into_iter().rev(), [1, 2, 3].iter().rev()); } @@ -1058,7 +1058,7 @@ fn test_impl_iter_compiles() // Requires that the iterators are covariant in the element type // base case: std - fn slice_iter_non_empty_indices<'s, 'a>(array: &'a Vec<&'s str>) -> impl Iterator + 'a + fn slice_iter_non_empty_indices<'a>(array: &'a Vec<&str>) -> impl Iterator + 'a { array .iter() @@ -1070,7 +1070,7 @@ fn test_impl_iter_compiles() let _ = slice_iter_non_empty_indices; // ndarray case - fn array_iter_non_empty_indices<'s, 'a>(array: &'a Array<&'s str, Ix1>) -> impl Iterator + 'a + fn array_iter_non_empty_indices<'a>(array: &'a Array<&str, Ix1>) -> impl Iterator + 'a { array .iter() diff --git a/tests/ix0.rs b/tests/ix0.rs index f1038556a..319de39e3 100644 --- a/tests/ix0.rs +++ b/tests/ix0.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use ndarray::Array; diff --git a/tests/ixdyn.rs b/tests/ixdyn.rs index f14df9f0e..517975145 100644 --- a/tests/ixdyn.rs +++ b/tests/ixdyn.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use ndarray::Array; diff --git a/tests/numeric.rs b/tests/numeric.rs index 7e6964812..11a9fce76 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -1,6 +1,5 @@ #![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names, - clippy::float_cmp + clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::float_cmp )] use approx::assert_abs_diff_eq; @@ -176,7 +175,7 @@ fn var_too_large_ddof() fn var_nan_ddof() { let a = Array2::::zeros((2, 3)); - let v = a.var(::std::f64::NAN); + let v = a.var(std::f64::NAN); assert!(v.is_nan()); } @@ -219,7 +218,7 @@ fn std_too_large_ddof() fn std_nan_ddof() { let a = Array2::::zeros((2, 3)); - let v = a.std(::std::f64::NAN); + let v = a.std(f64::NAN); assert!(v.is_nan()); } @@ -344,7 +343,7 @@ fn std_axis() ); assert_abs_diff_eq!( b.std_axis(Axis(1), 0.), - aview1(&[47140.214021552769]), + aview1(&[47_140.214_021_552_77]), epsilon = 1e-6, ); @@ -375,7 +374,7 @@ fn var_axis_too_large_ddof() fn var_axis_nan_ddof() { let a = Array2::::zeros((2, 3)); - let v = a.var_axis(Axis(1), ::std::f64::NAN); + let v = a.var_axis(Axis(1), f64::NAN); assert_eq!(v.shape(), &[2]); v.mapv(|x| assert!(x.is_nan())); } diff --git a/tests/oper.rs b/tests/oper.rs index 0751c0c13..4f68d27d3 100644 --- a/tests/oper.rs +++ b/tests/oper.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::linalg::general_mat_mul; use ndarray::linalg::kron; use ndarray::prelude::*; @@ -148,18 +146,16 @@ fn dot_product() assert_abs_diff_eq!(a2.dot(&b2), reference_dot(&a2, &b2), epsilon = 1e-5); } - let a = a.map(|f| *f as f32); - let b = b.map(|f| *f as f32); assert_abs_diff_eq!(a.dot(&b), dot as f32, epsilon = 1e-5); let max = 8 as Ixs; for i in 1..max { let a1 = a.slice(s![i..]); let b1 = b.slice(s![i..]); - assert_abs_diff_eq!(a1.dot(&b1), reference_dot(&a1, &b1), epsilon = 1e-5); + assert_abs_diff_eq!(a1.dot(&b1), reference_dot(a1, b1), epsilon = 1e-5); let a2 = a.slice(s![..-i]); let b2 = b.slice(s![i..]); - assert_abs_diff_eq!(a2.dot(&b2), reference_dot(&a2, &b2), epsilon = 1e-5); + assert_abs_diff_eq!(a2.dot(&b2), reference_dot(a2, b2), epsilon = 1e-5); } let a = a.map(|f| *f as i32); @@ -175,17 +171,17 @@ fn dot_product_0() let x = 1.5; let b = aview0(&x); let b = b.broadcast(a.dim()).unwrap(); - assert_abs_diff_eq!(a.dot(&b), reference_dot(&a, &b), epsilon = 1e-5); + assert_abs_diff_eq!(a.dot(&b), reference_dot(&a, b), epsilon = 1e-5); // test different alignments let max = 8 as Ixs; for i in 1..max { let a1 = a.slice(s![i..]); let b1 = b.slice(s![i..]); - assert_abs_diff_eq!(a1.dot(&b1), reference_dot(&a1, &b1), epsilon = 1e-5); + assert_abs_diff_eq!(a1.dot(&b1), reference_dot(a1, b1), epsilon = 1e-5); let a2 = a.slice(s![..-i]); let b2 = b.slice(s![i..]); - assert_abs_diff_eq!(a2.dot(&b2), reference_dot(&a2, &b2), epsilon = 1e-5); + assert_abs_diff_eq!(a2.dot(&b2), reference_dot(a2, b2), epsilon = 1e-5); } } @@ -199,13 +195,13 @@ fn dot_product_neg_stride() // both negative let a = a.slice(s![..;stride]); let b = b.slice(s![..;stride]); - assert_abs_diff_eq!(a.dot(&b), reference_dot(&a, &b), epsilon = 1e-5); + assert_abs_diff_eq!(a.dot(&b), reference_dot(a, b), epsilon = 1e-5); } for stride in -10..0 { // mixed let a = a.slice(s![..;-stride]); let b = b.slice(s![..;stride]); - assert_abs_diff_eq!(a.dot(&b), reference_dot(&a, &b), epsilon = 1e-5); + assert_abs_diff_eq!(a.dot(&b), reference_dot(a, b), epsilon = 1e-5); } } @@ -598,8 +594,10 @@ fn scaled_add_3() #[test] fn gen_mat_mul() { + use core::f64; + let alpha = -2.3; - let beta = 3.14; + let beta = f64::consts::PI; let sizes = vec![ (4, 4, 4), (8, 8, 8), @@ -687,6 +685,8 @@ fn gen_mat_mul_i32() #[cfg_attr(miri, ignore)] // Takes too long fn gen_mat_vec_mul() { + use core::f64; + use approx::assert_relative_eq; use ndarray::linalg::general_mat_vec_mul; @@ -709,7 +709,7 @@ fn gen_mat_vec_mul() } let alpha = -2.3; - let beta = 3.14; + let beta = f64::consts::PI; let sizes = vec![ (4, 4), (8, 8), diff --git a/tests/s.rs b/tests/s.rs index edb3f071a..27e009ebc 100644 --- a/tests/s.rs +++ b/tests/s.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::{s, Array}; diff --git a/tests/windows.rs b/tests/windows.rs index 4d4d0d7d7..7d0f36990 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -1,6 +1,4 @@ -#![allow( - clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::many_single_char_names -)] +#![allow(clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal)] use ndarray::prelude::*; use ndarray::{arr3, Zip}; From 59c1ce06635fb1f427836ab67be8ac3122d075e6 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sat, 10 Jan 2026 13:24:01 -0500 Subject: [PATCH 46/54] Craft a release log for 0.17.2 (#1572) --- RELEASES.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index cd335fb86..21f875a92 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,23 @@ +Version 0.17.2 (2026-01-10) +=========================== +Version 0.17.2 is mainly a patch fix to bugs related to the new `ArrayRef` implementation. + +In addition, `ndarray` has reduced its packaging footprint to ease supply chain reviews (and shrink the binary size!). +A special thanks to @SwishSwushPow and @weiznich for bringing this to our attention and making the necessary changes. + +Added +----- +- Add type aliases for higher-dimensional ArcArrays by [@varchasgopalaswamy](https://github.com/varchasgopalaswamy) [#1561](https://github.com/rust-ndarray/ndarray/pull/1561) + +Fixed +----- +- Add PartialEq implementations between ArrayRef and ArrayBase by [@akern40](https://github.com/akern40) [#1557](https://github.com/rust-ndarray/ndarray/pull/1557) +- Implement Sync for ArrayParts by [@gaumut](https://github.com/gaumut) [#1552](https://github.com/rust-ndarray/ndarray/pull/1552) + +Documentation +------------- +- fix some typos in comments by [@tinyfoolish](https://github.com/tinyfoolish) [#1547](https://github.com/rust-ndarray/ndarray/pull/1547) + Version 0.17.1 (2025-11-02) =========================== Version 0.17.1 provides a patch to fix the originally-unsound implementation of the new array reference types. From 1eb45593abe4db578024911562123a5c72fef74e Mon Sep 17 00:00:00 2001 From: akern40 Date: Sat, 10 Jan 2026 18:04:53 -0500 Subject: [PATCH 47/54] Remove most version specifiers in `README` (#1573) As we try to release versions more quickly, I'd like to avoid our README looking out of date. I suspect that almost everyone nowadays uses `cargo add` to add dependencies, rather than putting them directly into their `Cargo.toml`, so I've opted to just add a short instruction for installation. --- README.rst | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 49558b1c1..8deda7f5f 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,10 @@ Highlights elements from the end of the axis. - Views and subviews of arrays; iterators that yield subviews. +Installation +------------ +From a shell in your project folder, run `cargo add ndarray` to add the latest version. + Status and Lookout ------------------ @@ -85,7 +89,7 @@ your `Cargo.toml`. - ``approx`` - - Implementations of traits from version 0.5 of the [`approx`] crate. + - Implementations of traits the [`approx`] crate. - ``blas`` @@ -101,14 +105,6 @@ your `Cargo.toml`. - Whether ``portable-atomic`` should use ``critical-section`` -How to use with cargo ---------------------- - -:: - - [dependencies] - ndarray = "0.16.0" - How to enable BLAS integration ------------------------------ @@ -127,7 +123,7 @@ An example configuration using system openblas is shown below. Note that only end-user projects (not libraries) should select provider:: [dependencies] - ndarray = { version = "0.16.0", features = ["blas"] } + ndarray = { version = "0.x.y", features = ["blas"] } blas-src = { version = "0.10", features = ["openblas"] } openblas-src = { version = "0.10", features = ["cblas", "system"] } @@ -135,7 +131,7 @@ Using system-installed dependencies can save a long time building dependencies. An example configuration using (compiled) netlib is shown below anyway:: [dependencies] - ndarray = { version = "0.16.0", features = ["blas"] } + ndarray = { version = "0.x.y", features = ["blas"] } blas-src = { version = "0.10.0", default-features = false, features = ["netlib"] } When this is done, your program must also link to ``blas_src`` by using it or From 2cf23d6abf5f7a8a5e638fa1c69779dc4d7219a0 Mon Sep 17 00:00:00 2001 From: Adam Kern Date: Sat, 10 Jan 2026 18:19:12 -0500 Subject: [PATCH 48/54] chore: Release --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbc041a49..3cc64a637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,7 +457,7 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.17.1" +version = "0.17.2" dependencies = [ "approx", "cblas-sys", diff --git a/Cargo.toml b/Cargo.toml index efc035f00..bee7dcad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ndarray" -version = "0.17.1" +version = "0.17.2" edition = "2021" rust-version = "1.64" authors = [ From f6a32056ca206100078ef89a5ba95371dfa870b0 Mon Sep 17 00:00:00 2001 From: Ruben Felgenhauer Date: Mon, 12 Jan 2026 20:29:07 +0100 Subject: [PATCH 49/54] Expand Zip to up to 9 producers (#1559) As has been discussed in #1175, I expanded `Zip` for up to 9 producers (including `azip!`, `par_azip!`). Previously, it was only working for up to 6 producers. I also added two simple tests with 9 producers. --- src/parallel/impl_par_methods.rs | 5 ++++- src/parallel/par.rs | 3 +++ src/zip/mod.rs | 11 ++++++++++- tests/azip.rs | 19 +++++++++++++++++++ tests/par_azip.rs | 21 ++++++++++++++++++++- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/parallel/impl_par_methods.rs b/src/parallel/impl_par_methods.rs index 189436c3d..f825daa51 100644 --- a/src/parallel/impl_par_methods.rs +++ b/src/parallel/impl_par_methods.rs @@ -196,5 +196,8 @@ zip_impl! { [true P1 P2 P3], [true P1 P2 P3 P4], [true P1 P2 P3 P4 P5], - [false P1 P2 P3 P4 P5 P6], + [true P1 P2 P3 P4 P5 P6], + [true P1 P2 P3 P4 P5 P6 P7], + [true P1 P2 P3 P4 P5 P6 P7 P8], + [false P1 P2 P3 P4 P5 P6 P7 P8 P9], } diff --git a/src/parallel/par.rs b/src/parallel/par.rs index b59af4c8e..efff8e6b7 100644 --- a/src/parallel/par.rs +++ b/src/parallel/par.rs @@ -307,6 +307,9 @@ zip_impl! { [P1 P2 P3 P4], [P1 P2 P3 P4 P5], [P1 P2 P3 P4 P5 P6], + [P1 P2 P3 P4 P5 P6 P7], + [P1 P2 P3 P4 P5 P6 P7 P8], + [P1 P2 P3 P4 P5 P6 P7 P8 P9], } impl Parallel> diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 668eac093..c481e481f 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -504,6 +504,9 @@ offset_impl! { [A B C D][ a b c d], [A B C D E][ a b c d e], [A B C D E F][ a b c d e f], + [A B C D E F G][ a b c d e f g], + [A B C D E F G H][ a b c d e f g h], + [A B C D E F G H I][ a b c d e f g h i], } macro_rules! zipt_impl { @@ -563,6 +566,9 @@ zipt_impl! { [A B C D][ a b c d], [A B C D E][ a b c d e], [A B C D E F][ a b c d e f], + [A B C D E F G][ a b c d e f g], + [A B C D E F G H][ a b c d e f g h], + [A B C D E F G H I][ a b c d e f g h i], } macro_rules! map_impl { @@ -914,7 +920,10 @@ map_impl! { [true P1 P2 P3], [true P1 P2 P3 P4], [true P1 P2 P3 P4 P5], - [false P1 P2 P3 P4 P5 P6], + [true P1 P2 P3 P4 P5 P6], + [true P1 P2 P3 P4 P5 P6 P7], + [true P1 P2 P3 P4 P5 P6 P7 P8], + [false P1 P2 P3 P4 P5 P6 P7 P8 P9], } /// Value controlling the execution of `.fold_while` on `Zip`. diff --git a/tests/azip.rs b/tests/azip.rs index d10476207..f3618cb3b 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -476,3 +476,22 @@ fn test_zip_all_empty_array() assert!(Zip::from(&a).and(&b).all(|&_x, &_y| true)); assert!(Zip::from(&a).and(&b).all(|&_x, &_y| false)); } + +#[test] +fn test_azip9() +{ + let mut a = Array::::zeros(62); + let b = Array::from_shape_fn(a.dim(), |j| j as i32); + let c = Array::from_shape_fn(a.dim(), |j| (j * 2) as i32); + let d = Array::from_shape_fn(a.dim(), |j| (j * 4) as i32); + let e = Array::from_shape_fn(a.dim(), |j| (j * 8) as i32); + let f = Array::from_shape_fn(a.dim(), |j| (j * 16) as i32); + let g = Array::from_shape_fn(a.dim(), |j| (j * 32) as i32); + let h = Array::from_shape_fn(a.dim(), |j| (j * 64) as i32); + let i = Array::from_shape_fn(a.dim(), |j| (j * 128) as i32); + azip!((a in &mut a, &b in &b, &c in &c, &d in &d, &e in &e, &f in &f, &g in &g, &h in &h, &i in &i){ + *a = b + c + d + e + f + g + h + i; + }); + let x = Array::from_shape_fn(a.dim(), |j| (j * 255) as i32); + assert_equal(cloned(&a), x); +} diff --git a/tests/par_azip.rs b/tests/par_azip.rs index 418c21ef8..41011d495 100644 --- a/tests/par_azip.rs +++ b/tests/par_azip.rs @@ -1,7 +1,7 @@ #![cfg(feature = "rayon")] #[cfg(feature = "approx")] -use itertools::enumerate; +use itertools::{assert_equal, cloned, enumerate}; use ndarray::parallel::prelude::*; use ndarray::prelude::*; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -71,3 +71,22 @@ fn test_indices_1() }); assert_eq!(count.load(Ordering::SeqCst), a1.len()); } + +#[test] +fn test_par_azip9() +{ + let mut a = Array::::zeros(62); + let b = Array::from_shape_fn(a.dim(), |j| j as i32); + let c = Array::from_shape_fn(a.dim(), |j| (j * 2) as i32); + let d = Array::from_shape_fn(a.dim(), |j| (j * 4) as i32); + let e = Array::from_shape_fn(a.dim(), |j| (j * 8) as i32); + let f = Array::from_shape_fn(a.dim(), |j| (j * 16) as i32); + let g = Array::from_shape_fn(a.dim(), |j| (j * 32) as i32); + let h = Array::from_shape_fn(a.dim(), |j| (j * 64) as i32); + let i = Array::from_shape_fn(a.dim(), |j| (j * 128) as i32); + par_azip!((a in &mut a, &b in &b, &c in &c, &d in &d, &e in &e, &f in &f, &g in &g, &h in &h, &i in &i){ + *a = b + c + d + e + f + g + h + i; + }); + let x = Array::from_shape_fn(a.dim(), |j| (j * 255) as i32); + assert_equal(cloned(&a), x); +} From 49cbd5d05d7aa9becb6035df9363bbb8ec67b022 Mon Sep 17 00:00:00 2001 From: Jack Geraghty <121042936+jmg049@users.noreply.github.com> Date: Sun, 18 Jan 2026 21:43:28 +0000 Subject: [PATCH 50/54] Add phase angle calculation functions for complex arrays (#1543) Implement phase angle calculation for complex arrays, calculated as atan2(imaginary, real) in radians. Angles are wrapped to (-pi, pi]. --------- Co-authored-by: Adam Kern --- src/numeric/impl_float_maths.rs | 212 +++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) diff --git a/src/numeric/impl_float_maths.rs b/src/numeric/impl_float_maths.rs index d079985b4..6d6ebce52 100644 --- a/src/numeric/impl_float_maths.rs +++ b/src/numeric/impl_float_maths.rs @@ -1,7 +1,9 @@ // Element-wise methods for ndarray #[cfg(feature = "std")] -use num_traits::Float; +use num_complex::Complex; +#[cfg(feature = "std")] +use num_traits::{Float, Zero}; use crate::imp_prelude::*; @@ -166,6 +168,98 @@ where } } +#[cfg(feature = "std")] +impl ArrayRef +where + D: Dimension, + A: Clone + Zero, +{ + /// Map the array into the real part of a complex array; the imaginary part is 0. + /// + /// # Example + /// ``` + /// use ndarray::*; + /// use num_complex::Complex; + /// + /// let arr = array![1.0, -1.0, 0.0]; + /// let complex = arr.to_complex_re(); + /// + /// assert_eq!(complex[0], Complex::new(1.0, 0.0)); + /// assert_eq!(complex[1], Complex::new(-1.0, 0.0)); + /// assert_eq!(complex[2], Complex::new(0.0, 0.0)); + /// ``` + /// + /// # See Also + /// [ArrayRef::to_complex_im] + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn to_complex_re(&self) -> Array, D> + { + self.mapv(|v| Complex::new(v, A::zero())) + } + + /// Map the array into the imaginary part of a complex array; the real part is 0. + /// + /// # Example + /// ``` + /// use ndarray::*; + /// use num_complex::Complex; + /// + /// let arr = array![1.0, -1.0, 0.0]; + /// let complex = arr.to_complex_im(); + /// + /// assert_eq!(complex[0], Complex::new(0.0, 1.0)); + /// assert_eq!(complex[1], Complex::new(0.0, -1.0)); + /// assert_eq!(complex[2], Complex::new(0.0, 0.0)); + /// ``` + /// + /// # See Also + /// [ArrayRef::to_complex_re] + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn to_complex_im(&self) -> Array, D> + { + self.mapv(|v| Complex::new(A::zero(), v)) + } +} + +/// # Angle calculation methods for arrays +/// +/// Methods for calculating phase angles of complex values in arrays. +#[cfg(feature = "std")] +impl ArrayRef, D> +where + D: Dimension, + A: Float, +{ + /// Return the [phase angle (argument)](https://en.wikipedia.org/wiki/Argument_(complex_analysis)) of complex values in the array. + /// + /// This function always returns the same float type as was provided to it. Leaving the exact precision left to the user. + /// The angles are returned in ``radians`` and in the range ``(-π, π]``. + /// To get the angles in degrees, use the [`to_degrees()`][ArrayRef::to_degrees] method on the resulting array. + /// + /// # Examples + /// + /// ``` + /// use ndarray::array; + /// use num_complex::Complex; + /// use std::f64::consts::PI; + /// + /// let complex_arr = array![ + /// Complex::new(1.0f64, 0.0), + /// Complex::new(0.0, 1.0), + /// Complex::new(1.0, 1.0), + /// ]; + /// let angles = complex_arr.angle(); + /// assert!((angles[0] - 0.0).abs() < 1e-10); + /// assert!((angles[1] - PI/2.0).abs() < 1e-10); + /// assert!((angles[2] - PI/4.0).abs() < 1e-10); + /// ``` + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn angle(&self) -> Array + { + self.mapv(|v| v.im.atan2(v.re)) + } +} + impl ArrayRef where A: 'static + PartialOrd + Clone, @@ -190,3 +284,119 @@ where self.mapv(|a| num_traits::clamp(a, min.clone(), max.clone())) } } + +#[cfg(all(test, feature = "std"))] +mod angle_tests +{ + use crate::Array; + use num_complex::Complex; + use std::f64::consts::PI; + + /// Helper macro for floating-point comparison + macro_rules! assert_approx_eq { + ($a:expr, $b:expr, $tol:expr $(, $msg:expr)?) => {{ + let (a, b) = ($a, $b); + assert!( + (a - b).abs() < $tol, + concat!( + "assertion failed: |left - right| >= tol\n", + " left: {left:?}\n right: {right:?}\n tol: {tol:?}\n", + $($msg,)? + ), + left = a, + right = b, + tol = $tol + ); + }}; + } + + #[test] + fn test_complex_numbers_radians() + { + let arr = Array::from_vec(vec![ + Complex::new(1.0f64, 0.0), // 0 + Complex::new(0.0, 1.0), // π/2 + Complex::new(-1.0, 0.0), // π + Complex::new(0.0, -1.0), // -π/2 + Complex::new(1.0, 1.0), // π/4 + Complex::new(-1.0, -1.0), // -3π/4 + ]); + let a = arr.angle(); + + assert_approx_eq!(a[0], 0.0, 1e-10); + assert_approx_eq!(a[1], PI / 2.0, 1e-10); + assert_approx_eq!(a[2], PI, 1e-10); + assert_approx_eq!(a[3], -PI / 2.0, 1e-10); + assert_approx_eq!(a[4], PI / 4.0, 1e-10); + assert_approx_eq!(a[5], -3.0 * PI / 4.0, 1e-10); + } + + #[test] + fn test_complex_numbers_degrees() + { + let arr = Array::from_vec(vec![ + Complex::new(1.0f64, 0.0), + Complex::new(0.0, 1.0), + Complex::new(-1.0, 0.0), + Complex::new(1.0, 1.0), + ]); + let a = arr.angle().to_degrees(); + + assert_approx_eq!(a[0], 0.0, 1e-10); + assert_approx_eq!(a[1], 90.0, 1e-10); + assert_approx_eq!(a[2], 180.0, 1e-10); + assert_approx_eq!(a[3], 45.0, 1e-10); + } + + #[test] + fn test_signed_zeros() + { + let arr = Array::from_vec(vec![ + Complex::new(0.0f64, 0.0), + Complex::new(-0.0, 0.0), + Complex::new(0.0, -0.0), + Complex::new(-0.0, -0.0), + ]); + let a = arr.angle(); + + assert!(a[0] >= 0.0 && a[0].abs() < 1e-10); + assert_approx_eq!(a[1], PI, 1e-10); + assert!(a[2] <= 0.0 && a[2].abs() < 1e-10); + assert_approx_eq!(a[3], -PI, 1e-10); + } + + #[test] + fn test_edge_cases() + { + let arr = Array::from_vec(vec![ + Complex::new(f64::INFINITY, 0.0), + Complex::new(0.0, f64::INFINITY), + Complex::new(f64::NEG_INFINITY, 0.0), + Complex::new(0.0, f64::NEG_INFINITY), + ]); + let a = arr.angle(); + + assert_approx_eq!(a[0], 0.0, 1e-10); + assert_approx_eq!(a[1], PI / 2.0, 1e-10); + assert_approx_eq!(a[2], PI, 1e-10); + assert_approx_eq!(a[3], -PI / 2.0, 1e-10); + } + + #[test] + fn test_range_validation() + { + let n = 16; + let complex_arr: Vec<_> = (0..n) + .map(|i| { + let theta = 2.0 * PI * (i as f64) / (n as f64); + Complex::new(theta.cos(), theta.sin()) + }) + .collect(); + + let a = Array::from_vec(complex_arr).angle(); + + for &x in &a { + assert!(x > -PI && x <= PI, "Angle {} outside (-π, π]", x); + } + } +} From f6f06ce2f9cbf7ad945634e5703c6542d18159b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Bartos?= Date: Sat, 24 Jan 2026 22:08:25 +0100 Subject: [PATCH 51/54] fix(docs): readme: spelling mistake (#1579) --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8deda7f5f..a9630704e 100644 --- a/README.rst +++ b/README.rst @@ -70,10 +70,10 @@ your `Cargo.toml`. default `std` feature. To do so, use this in your `Cargo.toml`: :: - + [dependencies] ndarray = { version = "0.x.y", default-features = false } - + - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis` and `std_axis` methods are only available when `std` is enabled. @@ -89,7 +89,7 @@ your `Cargo.toml`. - ``approx`` - - Implementations of traits the [`approx`] crate. + - Implementations of traits from the [`approx`] crate. - ``blas`` From 1daa82fcef61d69e2a9e4ce6ec712147765ad4d7 Mon Sep 17 00:00:00 2001 From: Alex <45256796+RPG-Alex@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:47:56 +0800 Subject: [PATCH 52/54] docs/tests: fix rustdoc links and appease clippy strict lints (#1576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten linting; fix rustdoc warnings across feature gates I continued using stricter linting and found several odd cases in tests and examples, along with rustdoc warnings triggered by feature-gated items. This commit cleans up those warnings by: - adjusting tests to avoid clippy false positives under -D warnings - removing unused generics and ambiguous empty vec initializers - fixing rustdoc links via feature-gated doc attributes rather than linking to non-exported items All tests are still passing. Clippy still warns about the use of `1..-1` as an empty range; this appears intentional in ndarray slicing semantics, so I’ve left it unchanged for now. Please review the rustdoc changes that rely on feature-gated documentation. --- src/doc/crate_feature_flags.rs | 16 ++--- src/lib.rs | 13 +++- src/linalg/impl_linalg.rs | 114 ++++++++++++++++----------------- src/zip/mod.rs | 1 + tests/array.rs | 5 +- tests/iterators.rs | 6 +- tests/numeric.rs | 2 +- tests/oper.rs | 8 +-- 8 files changed, 88 insertions(+), 77 deletions(-) diff --git a/src/doc/crate_feature_flags.rs b/src/doc/crate_feature_flags.rs index fc2c2bd49..9cfe33313 100644 --- a/src/doc/crate_feature_flags.rs +++ b/src/doc/crate_feature_flags.rs @@ -14,9 +14,14 @@ //! ## `serde` //! - Enables serialization support for serde 1.x //! -//! ## `rayon` -//! - Enables parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]. -//! - Implies std +#![cfg_attr( + not(feature = "rayon"), + doc = "//! ## `rayon`\n//! - Enables parallel iterators, parallelized methods, and the `par_azip!` macro.\n//! - Implies std\n" +)] +#![cfg_attr( + feature = "rayon", + doc = "//! ## `rayon`\n//! - Enables parallel iterators, parallelized methods, the [`crate::parallel`] module and [`crate::parallel::par_azip`].\n//! - Implies std\n" +)] //! //! ## `approx` //! - Enables implementations of traits of the [`approx`] crate. @@ -28,8 +33,3 @@ //! //! ## `matrixmultiply-threading` //! - Enable the ``threading`` feature in the matrixmultiply package -//! -//! [`parallel`]: crate::parallel - -#[cfg(doc)] -use crate::parallel::par_azip; diff --git a/src/lib.rs b/src/lib.rs index 41e5ca350..91c2bb477 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,11 +84,18 @@ //! ## Crate Feature Flags //! //! The following crate feature flags are available. They are configured in your -//! `Cargo.toml`. See [`doc::crate_feature_flags`] for more information. +//! `Cargo.toml`. See [`crate::doc::crate_feature_flags`] for more information. //! //! - `std`: Rust standard library-using functionality (enabled by default) //! - `serde`: serialization support for serde 1.x -//! - `rayon`: Parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]. +#![cfg_attr( + not(feature = "rayon"), + doc = "//! - `rayon`: Parallel iterators, parallelized methods, and the `par_azip!` macro." +)] +#![cfg_attr( + feature = "rayon", + doc = "//! - `rayon`: Parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]." +)] //! - `approx` Implementations of traits from the [`approx`] crate. //! - `blas`: transparent BLAS support for matrix multiplication, needs configuration. //! - `matrixmultiply-threading`: Use threading from `matrixmultiply`. @@ -129,7 +136,7 @@ extern crate std; #[cfg(feature = "blas")] extern crate cblas_sys; -#[cfg(docsrs)] +#[cfg(any(doc, docsrs))] pub mod doc; use alloc::fmt::Debug; diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index 7dfe98578..81c942bc3 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -968,6 +968,63 @@ where is_blas_2d(a._dim(), a._strides(), BlasOrder::F) } +/// Dot product for dynamic-dimensional arrays (`ArrayD`). +/// +/// For one-dimensional arrays, computes the vector dot product, which is the sum +/// of the elementwise products (no conjugation of complex operands). +/// Both arrays must have the same length. +/// +/// For two-dimensional arrays, performs matrix multiplication. The array shapes +/// must be compatible in the following ways: +/// - If `self` is *M* × *N*, then `rhs` must be *N* × *K* for matrix-matrix multiplication +/// - If `self` is *M* × *N* and `rhs` is *N*, returns a vector of length *M* +/// - If `self` is *M* and `rhs` is *M* × *N*, returns a vector of length *N* +/// - If both arrays are one-dimensional of length *N*, returns a scalar +/// +/// **Panics** if: +/// - The arrays have dimensions other than 1 or 2 +/// - The array shapes are incompatible for the operation +/// - For vector dot product: the vectors have different lengths +impl Dot> for ArrayRef +where A: LinalgScalar +{ + type Output = Array; + + fn dot(&self, rhs: &ArrayRef) -> Self::Output + { + match (self.ndim(), rhs.ndim()) { + (1, 1) => { + let a = self.view().into_dimensionality::().unwrap(); + let b = rhs.view().into_dimensionality::().unwrap(); + let result = a.dot(&b); + ArrayD::from_elem(vec![], result) + } + (2, 2) => { + // Matrix-matrix multiplication + let a = self.view().into_dimensionality::().unwrap(); + let b = rhs.view().into_dimensionality::().unwrap(); + let result = a.dot(&b); + result.into_dimensionality::().unwrap() + } + (2, 1) => { + // Matrix-vector multiplication + let a = self.view().into_dimensionality::().unwrap(); + let b = rhs.view().into_dimensionality::().unwrap(); + let result = a.dot(&b); + result.into_dimensionality::().unwrap() + } + (1, 2) => { + // Vector-matrix multiplication + let a = self.view().into_dimensionality::().unwrap(); + let b = rhs.view().into_dimensionality::().unwrap(); + let result = a.dot(&b); + result.into_dimensionality::().unwrap() + } + _ => panic!("Dot product for ArrayD is only supported for 1D and 2D arrays"), + } + } +} + #[cfg(test)] #[cfg(feature = "blas")] mod blas_tests @@ -1083,60 +1140,3 @@ mod blas_tests } } } - -/// Dot product for dynamic-dimensional arrays (`ArrayD`). -/// -/// For one-dimensional arrays, computes the vector dot product, which is the sum -/// of the elementwise products (no conjugation of complex operands). -/// Both arrays must have the same length. -/// -/// For two-dimensional arrays, performs matrix multiplication. The array shapes -/// must be compatible in the following ways: -/// - If `self` is *M* × *N*, then `rhs` must be *N* × *K* for matrix-matrix multiplication -/// - If `self` is *M* × *N* and `rhs` is *N*, returns a vector of length *M* -/// - If `self` is *M* and `rhs` is *M* × *N*, returns a vector of length *N* -/// - If both arrays are one-dimensional of length *N*, returns a scalar -/// -/// **Panics** if: -/// - The arrays have dimensions other than 1 or 2 -/// - The array shapes are incompatible for the operation -/// - For vector dot product: the vectors have different lengths -impl Dot> for ArrayRef -where A: LinalgScalar -{ - type Output = Array; - - fn dot(&self, rhs: &ArrayRef) -> Self::Output - { - match (self.ndim(), rhs.ndim()) { - (1, 1) => { - let a = self.view().into_dimensionality::().unwrap(); - let b = rhs.view().into_dimensionality::().unwrap(); - let result = a.dot(&b); - ArrayD::from_elem(vec![], result) - } - (2, 2) => { - // Matrix-matrix multiplication - let a = self.view().into_dimensionality::().unwrap(); - let b = rhs.view().into_dimensionality::().unwrap(); - let result = a.dot(&b); - result.into_dimensionality::().unwrap() - } - (2, 1) => { - // Matrix-vector multiplication - let a = self.view().into_dimensionality::().unwrap(); - let b = rhs.view().into_dimensionality::().unwrap(); - let result = a.dot(&b); - result.into_dimensionality::().unwrap() - } - (1, 2) => { - // Vector-matrix multiplication - let a = self.view().into_dimensionality::().unwrap(); - let b = rhs.view().into_dimensionality::().unwrap(); - let result = a.dot(&b); - result.into_dimensionality::().unwrap() - } - _ => panic!("Dot product for ArrayD is only supported for 1D and 2D arrays"), - } - } -} diff --git a/src/zip/mod.rs b/src/zip/mod.rs index c481e481f..b01ae04ff 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -424,6 +424,7 @@ where D: Dimension } #[cfg(feature = "rayon")] + #[allow(dead_code)] pub(crate) fn uninitialized_for_current_layout(&self) -> Array, D> { let is_f = self.prefer_f(); diff --git a/tests/array.rs b/tests/array.rs index 512227bb6..391f88b95 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -4,6 +4,7 @@ )] use approx::assert_relative_eq; +use core::panic; use defmac::defmac; #[allow(deprecated)] use itertools::{zip, Itertools}; @@ -1005,7 +1006,7 @@ fn iter_size_hint() fn zero_axes() { let mut a = arr1::(&[]); - if let Some(_) = a.iter().next() { + if a.iter().next().is_some() { panic!(); } a.map(|_| panic!()); @@ -2080,7 +2081,7 @@ fn test_contiguous() assert!(c.as_slice_memory_order().is_some()); let v = c.slice(s![.., 0..1, ..]); assert!(!v.is_standard_layout()); - assert!(!v.as_slice_memory_order().is_some()); + assert!(v.as_slice_memory_order().is_none()); let v = c.slice(s![1..2, .., ..]); assert!(v.is_standard_layout()); diff --git a/tests/iterators.rs b/tests/iterators.rs index e8933a823..96b0673aa 100644 --- a/tests/iterators.rs +++ b/tests/iterators.rs @@ -195,7 +195,7 @@ fn inner_iter_corner_cases() assert_equal(a0.rows(), vec![aview1(&[0])]); let a2 = ArcArray::::zeros((0, 3)); - assert_equal(a2.rows(), vec![aview1(&[]); 0]); + assert_equal(a2.rows(), Vec::>::new()); let a2 = ArcArray::::zeros((3, 0)); assert_equal(a2.rows(), vec![aview1(&[]); 3]); @@ -359,11 +359,13 @@ fn axis_iter_zip_partially_consumed_discontiguous() } } +use ndarray::ArrayView1; + #[test] fn outer_iter_corner_cases() { let a2 = ArcArray::::zeros((0, 3)); - assert_equal(a2.outer_iter(), vec![aview1(&[]); 0]); + assert_equal(a2.outer_iter(), Vec::>::new()); let a2 = ArcArray::::zeros((3, 0)); assert_equal(a2.outer_iter(), vec![aview1(&[]); 3]); diff --git a/tests/numeric.rs b/tests/numeric.rs index 11a9fce76..b82a3561f 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -175,7 +175,7 @@ fn var_too_large_ddof() fn var_nan_ddof() { let a = Array2::::zeros((2, 3)); - let v = a.var(std::f64::NAN); + let v = a.var(f64::NAN); assert!(v.is_nan()); } diff --git a/tests/oper.rs b/tests/oper.rs index 4f68d27d3..a6d7054ba 100644 --- a/tests/oper.rs +++ b/tests/oper.rs @@ -19,20 +19,20 @@ fn test_oper(op: &str, a: &[f32], b: &[f32], c: &[f32]) let aa = CowArray::from(arr1(a)); let bb = CowArray::from(arr1(b)); let cc = CowArray::from(arr1(c)); - test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); + test_oper_arr(op, aa.clone(), bb.clone(), cc.clone()); let dim = (2, 2); let aa = aa.to_shape(dim).unwrap(); let bb = bb.to_shape(dim).unwrap(); let cc = cc.to_shape(dim).unwrap(); - test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); + test_oper_arr(op, aa.clone(), bb.clone(), cc.clone()); let dim = (1, 2, 1, 2); let aa = aa.to_shape(dim).unwrap(); let bb = bb.to_shape(dim).unwrap(); let cc = cc.to_shape(dim).unwrap(); - test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); + test_oper_arr(op, aa.clone(), bb.clone(), cc.clone()); } -fn test_oper_arr(op: &str, mut aa: CowArray, bb: CowArray, cc: CowArray) +fn test_oper_arr(op: &str, mut aa: CowArray, bb: CowArray, cc: CowArray) where D: Dimension { match op { From fd67f705e2cf3d8fa2c7c69e35687eaf84112c05 Mon Sep 17 00:00:00 2001 From: Kwonunn Date: Fri, 6 Feb 2026 00:31:03 +0100 Subject: [PATCH 53/54] Change `linspace` to take a range argument. (#1580) This is a breaking change, since it alters the arguments of `linspace` rather than adding a new function. Closes #1169 --- README-quick-start.md | 2 +- benches/bench1.rs | 2 +- benches/construct.rs | 4 +-- benches/higher-order.rs | 10 +++--- benches/iter.rs | 12 ++++---- benches/numeric.rs | 2 +- examples/sort-axis.rs | 2 +- src/doc/ndarray_for_numpy_users/mod.rs | 4 +-- src/finite_bounds.rs | 42 ++++++++++++++++++++++++++ src/impl_constructors.rs | 27 +++++++++-------- src/lib.rs | 2 ++ src/linspace.rs | 20 +++++++++--- src/logspace.rs | 30 ++++++++++++------ src/parallel/mod.rs | 4 +-- tests/par_azip.rs | 2 +- tests/par_rayon.rs | 4 +-- 16 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 src/finite_bounds.rs diff --git a/README-quick-start.md b/README-quick-start.md index ad13acc72..84e968744 100644 --- a/README-quick-start.md +++ b/README-quick-start.md @@ -91,7 +91,7 @@ fn main() { use ndarray::prelude::*; use ndarray::{Array, Ix3}; fn main() { - let a = Array::::linspace(0., 5., 11); + let a = Array::::linspace(0.0..=5.0, 11); println!("{:?}", a); } ``` diff --git a/benches/bench1.rs b/benches/bench1.rs index ea527cd35..3b5405329 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -984,7 +984,7 @@ const MEAN_SUM_N: usize = 127; fn range_mat(m: Ix, n: Ix) -> Array2 { assert!(m * n != 0); - Array::linspace(0., (m * n - 1) as f32, m * n) + Array::linspace(0.0..=(m * n - 1) as f32, m * n) .into_shape_with_order((m, n)) .unwrap() } diff --git a/benches/construct.rs b/benches/construct.rs index 71a4fb905..958eaa3b6 100644 --- a/benches/construct.rs +++ b/benches/construct.rs @@ -21,7 +21,7 @@ fn zeros_f64(bench: &mut Bencher) #[bench] fn map_regular(bench: &mut test::Bencher) { - let a = Array::linspace(0., 127., 128) + let a = Array::linspace(0.0..=127.0, 128) .into_shape_with_order((8, 16)) .unwrap(); bench.iter(|| a.map(|&x| 2. * x)); @@ -31,7 +31,7 @@ fn map_regular(bench: &mut test::Bencher) #[bench] fn map_stride(bench: &mut test::Bencher) { - let a = Array::linspace(0., 127., 256) + let a = Array::linspace(0.0..=127.0, 256) .into_shape_with_order((8, 32)) .unwrap(); let av = a.slice(s![.., ..;2]); diff --git a/benches/higher-order.rs b/benches/higher-order.rs index 5eb009566..6356687fb 100644 --- a/benches/higher-order.rs +++ b/benches/higher-order.rs @@ -14,7 +14,7 @@ const Y: usize = 16; #[bench] fn map_regular(bench: &mut Bencher) { - let a = Array::linspace(0., 127., N) + let a = Array::linspace(0.0..=127.0, N) .into_shape_with_order((X, Y)) .unwrap(); bench.iter(|| a.map(|&x| 2. * x)); @@ -29,7 +29,7 @@ pub fn double_array(mut a: ArrayViewMut2<'_, f64>) #[bench] fn map_stride_double_f64(bench: &mut Bencher) { - let mut a = Array::linspace(0., 127., N * 2) + let mut a = Array::linspace(0.0..=127.0, N * 2) .into_shape_with_order([X, Y * 2]) .unwrap(); let mut av = a.slice_mut(s![.., ..;2]); @@ -42,7 +42,7 @@ fn map_stride_double_f64(bench: &mut Bencher) #[bench] fn map_stride_f64(bench: &mut Bencher) { - let a = Array::linspace(0., 127., N * 2) + let a = Array::linspace(0.0..=127.0, N * 2) .into_shape_with_order([X, Y * 2]) .unwrap(); let av = a.slice(s![.., ..;2]); @@ -53,7 +53,7 @@ fn map_stride_f64(bench: &mut Bencher) #[bench] fn map_stride_u32(bench: &mut Bencher) { - let a = Array::linspace(0., 127., N * 2) + let a = Array::linspace(0.0..=127.0, N * 2) .into_shape_with_order([X, Y * 2]) .unwrap(); let b = a.mapv(|x| x as u32); @@ -65,7 +65,7 @@ fn map_stride_u32(bench: &mut Bencher) #[bench] fn fold_axis(bench: &mut Bencher) { - let a = Array::linspace(0., 127., N * 2) + let a = Array::linspace(0.0..=127.0, N * 2) .into_shape_with_order([X, Y * 2]) .unwrap(); bench.iter(|| a.fold_axis(Axis(0), 0., |&acc, &elt| acc + elt)); diff --git a/benches/iter.rs b/benches/iter.rs index bc483c8c2..0e18f1230 100644 --- a/benches/iter.rs +++ b/benches/iter.rs @@ -47,7 +47,7 @@ fn iter_sum_2d_transpose(bench: &mut Bencher) #[bench] fn iter_filter_sum_2d_u32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256) + let a = Array::linspace(0.0..=1.0, 256) .into_shape_with_order((16, 16)) .unwrap(); let b = a.mapv(|x| (x * 100.) as u32); @@ -58,7 +58,7 @@ fn iter_filter_sum_2d_u32(bench: &mut Bencher) #[bench] fn iter_filter_sum_2d_f32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256) + let a = Array::linspace(0.0..=1.0, 256) .into_shape_with_order((16, 16)) .unwrap(); let b = a * 100.; @@ -69,7 +69,7 @@ fn iter_filter_sum_2d_f32(bench: &mut Bencher) #[bench] fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256) + let a = Array::linspace(0.0..=1.0, 256) .into_shape_with_order((16, 16)) .unwrap(); let b = a.mapv(|x| (x * 100.) as u32); @@ -81,7 +81,7 @@ fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) #[bench] fn iter_filter_sum_2d_stride_f32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256) + let a = Array::linspace(0.0..=1.0, 256) .into_shape_with_order((16, 16)) .unwrap(); let b = a * 100.; @@ -93,7 +93,7 @@ fn iter_filter_sum_2d_stride_f32(bench: &mut Bencher) #[bench] fn iter_rev_step_by_contiguous(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 512); + let a = Array::linspace(0.0..=1.0, 512); bench.iter(|| { a.iter().rev().step_by(2).for_each(|x| { black_box(x); @@ -105,7 +105,7 @@ fn iter_rev_step_by_contiguous(bench: &mut Bencher) #[bench] fn iter_rev_step_by_discontiguous(bench: &mut Bencher) { - let mut a = Array::linspace(0., 1., 1024); + let mut a = Array::linspace(0.0..=1.0, 1024); a.slice_axis_inplace(Axis(0), Slice::new(0, None, 2)); bench.iter(|| { a.iter().rev().step_by(2).for_each(|x| { diff --git a/benches/numeric.rs b/benches/numeric.rs index ceb57fbd7..5dcde52d4 100644 --- a/benches/numeric.rs +++ b/benches/numeric.rs @@ -13,7 +13,7 @@ const Y: usize = 16; #[bench] fn clip(bench: &mut Bencher) { - let mut a = Array::linspace(0., 127., N * 2) + let mut a = Array::linspace(0.0..=127.0, N * 2) .into_shape_with_order([X, Y * 2]) .unwrap(); let min = 2.; diff --git a/examples/sort-axis.rs b/examples/sort-axis.rs index 4da3a64d5..112abfc77 100644 --- a/examples/sort-axis.rs +++ b/examples/sort-axis.rs @@ -169,7 +169,7 @@ where D: Dimension #[cfg(feature = "std")] fn main() { - let a = Array::linspace(0., 63., 64) + let a = Array::linspace(0.0..=63.0, 64) .into_shape_with_order((8, 8)) .unwrap(); let strings = a.map(|x| x.to_string()); diff --git a/src/doc/ndarray_for_numpy_users/mod.rs b/src/doc/ndarray_for_numpy_users/mod.rs index bb6b7ae83..a9400211d 100644 --- a/src/doc/ndarray_for_numpy_users/mod.rs +++ b/src/doc/ndarray_for_numpy_users/mod.rs @@ -195,8 +195,8 @@ //! ------|-----------|------ //! `np.array([[1.,2.,3.], [4.,5.,6.]])` | [`array![[1.,2.,3.], [4.,5.,6.]]`][array!] or [`arr2(&[[1.,2.,3.], [4.,5.,6.]])`][arr2()] | 2×3 floating-point array literal //! `np.arange(0., 10., 0.5)` or `np.r_[:10.:0.5]` | [`Array::range(0., 10., 0.5)`][::range()] | create a 1-D array with values `0.`, `0.5`, …, `9.5` -//! `np.linspace(0., 10., 11)` or `np.r_[:10.:11j]` | [`Array::linspace(0., 10., 11)`][::linspace()] | create a 1-D array with 11 elements with values `0.`, …, `10.` -//! `np.logspace(2.0, 3.0, num=4, base=10.0)` | [`Array::logspace(10.0, 2.0, 3.0, 4)`][::logspace()] | create a 1-D array with 4 elements with values `100.`, `215.4`, `464.1`, `1000.` +//! `np.linspace(0., 10., 11)` or `np.r_[:10.:11j]` | [`Array::linspace(0.0..=10.0, 11)`][::linspace()] | create a 1-D array with 11 elements with values `0.`, …, `10.` +//! `np.logspace(2.0, 3.0, num=4, base=10.0)` | [`Array::logspace(10.0, 2.0..=3.0, 4)`][::logspace()] | create a 1-D array with 4 elements with values `100.`, `215.4`, `464.1`, `1000.` //! `np.geomspace(1., 1000., num=4)` | [`Array::geomspace(1e0, 1e3, 4)`][::geomspace()] | create a 1-D array with 4 elements with values `1.`, `10.`, `100.`, `1000.` //! `np.ones((3, 4, 5))` | [`Array::ones((3, 4, 5))`][::ones()] | create a 3×4×5 array filled with ones (inferring the element type) //! `np.zeros((3, 4, 5))` | [`Array::zeros((3, 4, 5))`][::zeros()] | create a 3×4×5 array filled with zeros (inferring the element type) diff --git a/src/finite_bounds.rs b/src/finite_bounds.rs new file mode 100644 index 000000000..565fe2bcb --- /dev/null +++ b/src/finite_bounds.rs @@ -0,0 +1,42 @@ +use num_traits::Float; + +pub enum Bound +{ + Included(F), + Excluded(F), +} + +/// A version of std::ops::RangeBounds that only implements a..b and a..=b ranges. +pub trait FiniteBounds +{ + fn start_bound(&self) -> F; + fn end_bound(&self) -> Bound; +} + +impl FiniteBounds for std::ops::Range +where F: Float +{ + fn start_bound(&self) -> F + { + self.start + } + + fn end_bound(&self) -> Bound + { + Bound::Excluded(self.end) + } +} + +impl FiniteBounds for std::ops::RangeInclusive +where F: Float +{ + fn start_bound(&self) -> F + { + *self.start() + } + + fn end_bound(&self) -> Bound + { + Bound::Included(*self.end()) + } +} diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index ba01e2ca3..7f71cca5b 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -58,10 +58,7 @@ where S: DataOwned pub fn from_vec(v: Vec) -> Self { if mem::size_of::() == 0 { - assert!( - v.len() <= isize::MAX as usize, - "Length must fit in `isize`.", - ); + assert!(v.len() <= isize::MAX as usize, "Length must fit in `isize`.",); } unsafe { Self::from_shape_vec_unchecked(v.len() as Ix, v) } } @@ -95,14 +92,16 @@ where S: DataOwned /// ```rust /// use ndarray::{Array, arr1}; /// - /// let array = Array::linspace(0., 1., 5); + /// let array = Array::linspace(0.0..=1.0, 5); /// assert!(array == arr1(&[0.0, 0.25, 0.5, 0.75, 1.0])) /// ``` #[cfg(feature = "std")] - pub fn linspace(start: A, end: A, n: usize) -> Self - where A: Float + pub fn linspace(range: R, n: usize) -> Self + where + R: crate::finite_bounds::FiniteBounds, + A: Float, { - Self::from(to_vec(linspace::linspace(start, end, n))) + Self::from(to_vec(linspace::linspace(range, n))) } /// Create a one-dimensional array with elements from `start` to `end` @@ -137,18 +136,20 @@ where S: DataOwned /// use approx::assert_abs_diff_eq; /// use ndarray::{Array, arr1}; /// - /// let array = Array::logspace(10.0, 0.0, 3.0, 4); + /// let array = Array::logspace(10.0, 0.0..=3.0, 4); /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); /// - /// let array = Array::logspace(-10.0, 3.0, 0.0, 4); + /// let array = Array::logspace(-10.0, 3.0..=0.0, 4); /// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); /// # } /// ``` #[cfg(feature = "std")] - pub fn logspace(base: A, start: A, end: A, n: usize) -> Self - where A: Float + pub fn logspace(base: A, range: R, n: usize) -> Self + where + R: crate::finite_bounds::FiniteBounds, + A: Float, { - Self::from(to_vec(logspace::logspace(base, start, end, n))) + Self::from(to_vec(logspace::logspace(base, range, n))) } /// Create a one-dimensional array with `n` geometrically spaced elements diff --git a/src/lib.rs b/src/lib.rs index 91c2bb477..458ce8b2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,6 +206,8 @@ mod indexes; mod iterators; mod layout; mod linalg_traits; +#[cfg(feature = "std")] +mod finite_bounds; mod linspace; #[cfg(feature = "std")] pub use crate::linspace::{linspace, range, Linspace}; diff --git a/src/linspace.rs b/src/linspace.rs index 411c480db..ff52bf0c1 100644 --- a/src/linspace.rs +++ b/src/linspace.rs @@ -6,6 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. #![cfg(feature = "std")] + +use crate::finite_bounds::{Bound, FiniteBounds}; + use num_traits::Float; /// An iterator of a sequence of evenly spaced floats. @@ -71,17 +74,24 @@ impl ExactSizeIterator for Linspace where Linspace: Iterator {} /// The iterator element type is `F`, where `F` must implement [`Float`], e.g. /// [`f32`] or [`f64`]. /// -/// **Panics** if converting `n - 1` to type `F` fails. +/// **Panics** if converting `n` to type `F` fails. #[inline] -pub fn linspace(a: F, b: F, n: usize) -> Linspace -where F: Float +pub fn linspace(range: R, n: usize) -> Linspace +where + R: FiniteBounds, + F: Float, { - let step = if n > 1 { - let num_steps = F::from(n - 1).expect("Converting number of steps to `A` must not fail."); + let (a, b, num_steps) = match (range.start_bound(), range.end_bound()) { + (a, Bound::Included(b)) => (a, b, F::from(n - 1).expect("Converting number of steps to `A` must not fail.")), + (a, Bound::Excluded(b)) => (a, b, F::from(n).expect("Converting number of steps to `A` must not fail.")), + }; + + let step = if num_steps > F::zero() { (b - a) / num_steps } else { F::zero() }; + Linspace { start: a, step, diff --git a/src/logspace.rs b/src/logspace.rs index 463012018..dd1b7ae19 100644 --- a/src/logspace.rs +++ b/src/logspace.rs @@ -6,6 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. #![cfg(feature = "std")] + +use crate::finite_bounds::{Bound, FiniteBounds}; + use num_traits::Float; /// An iterator of a sequence of logarithmically spaced number. @@ -79,15 +82,22 @@ impl ExactSizeIterator for Logspace where Logspace: Iterator {} /// /// **Panics** if converting `n - 1` to type `F` fails. #[inline] -pub fn logspace(base: F, a: F, b: F, n: usize) -> Logspace -where F: Float +pub fn logspace(base: F, range: R, n: usize) -> Logspace +where + R: FiniteBounds, + F: Float, { - let step = if n > 1 { - let num_steps = F::from(n - 1).expect("Converting number of steps to `A` must not fail."); + let (a, b, num_steps) = match (range.start_bound(), range.end_bound()) { + (a, Bound::Included(b)) => (a, b, F::from(n - 1).expect("Converting number of steps to `A` must not fail.")), + (a, Bound::Excluded(b)) => (a, b, F::from(n).expect("Converting number of steps to `A` must not fail.")), + }; + + let step = if num_steps > F::zero() { (b - a) / num_steps } else { F::zero() }; + Logspace { sign: base.signum(), base: base.abs(), @@ -110,23 +120,23 @@ mod tests use crate::{arr1, Array1}; use approx::assert_abs_diff_eq; - let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); + let array: Array1<_> = logspace(10.0, 0.0..=3.0, 4).collect(); assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); - let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); + let array: Array1<_> = logspace(10.0, 3.0..=0.0, 4).collect(); assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0]), epsilon = 1e-12); - let array: Array1<_> = logspace(-10.0, 3.0, 0.0, 4).collect(); + let array: Array1<_> = logspace(-10.0, 3.0..=0.0, 4).collect(); assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12); - let array: Array1<_> = logspace(-10.0, 0.0, 3.0, 4).collect(); + let array: Array1<_> = logspace(-10.0, 0.0..=3.0, 4).collect(); assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3]), epsilon = 1e-12); } #[test] fn iter_forward() { - let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + let mut iter = logspace(10.0f64, 0.0..=3.0, 4); assert!(iter.size_hint() == (4, Some(4))); @@ -142,7 +152,7 @@ mod tests #[test] fn iter_backward() { - let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + let mut iter = logspace(10.0f64, 0.0..=3.0, 4); assert!(iter.size_hint() == (4, Some(4))); diff --git a/src/parallel/mod.rs b/src/parallel/mod.rs index 2eef69307..3ac0d4b04 100644 --- a/src/parallel/mod.rs +++ b/src/parallel/mod.rs @@ -65,7 +65,7 @@ //! use ndarray::Axis; //! use ndarray::parallel::prelude::*; //! -//! let a = Array::linspace(0., 63., 64).into_shape_with_order((4, 16)).unwrap(); +//! let a = Array::linspace(0.0..=63.0, 64).into_shape_with_order((4, 16)).unwrap(); //! let mut sums = Vec::new(); //! a.axis_iter(Axis(0)) //! .into_par_iter() @@ -84,7 +84,7 @@ //! use ndarray::Axis; //! use ndarray::parallel::prelude::*; //! -//! let a = Array::linspace(0., 63., 64).into_shape_with_order((4, 16)).unwrap(); +//! let a = Array::linspace(0.0..=63.0, 64).into_shape_with_order((4, 16)).unwrap(); //! let mut shapes = Vec::new(); //! a.axis_chunks_iter(Axis(0), 3) //! .into_par_iter() diff --git a/tests/par_azip.rs b/tests/par_azip.rs index 41011d495..7dd233e5e 100644 --- a/tests/par_azip.rs +++ b/tests/par_azip.rs @@ -41,7 +41,7 @@ fn test_par_azip3() *a += b / 10.; *c = a.sin(); }); - let res = Array::linspace(0., 3.1, 32).mapv_into(f32::sin); + let res = Array::linspace(0.0..=3.1, 32).mapv_into(f32::sin); assert_abs_diff_eq!(res, ArrayView::from(&c), epsilon = 1e-4); } diff --git a/tests/par_rayon.rs b/tests/par_rayon.rs index 13669763f..1b6b2b794 100644 --- a/tests/par_rayon.rs +++ b/tests/par_rayon.rs @@ -26,7 +26,7 @@ fn test_axis_iter() fn test_axis_iter_mut() { use approx::assert_abs_diff_eq; - let mut a = Array::linspace(0., 1.0f64, M * N) + let mut a = Array::linspace(0.0..=1.0f64, M * N) .into_shape_with_order((M, N)) .unwrap(); let b = a.mapv(|x| x.exp()); @@ -82,7 +82,7 @@ fn test_axis_chunks_iter() fn test_axis_chunks_iter_mut() { use approx::assert_abs_diff_eq; - let mut a = Array::linspace(0., 1.0f64, M * N) + let mut a = Array::linspace(0.0..=1.0f64, M * N) .into_shape_with_order((M, N)) .unwrap(); let b = a.mapv(|x| x.exp()); From 27af864668320e9092a20fa29dee753123798084 Mon Sep 17 00:00:00 2001 From: akern40 Date: Mon, 16 Feb 2026 09:20:44 -0500 Subject: [PATCH 54/54] perf(iteration): use move instead of clone for iteration (#1586) @scho-furiosa pointed out in #1584 that there was an unnecessary `clone` in `Baseiter::next` that was degrading performance for dynamic-dimensional arrays. This change switches to a move, netting a 74% speedup in iteration for dynamic-dimensional arrays. Tests (detailed in #1584) showed that there was a trade-off in performance between dynamic- and fixed-dimensional iteration. `ArrayD` performs best when mutating its index; `ArrayN` performs best when using a move. This is an indication that, in the future, `Baseiter::next` should dispatch to the "layout" / `Dimension` underlying type. Co-authored-by: Sungkeun Cho --- src/dimension/dimension_trait.rs | 3 +-- src/iterators/mod.rs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs index 3544a7f3c..373edb35e 100644 --- a/src/dimension/dimension_trait.rs +++ b/src/dimension/dimension_trait.rs @@ -197,9 +197,8 @@ pub trait Dimension: /// or None if there are no more. // FIXME: use &Self for index or even &mut? #[inline] - fn next_for(&self, index: Self) -> Option + fn next_for(&self, mut index: Self) -> Option { - let mut index = index; let mut done = false; for (&dim, ix) in zip(self.slice(), index.slice_mut()).rev() { *ix += 1; diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index f7892a8c9..abca3579d 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -72,10 +72,7 @@ impl Iterator for Baseiter #[inline] fn next(&mut self) -> Option { - let index = match self.index { - None => return None, - Some(ref ix) => ix.clone(), - }; + let index = self.index.take()?; let offset = D::stride_offset(&index, &self.strides); self.index = self.dim.next_for(index); unsafe { Some(self.ptr.offset(offset)) }