use crate::{
ContainerType,
dense::{array::Array, layout::convert_1d_nd_from_shape},
traits::{
accessors::{
UnsafeRandom1DAccessByRef, UnsafeRandom1DAccessByValue, UnsafeRandom1DAccessMut,
UnsafeRandomAccessByRef, UnsafeRandomAccessByValue, UnsafeRandomAccessMut,
},
base_operations::{BaseItem, Shape},
},
};
pub struct ArrayTranspose<ArrayImpl, const NDIM: usize> {
arr: Array<ArrayImpl, NDIM>,
permutation: [usize; NDIM],
shape: [usize; NDIM],
}
impl<ArrayImpl, const NDIM: usize> ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: Shape<NDIM>,
{
pub fn new(arr: Array<ArrayImpl, NDIM>, permutation: [usize; NDIM]) -> Self {
let mut seen = [false; NDIM];
let mut shape = [0; NDIM];
let source_shape = arr.shape();
for (output_axis, &source_axis) in permutation.iter().enumerate() {
assert!(source_axis < NDIM, "axis permutation entry out of range");
assert!(
!seen[source_axis],
"axis permutation contains repeated axes"
);
seen[source_axis] = true;
shape[output_axis] = source_shape[source_axis];
}
Self {
arr,
permutation,
shape,
}
}
}
impl<ArrayImpl, const NDIM: usize> ContainerType for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: ContainerType,
{
type Type = ArrayImpl::Type;
}
impl<Item: Copy + Default, ArrayImpl, const NDIM: usize> BaseItem
for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: BaseItem<Item = Item>,
{
type Item = Item;
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandomAccessByValue<NDIM>
for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessByValue<NDIM>,
{
#[inline(always)]
unsafe fn get_value_unchecked(&self, multi_index: [usize; NDIM]) -> Self::Item {
unsafe {
self.arr
.get_value_unchecked(inverse_permute_multi_index(multi_index, self.permutation))
}
}
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandomAccessByRef<NDIM> for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessByRef<NDIM>,
{
#[inline(always)]
unsafe fn get_unchecked(&self, multi_index: [usize; NDIM]) -> &Self::Item {
unsafe {
self.arr
.get_unchecked(inverse_permute_multi_index(multi_index, self.permutation))
}
}
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandomAccessMut<NDIM> for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessMut<NDIM>,
{
#[inline(always)]
unsafe fn get_unchecked_mut(&mut self, multi_index: [usize; NDIM]) -> &mut Self::Item {
unsafe {
self.arr
.get_unchecked_mut(inverse_permute_multi_index(multi_index, self.permutation))
}
}
}
impl<ArrayImpl: Shape<NDIM>, const NDIM: usize> Shape<NDIM> for ArrayTranspose<ArrayImpl, NDIM> {
#[inline(always)]
fn shape(&self) -> [usize; NDIM] {
self.shape
}
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandom1DAccessByValue for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessByValue<NDIM> + Shape<NDIM>,
{
#[inline(always)]
unsafe fn get_value_1d_unchecked(&self, index: usize) -> Self::Item {
unsafe { self.get_value_unchecked(convert_1d_nd_from_shape(index, self.shape())) }
}
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandom1DAccessByRef for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessByRef<NDIM> + Shape<NDIM>,
{
#[inline(always)]
unsafe fn get_1d_unchecked(&self, index: usize) -> &Self::Item {
unsafe { self.get_unchecked(convert_1d_nd_from_shape(index, self.shape())) }
}
}
impl<ArrayImpl, const NDIM: usize> UnsafeRandom1DAccessMut for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: UnsafeRandomAccessMut<NDIM> + Shape<NDIM>,
{
#[inline(always)]
unsafe fn get_1d_unchecked_mut(&mut self, index: usize) -> &mut Self::Item {
unsafe { self.get_unchecked_mut(convert_1d_nd_from_shape(index, self.shape())) }
}
}
fn inverse_permute_multi_index<const NDIM: usize>(
multi_index: [usize; NDIM],
permutation: [usize; NDIM],
) -> [usize; NDIM] {
let mut result = [0; NDIM];
for (index, &p) in permutation.iter().enumerate() {
result[p] = multi_index[index];
}
result
}
unsafe impl<ArrayImpl, const NDIM: usize> crate::UnsafeDisjointRandom1DAccessMut
for ArrayTranspose<ArrayImpl, NDIM>
where
ArrayImpl: crate::UnsafeDisjointRandom1DAccessMut + UnsafeRandomAccessMut<NDIM> + Shape<NDIM>,
{
}
#[cfg(test)]
mod tests {
use super::ArrayTranspose;
use crate::DynArray;
#[test]
#[should_panic(expected = "axis permutation contains repeated axes")]
fn constructor_rejects_repeated_axes() {
let array = DynArray::<f64, 2>::from_shape([2, 3]);
let _ = ArrayTranspose::new(array, [0, 0]);
}
#[test]
#[should_panic(expected = "axis permutation contains repeated axes")]
fn permute_axes_rejects_repeated_axes() {
let _ = DynArray::<f64, 2>::from_shape([2, 3]).permute_axes([1, 1]);
}
#[test]
#[should_panic(expected = "axis permutation contains repeated axes")]
fn empty_array_rejects_repeated_axes() {
let _ = DynArray::<f64, 2>::from_shape([0, 3]).permute_axes([0, 0]);
}
#[test]
#[should_panic(expected = "axis permutation entry out of range")]
fn constructor_rejects_out_of_range_axis() {
let array = DynArray::<f64, 2>::from_shape([2, 3]);
let _ = ArrayTranspose::new(array, [0, 2]);
}
#[test]
fn cyclic_permutation_maps_shape_and_elements() {
let mut array = DynArray::<f64, 3>::from_shape([2, 3, 4]);
for i in 0..2 {
for j in 0..3 {
for k in 0..4 {
array[[i, j, k]] = (100 * i + 10 * j + k) as f64;
}
}
}
let view = array.permute_axes([2, 0, 1]);
assert_eq!(view.shape(), [4, 2, 3]);
for i in 0..2 {
for j in 0..3 {
for k in 0..4 {
assert_eq!(view[[k, i, j]], (100 * i + 10 * j + k) as f64);
}
}
}
}
#[test]
fn mutable_iteration_yields_disjoint_elements() {
let mut array = DynArray::<f64, 3>::from_shape([2, 3, 4]);
{
let mut view = array.r_mut().permute_axes([2, 0, 1]);
let elements: Vec<_> = view.iter_mut().collect();
for (index, element) in elements.into_iter().enumerate() {
*element = index as f64;
}
}
for i in 0..2 {
for j in 0..3 {
for k in 0..4 {
assert_eq!(array[[i, j, k]], (k + 4 * i + 8 * j) as f64);
}
}
}
}
#[test]
fn identity_and_empty_permutations_are_valid() {
let view = DynArray::<f64, 3>::from_shape([2, 3, 4]).permute_axes([0, 1, 2]);
assert_eq!(view.shape(), [2, 3, 4]);
let empty = DynArray::<f64, 2>::from_shape([0, 3]).permute_axes([1, 0]);
assert_eq!(empty.shape(), [3, 0]);
assert_eq!(empty.iter_value().count(), 0);
let vector = DynArray::<f64, 1>::from_shape([3]).permute_axes([0]);
assert_eq!(vector.shape(), [3]);
}
}