ndshape/
lib.rs

1//! Simple, fast linearization of 2D, 3D, and 4D coordinates.
2//!
3//! The canonical choice of linearization function is row-major, i.e. stepping linearly through an N dimensional array would
4//! step by X first, then Y, then Z, etc, assuming that `[T; N]` coordinates are provided as `[X, Y, Z, ...]`. More explicitly:
5//!
6//! ```text
7//! linearize([x, y, z, ...]) = x + X_SIZE * y + X_SIZE * Y_SIZE * z + ...
8//! ```
9//!
10//! To achieve a different layout, one only needs to choose a different permutation of coordinates. For example, column-major
11//! layout would require coordinates specified as `[..., Z, Y, X]`. For a 3D layout where each Y level set is contiguous in
12//! memory, either layout `[X, Z, Y]` or `[Z, X, Y]` would work.
13//!
14//! # Example: Indexing Multidimensional Arrays
15//!
16//! ```
17//! use ndshape::{Shape, ConstShape3u32, ConstShape4u32, ConstPow2Shape3u32, RuntimeShape};
18//!
19//! // An arbitrary shape.
20//! let shape = ConstShape3u32::<5, 6, 7>;
21//! let index = shape.linearize([1, 2, 3]);
22//! assert_eq!(index, 101);
23//! assert_eq!(shape.delinearize(index), [1, 2, 3]);
24//!
25//! // A shape with power-of-two dimensions
26//! // This allows us to use bit shifting and masking for linearization.
27//! let shape = ConstPow2Shape3u32::<1, 2, 3>; // These are number of bits per dimension.
28//! let index = shape.linearize([1, 2, 3]);
29//! assert_eq!(index, 0b011_10_1);
30//! assert_eq!(shape.delinearize(index), [1, 2, 3]);
31//!
32//! // A runtime shape.
33//! let shape = RuntimeShape::<u32, 3>::new([5, 6, 7]);
34//! let index = shape.linearize([1, 2, 3]);
35//! assert_eq!(index, 101);
36//! assert_eq!(shape.delinearize(index), [1, 2, 3]);
37//!
38//! // Use a shape for indexing an array in 4D.
39//! // Step X, then Y, then Z, since that results in monotonic increasing indices.
40//! // (Believe it or not, Rust's N-dimensional array (e.g. `[[T; N]; M]`)
41//! // indexing is significantly slower than this).
42//! let shape = ConstShape4u32::<5, 6, 7, 8>;
43//! let data = [0; 5 * 6 * 7 * 8];
44//! for w in 0..8 {
45//!     for z in 0..7 {
46//!         for y in 0..6 {
47//!             for x in 0..5 {
48//!                 let i = shape.linearize([x, y, z, w]);
49//!                 assert_eq!(0, data[i as usize]);
50//!             }
51//!         }
52//!     }
53//! }
54//! ```
55//!
56//! # Example: Negative Strides with Modular Arithmetic
57//!
58//! It is often beneficial to linearize a negative vector that results in a negative linear "stride." But when using unsigned
59//! linear indices, a negative stride would require a modular arithmetic representation, where e.g. `-1` maps to `u32::MAX`.
60//! This works fine with any [`Shape`](crate::Shape). You just need to be sure to use modular arithmetic with the resulting
61//! linear strides, e.g. [`u32::wrapping_add`](u32::wrapping_add) and [`u32::wrapping_mul`](u32::wrapping_mul). Also, it is not
62//! possible to delinearize a negative stride with modular arithmetic. For that, you must use signed integer coordinates.
63//!
64//! ```
65//! use ndshape::{Shape, ConstShape3u32, ConstShape3i32};
66//!
67//! let shape = ConstShape3u32::<10, 10, 10>;
68//! let stride = shape.linearize([0, -1i32 as u32, 0]);
69//! assert_eq!(stride, -10i32 as u32);
70//!
71//! // Delinearize does not work with unsigned coordinates!
72//! assert_ne!(shape.delinearize(stride), [0, -1i32 as u32, 0]);
73//! assert_eq!(shape.delinearize(stride), [6, 8, 42949672]);
74//!
75//! let shape = ConstShape3i32::<10, 10, 10>;
76//! let stride = shape.linearize([0, -1, 0]);
77//! assert_eq!(stride, -10);
78//!
79//! // Delinearize works with signed coordinates.
80//! assert_eq!(shape.delinearize(stride), [0, -1, 0]);
81//! ```
82
83mod const_shape;
84mod runtime_shape;
85
86pub use const_shape::*;
87pub use runtime_shape::*;
88
89/// The shape of an array with unspecified dimensionality.
90pub trait AbstractShape<Coord, Vector> {
91    /// The number of elements in an array with this shape.
92    fn size(&self) -> Coord;
93    /// Translates a vector `V` (with an unspecified number of dimensions) into a single number `T` that can be used for
94    /// linear indexing.
95    fn linearize(&self, p: Vector) -> Coord;
96    /// The inverse of `linearize`.
97    fn delinearize(&self, i: Coord) -> Vector;
98}
99
100/// The shape of an `N`-dimensional array.
101pub trait Shape<const N: usize> {
102    type Coord;
103
104    /// The number of elements in an array with this shape.
105    fn size(&self) -> Self::Coord;
106    /// The same as `self.size() as usize`.
107    fn usize(&self) -> usize;
108    /// The dimensions of the shape.
109    fn as_array(&self) -> [Self::Coord; N];
110    /// Translate an `N`-dimensional vector into a single number `T` that can be used for linear indexing.
111    fn linearize(&self, p: [Self::Coord; N]) -> Self::Coord;
112    /// The inverse of `linearize`.
113    fn delinearize(&self, i: Self::Coord) -> [Self::Coord; N];
114}
115
116/// A constant shape of an `N`-dimensional array.
117pub trait ConstShape<const N: usize> {
118    type Coord;
119
120    /// The number of elements in an array with this shape.
121    const SIZE: Self::Coord;
122    /// Same as `Self::SIZE as usize`.
123    const USIZE: usize;
124    /// The dimensions of the shape.
125    const ARRAY: [Self::Coord; N];
126    /// Translate an `N`-dimensional vector into a single number `T` that can be used for linear indexing.
127    fn linearize(p: [Self::Coord; N]) -> Self::Coord;
128    /// The inverse of `linearize`.
129    fn delinearize(i: Self::Coord) -> [Self::Coord; N];
130}
131
132impl<S, const N: usize> AbstractShape<S::Coord, [S::Coord; N]> for S
133where
134    S: Shape<N>,
135{
136    #[inline]
137    fn size(&self) -> S::Coord {
138        self.size()
139    }
140    #[inline]
141    fn linearize(&self, p: [S::Coord; N]) -> S::Coord {
142        self.linearize(p)
143    }
144    #[inline]
145    fn delinearize(&self, i: S::Coord) -> [S::Coord; N] {
146        self.delinearize(i)
147    }
148}
149
150impl<S, const N: usize> Shape<N> for S
151where
152    S: ConstShape<N>,
153{
154    type Coord = S::Coord;
155
156    #[inline]
157    fn size(&self) -> Self::Coord {
158        S::SIZE
159    }
160    #[inline]
161    fn usize(&self) -> usize {
162        S::USIZE
163    }
164    #[inline]
165    fn as_array(&self) -> [Self::Coord; N] {
166        S::ARRAY
167    }
168    #[inline]
169    fn linearize(&self, p: [Self::Coord; N]) -> Self::Coord {
170        S::linearize(p)
171    }
172    #[inline]
173    fn delinearize(&self, i: Self::Coord) -> [Self::Coord; N] {
174        S::delinearize(i)
175    }
176}