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}