Skip to main content

binius_utils/
strided_array.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use core::slice;
4use std::ops::{Index, IndexMut, Range};
5
6use crate::rayon::prelude::*;
7
8#[derive(Debug, thiserror::Error)]
9pub enum Error {
10	#[error("dimensions do not match data size")]
11	DimensionMismatch,
12}
13
14/// A mutable view of an 2D array in row-major order that allows for parallel processing of
15/// vertical slices.
16#[derive(Debug)]
17pub struct StridedArray2DViewMut<'a, T> {
18	data: &'a mut [T],
19	data_width: usize,
20	height: usize,
21	cols: Range<usize>,
22}
23
24impl<'a, T> StridedArray2DViewMut<'a, T> {
25	/// Create a single-piece view of the data.
26	pub const fn without_stride(
27		data: &'a mut [T],
28		height: usize,
29		width: usize,
30	) -> Result<Self, Error> {
31		if width * height != data.len() {
32			return Err(Error::DimensionMismatch);
33		}
34		Ok(Self {
35			data,
36			data_width: width,
37			height,
38			cols: 0..width,
39		})
40	}
41
42	/// Returns a reference to the data at the given indices without bounds checking.
43	/// # Safety
44	/// The caller must ensure that `i < self.height` and `j < self.width()`.
45	pub unsafe fn get_unchecked_ref(&self, i: usize, j: usize) -> &T {
46		debug_assert!(i < self.height);
47		debug_assert!(j < self.width());
48		unsafe {
49			self.data
50				.get_unchecked(i * self.data_width + j + self.cols.start)
51		}
52	}
53
54	/// Returns a mutable reference to the data at the given indices without bounds checking.
55	/// # Safety
56	/// The caller must ensure that `i < self.height` and `j < self.width()`.
57	pub unsafe fn get_unchecked_mut(&mut self, i: usize, j: usize) -> &mut T {
58		debug_assert!(i < self.height);
59		debug_assert!(j < self.width());
60		unsafe {
61			self.data
62				.get_unchecked_mut(i * self.data_width + j + self.cols.start)
63		}
64	}
65
66	pub const fn height(&self) -> usize {
67		self.height
68	}
69
70	pub const fn width(&self) -> usize {
71		self.cols.end - self.cols.start
72	}
73
74	/// Iterate over the mutable references to the elements in the specified column.
75	pub fn iter_column_mut(&mut self, col: usize) -> impl Iterator<Item = &mut T> + '_ {
76		assert!(col < self.width());
77		let start = col + self.cols.start;
78		let data_ptr = self.data.as_mut_ptr();
79		(0..self.height).map(move |i|
80				// Safety:
81				// - `data_ptr` points to the start of the data slice.
82				// - `col` is within bounds of the width.
83				// - different iterator values do not overlap.
84				unsafe { &mut *data_ptr.add(i * self.data_width + start) })
85	}
86
87	/// Returns iterator over vertical slices of the data for the given stride.
88	pub fn into_strides(self, stride: usize) -> impl Iterator<Item = Self> + 'a {
89		let Self {
90			data,
91			data_width,
92			height,
93			cols,
94		} = self;
95
96		cols.clone().step_by(stride).map(move |start| {
97			let end = (start + stride).min(cols.end);
98			Self {
99				// Safety: different instances of StridedArray2DViewMut created with the same data
100				// slice do not access overlapping indices.
101				data: unsafe { slice::from_raw_parts_mut(data.as_mut_ptr(), data.len()) },
102				data_width,
103				height,
104				cols: start..end,
105			}
106		})
107	}
108
109	/// Returns parallel iterator over vertical slices of the data for the given stride.
110	pub fn into_par_strides(self, stride: usize) -> impl IndexedParallelIterator<Item = Self> + 'a
111	where
112		T: Send + Sync,
113	{
114		self.cols
115			.clone()
116			.into_par_iter()
117			.step_by(stride)
118			.map(move |start| {
119				let end = (start + stride).min(self.cols.end);
120				// We are setting the same lifetime as `self` captures.
121				Self {
122					// Safety: different instances of StridedArray2DViewMut created with the same
123					// data slice do not access overlapping indices.
124					data: unsafe {
125						slice::from_raw_parts_mut(self.data.as_ptr() as *mut T, self.data.len())
126					},
127					data_width: self.data_width,
128					height: self.height,
129					cols: start..end,
130				}
131			})
132	}
133
134	/// Returns iterator over single-column mutable views of the data.
135	pub fn iter_cols(&mut self) -> impl Iterator<Item = StridedArray2DColMut<'_, T>> + '_ {
136		let data_ptr = self.data.as_mut_ptr();
137		let data_len = self.data.len();
138		self.cols.clone().map(move |col| StridedArray2DColMut {
139			// Safety: different instances of StridedArray2DColMut created with the same data
140			// slice do not access overlapping indices since each accesses a different column.
141			data: unsafe { slice::from_raw_parts_mut(data_ptr, data_len) },
142			data_width: self.data_width,
143			height: self.height,
144			col,
145		})
146	}
147
148	/// Returns parallel iterator over single-column mutable views of the data.
149	pub fn par_iter_cols(
150		&mut self,
151	) -> impl IndexedParallelIterator<Item = StridedArray2DColMut<'_, T>> + '_
152	where
153		T: Send + Sync,
154	{
155		let data_ptr = SendPtr(self.data.as_mut_ptr());
156		let data_len = self.data.len();
157		let data_width = self.data_width;
158		let height = self.height;
159		self.cols.clone().into_par_iter().map(move |col| {
160			StridedArray2DColMut {
161				// Safety: different instances of StridedArray2DColMut created with the same data
162				// slice do not access overlapping indices since each accesses a different column.
163				data: unsafe { slice::from_raw_parts_mut(data_ptr.as_ptr(), data_len) },
164				data_width,
165				height,
166				col,
167			}
168		})
169	}
170}
171
172/// A mutable view of a single column (vertical slice) of a 2D array in row-major order.
173#[derive(Debug)]
174pub struct StridedArray2DColMut<'a, T> {
175	data: &'a mut [T],
176	data_width: usize,
177	height: usize,
178	col: usize,
179}
180
181impl<'a, T> StridedArray2DColMut<'a, T> {
182	pub const fn height(&self) -> usize {
183		self.height
184	}
185
186	/// Returns a reference to the data at the given row index without bounds checking.
187	/// # Safety
188	/// The caller must ensure that `i < self.height`.
189	pub unsafe fn get_unchecked_ref(&self, i: usize) -> &T {
190		debug_assert!(i < self.height);
191		unsafe { self.data.get_unchecked(i * self.data_width + self.col) }
192	}
193
194	/// Returns a mutable reference to the data at the given row index without bounds checking.
195	/// # Safety
196	/// The caller must ensure that `i < self.height`.
197	pub unsafe fn get_unchecked_mut(&mut self, i: usize) -> &mut T {
198		debug_assert!(i < self.height);
199		unsafe { self.data.get_unchecked_mut(i * self.data_width + self.col) }
200	}
201}
202
203impl<T> Index<usize> for StridedArray2DColMut<'_, T> {
204	type Output = T;
205
206	fn index(&self, i: usize) -> &T {
207		assert!(i < self.height());
208		unsafe { self.get_unchecked_ref(i) }
209	}
210}
211
212impl<T> IndexMut<usize> for StridedArray2DColMut<'_, T> {
213	fn index_mut(&mut self, i: usize) -> &mut Self::Output {
214		assert!(i < self.height());
215		unsafe { self.get_unchecked_mut(i) }
216	}
217}
218
219impl<T> Index<(usize, usize)> for StridedArray2DViewMut<'_, T> {
220	type Output = T;
221
222	fn index(&self, (i, j): (usize, usize)) -> &T {
223		assert!(i < self.height());
224		assert!(j < self.width());
225		unsafe { self.get_unchecked_ref(i, j) }
226	}
227}
228
229impl<T> IndexMut<(usize, usize)> for StridedArray2DViewMut<'_, T> {
230	fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut Self::Output {
231		assert!(i < self.height());
232		assert!(j < self.width());
233		unsafe { self.get_unchecked_mut(i, j) }
234	}
235}
236
237/// A wrapper around a raw pointer that implements Send and Sync.
238///
239/// # Safety
240/// The caller must ensure that the pointer is valid and that concurrent access
241/// through multiple `SendPtr` instances does not cause data races.
242struct SendPtr<T>(*mut T);
243
244impl<T> SendPtr<T> {
245	const fn as_ptr(self) -> *mut T {
246		self.0
247	}
248}
249
250impl<T> Clone for SendPtr<T> {
251	fn clone(&self) -> Self {
252		*self
253	}
254}
255
256impl<T> Copy for SendPtr<T> {}
257
258// Safety: SendPtr is only used internally where we ensure non-overlapping access
259unsafe impl<T: Send> Send for SendPtr<T> {}
260unsafe impl<T: Sync> Sync for SendPtr<T> {}
261
262#[cfg(test)]
263mod tests {
264	use std::array;
265
266	use super::*;
267
268	#[test]
269	fn test_indexing() {
270		let mut data = array::from_fn::<_, 12, _>(|i| i);
271		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
272		assert_eq!(arr[(3, 1)], 10);
273		arr[(2, 2)] = 88;
274		assert_eq!(data[8], 88);
275	}
276
277	#[test]
278	fn test_strides() {
279		let mut data = array::from_fn::<_, 12, _>(|i| i);
280		let arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
281
282		{
283			let mut strides = arr.into_strides(2);
284			let mut stride0 = strides.next().unwrap();
285			let mut stride1 = strides.next().unwrap();
286			assert!(strides.next().is_none());
287
288			assert_eq!(stride0.width(), 2);
289			assert_eq!(stride1.width(), 1);
290
291			stride0[(0, 0)] = 88;
292			stride1[(1, 0)] = 99;
293		}
294
295		assert_eq!(data[0], 88);
296		assert_eq!(data[5], 99);
297	}
298
299	#[test]
300	fn test_parallel_strides() {
301		let mut data = array::from_fn::<_, 12, _>(|i| i);
302		let arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
303
304		{
305			let mut strides: Vec<_> = arr.into_par_strides(2).collect();
306			assert_eq!(strides.len(), 2);
307			assert_eq!(strides[0].width(), 2);
308			assert_eq!(strides[1].width(), 1);
309
310			strides[0][(0, 0)] = 88;
311			strides[1][(1, 0)] = 99;
312		}
313
314		assert_eq!(data[0], 88);
315		assert_eq!(data[5], 99);
316	}
317
318	#[test]
319	fn test_iter_column_mut() {
320		let mut data = array::from_fn::<_, 12, _>(|i| i);
321		let data_clone = data;
322		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
323
324		let mut col_iter = arr.iter_column_mut(1);
325		assert_eq!(col_iter.next().copied(), Some(data_clone[1]));
326		assert_eq!(col_iter.next().copied(), Some(data_clone[4]));
327		assert_eq!(col_iter.next().copied(), Some(data_clone[7]));
328		assert_eq!(col_iter.next().copied(), Some(data_clone[10]));
329		assert_eq!(col_iter.next(), None);
330	}
331
332	#[test]
333	fn test_col_mut_indexing() {
334		let mut data = array::from_fn::<_, 12, _>(|i| i);
335		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
336
337		let mut cols: Vec<_> = arr.iter_cols().collect();
338		assert_eq!(cols.len(), 3);
339
340		// Test reading - column 1 contains elements at indices 1, 4, 7, 10
341		assert_eq!(cols[1][0], 1);
342		assert_eq!(cols[1][1], 4);
343		assert_eq!(cols[1][2], 7);
344		assert_eq!(cols[1][3], 10);
345
346		// Test writing
347		cols[0][2] = 88;
348		cols[2][1] = 99;
349
350		assert_eq!(data[6], 88); // row 2, col 0
351		assert_eq!(data[5], 99); // row 1, col 2
352	}
353
354	#[test]
355	fn test_iter_cols() {
356		let mut data = array::from_fn::<_, 12, _>(|i| i);
357		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
358
359		{
360			let mut cols = arr.iter_cols();
361			let mut col0 = cols.next().unwrap();
362			let mut col1 = cols.next().unwrap();
363			let mut col2 = cols.next().unwrap();
364			assert!(cols.next().is_none());
365
366			assert_eq!(col0.height(), 4);
367			assert_eq!(col1.height(), 4);
368			assert_eq!(col2.height(), 4);
369
370			col0[0] = 88;
371			col1[1] = 99;
372			col2[3] = 77;
373		}
374
375		assert_eq!(data[0], 88); // row 0, col 0
376		assert_eq!(data[4], 99); // row 1, col 1
377		assert_eq!(data[11], 77); // row 3, col 2
378	}
379
380	#[test]
381	fn test_par_iter_cols() {
382		let mut data = array::from_fn::<_, 12, _>(|i| i);
383		let mut arr = StridedArray2DViewMut::without_stride(&mut data, 4, 3).unwrap();
384
385		{
386			let mut cols: Vec<_> = arr.par_iter_cols().collect();
387			assert_eq!(cols.len(), 3);
388
389			cols[0][0] = 88;
390			cols[1][1] = 99;
391			cols[2][3] = 77;
392		}
393
394		assert_eq!(data[0], 88); // row 0, col 0
395		assert_eq!(data[4], 99); // row 1, col 1
396		assert_eq!(data[11], 77); // row 3, col 2
397	}
398}