From 8e36dd673420d99bc5e741ba25ee9fa1ae365b9f Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 13 Apr 2025 16:19:28 +0900 Subject: [PATCH 1/7] draft for inplace reverse, permute --- src/impl_methods.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index ea9c9a0d5..a0816410c 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2548,6 +2548,72 @@ 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(); + } + + /// 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. + /// + /// # Examples + /// + /// ```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"); + } + + // Create temporary arrays for the new dimensions and strides + let mut new_dim = D::zeros(self.ndim()); + let mut new_strides = D::zeros(self.ndim()); + + { + let dim = self.layout.dim.slice(); + let strides = self.layout.strides.slice(); + for (new_axis, &axis) in axes.slice().iter().enumerate() { + new_dim[new_axis] = dim[axis]; + new_strides[new_axis] = strides[axis]; + } + } + + // Update the dimensions and strides in place + self.layout.dim.slice_mut().copy_from_slice(new_dim.slice()); + self.layout.strides.slice_mut().copy_from_slice(new_strides.slice()); + } } impl ArrayRef From 25f1ee6d26a124d6563a7e146db470f8e4754854 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 13 Apr 2025 16:30:23 +0900 Subject: [PATCH 2/7] add test cases --- tests/array.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) 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 ddfdfd0bca135122fa547be77da4d69a72cc7f2d Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 13 Apr 2025 16:38:44 +0900 Subject: [PATCH 3/7] formatter --- src/impl_methods.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index a0816410c..14f87541b 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2596,11 +2596,11 @@ where for count in usage_counts.slice() { assert_eq!(*count, 1, "each axis must be listed exactly once"); } - + // Create temporary arrays for the new dimensions and strides let mut new_dim = D::zeros(self.ndim()); let mut new_strides = D::zeros(self.ndim()); - + { let dim = self.layout.dim.slice(); let strides = self.layout.strides.slice(); @@ -2609,10 +2609,13 @@ where new_strides[new_axis] = strides[axis]; } } - + // Update the dimensions and strides in place self.layout.dim.slice_mut().copy_from_slice(new_dim.slice()); - self.layout.strides.slice_mut().copy_from_slice(new_strides.slice()); + self.layout + .strides + .slice_mut() + .copy_from_slice(new_strides.slice()); } } From 9903c4d46867f3f162c1eccbcd3770404fe909d2 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 20 Apr 2025 12:43:15 +0900 Subject: [PATCH 4/7] cycle detection logic with bitmask --- src/impl_methods.rs | 106 ++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 14f87541b..4b98ece58 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2538,27 +2538,6 @@ where unsafe { self.with_strides_dim(new_strides, new_dim) } } - /// Transpose the array by reversing axes. - /// - /// Transposition reverses the order of the axes (dimensions and strides) - /// 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 - } - - /// 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(); - } - /// Permute the axes in-place. /// /// This does not move any data, it just adjusts the array's dimensions @@ -2568,10 +2547,38 @@ where /// 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. - /// - /// # Examples + /// or if an axis is repeated more than once. + /// + /// # About the Cycle Detection + /// + /// 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 + /// + /// The check `(visited & (1 << axis)) != 0` works as follows: + /// ```no_run + /// let mut visited = 0; // 0b0000 + /// // Check axis 1 + /// if (visited & (1 << 1)) != 0 { // 0b0000 & 0b0010 = 0b0000 + /// // Not visited yet + /// } + /// // Mark axis 1 as visited + /// visited |= (1 << axis) | (1 << new_axis); /// // Check axis 1 again + /// if (visited & (1 << 1)) != 0 { // 0b0010 & 0b0010 = 0b0010 + /// // Already visited! + /// } + /// ``` /// + /// # Example /// ```rust /// use ndarray::{arr2, Array3}; /// @@ -2597,25 +2604,46 @@ where assert_eq!(*count, 1, "each axis must be listed exactly once"); } - // Create temporary arrays for the new dimensions and strides - let mut new_dim = D::zeros(self.ndim()); - let mut new_strides = D::zeros(self.ndim()); + let dim = self.layout.dim.slice_mut(); + let strides = self.layout.strides.slice_mut(); + let axes = axes.slice(); - { - let dim = self.layout.dim.slice(); - let strides = self.layout.strides.slice(); - for (new_axis, &axis) in axes.slice().iter().enumerate() { - new_dim[new_axis] = dim[axis]; - new_strides[new_axis] = strides[axis]; + let mut visited = 0usize; + for (new_axis, &axis) in axes.iter().enumerate() { + if (visited & (1 << axis)) != 0 { + continue; } + + let temp = dim[axis]; + dim[axis] = dim[new_axis]; + dim[new_axis] = temp; + + let temp = strides[axis]; + strides[axis] = strides[new_axis]; + strides[new_axis] = temp; + visited |= (1 << axis) | (1 << new_axis); } + } + + /// Transpose the array by reversing axes. + /// + /// Transposition reverses the order of the axes (dimensions and strides) + /// 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 + } - // Update the dimensions and strides in place - self.layout.dim.slice_mut().copy_from_slice(new_dim.slice()); - self.layout - .strides - .slice_mut() - .copy_from_slice(new_strides.slice()); + /// 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(); } } From faf0fc0e1dec06fd7a727e81fe2523bd548cc073 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 20 Apr 2025 13:24:53 +0900 Subject: [PATCH 5/7] formatter --- 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 4b98ece58..2d301a172 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2558,7 +2558,7 @@ where /// 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 From 12878c643871d5edbcbf4b88cb9b5c0ca77987d3 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sun, 20 Apr 2025 14:12:01 +0900 Subject: [PATCH 6/7] satisfying CI(cargo doc, etc.) --- src/impl_methods.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 2d301a172..1c02d03d7 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2553,11 +2553,11 @@ where /// /// 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) + /// 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) @@ -2567,13 +2567,15 @@ where /// The check `(visited & (1 << axis)) != 0` works as follows: /// ```no_run /// let mut visited = 0; // 0b0000 + /// let axis = 1; + /// let new_axis = 0; /// // Check axis 1 - /// if (visited & (1 << 1)) != 0 { // 0b0000 & 0b0010 = 0b0000 + /// if (visited & (1 << axis)) != 0 { // 0b0000 & 0b0010 = 0b0000 /// // Not visited yet /// } /// // Mark axis 1 as visited - /// visited |= (1 << axis) | (1 << new_axis); /// // Check axis 1 again - /// if (visited & (1 << 1)) != 0 { // 0b0010 & 0b0010 = 0b0010 + /// visited |= (1 << axis) | (1 << new_axis); // 0b0000 | 0b0010 | 0b0001 = 0b0011 + /// if (visited & (1 << 1)) != 0 { // 0b0011 & 0b0010 = 0b0010 /// // Already visited! /// } /// ``` @@ -2614,13 +2616,9 @@ where continue; } - let temp = dim[axis]; - dim[axis] = dim[new_axis]; - dim[new_axis] = temp; + dim.swap(axis, new_axis); + strides.swap(axis, new_axis); - let temp = strides[axis]; - strides[axis] = strides[new_axis]; - strides[new_axis] = temp; visited |= (1 << axis) | (1 << new_axis); } } From 3c9415549fc42fb6c01b17f883943d737464e2e2 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Thu, 24 Apr 2025 20:45:46 +0900 Subject: [PATCH 7/7] add comments from doc, to describe how the locig works --- src/impl_methods.rs | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 1c02d03d7..0fd3a5a71 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -2549,37 +2549,6 @@ where /// **Panics** if any of the axes are out of bounds, if an axis is missing, /// or if an axis is repeated more than once. /// - /// # About the Cycle Detection - /// - /// 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 - /// - /// The check `(visited & (1 << axis)) != 0` works as follows: - /// ```no_run - /// let mut visited = 0; // 0b0000 - /// let axis = 1; - /// let new_axis = 0; - /// // Check axis 1 - /// if (visited & (1 << axis)) != 0 { // 0b0000 & 0b0010 = 0b0000 - /// // Not visited yet - /// } - /// // Mark axis 1 as visited - /// visited |= (1 << axis) | (1 << new_axis); // 0b0000 | 0b0010 | 0b0001 = 0b0011 - /// if (visited & (1 << 1)) != 0 { // 0b0011 & 0b0010 = 0b0010 - /// // Already visited! - /// } - /// ``` - /// /// # Example /// ```rust /// use ndarray::{arr2, Array3}; @@ -2610,6 +2579,18 @@ where 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 {