Skip to main content

binius_utils/
random_access_sequence.rs

1// Copyright 2025 Irreducible Inc.
2
3/// A trait for a collection that allows indexed access by value.
4/// This trait is used to abstract over different types of collections - scalar slices,
5/// slices of packed field elements including subranges of collections.
6pub trait RandomAccessSequence<T: Copy> {
7	fn len(&self) -> usize;
8
9	#[inline(always)]
10	fn is_empty(&self) -> bool {
11		self.len() == 0
12	}
13
14	#[inline(always)]
15	fn get(&self, index: usize) -> T {
16		assert!(index < self.len(), "Index out of bounds");
17		unsafe { self.get_unchecked(index) }
18	}
19
20	/// Returns a copy of the element at the given index.
21	///
22	/// # Safety
23	/// The caller must ensure that the `index` < `self.len()`.
24	unsafe fn get_unchecked(&self, index: usize) -> T;
25}
26
27/// A trait for a mutable access to a collection of scalars.
28pub trait RandomAccessSequenceMut<T: Copy>: RandomAccessSequence<T> {
29	#[inline(always)]
30	fn set(&mut self, index: usize, value: T) {
31		assert!(index < self.len(), "Index out of bounds");
32		unsafe { self.set_unchecked(index, value) }
33	}
34
35	/// Sets the element at the given index to the given value.
36	///
37	/// # Safety
38	/// The caller must ensure that the `index` < `self.len()`.
39	unsafe fn set_unchecked(&mut self, index: usize, value: T);
40}
41
42impl<T: Copy> RandomAccessSequence<T> for &[T] {
43	#[inline(always)]
44	fn len(&self) -> usize {
45		<[T]>::len(self)
46	}
47
48	#[inline(always)]
49	fn get(&self, index: usize) -> T {
50		self[index]
51	}
52
53	#[inline(always)]
54	unsafe fn get_unchecked(&self, index: usize) -> T {
55		unsafe { *<[T]>::get_unchecked(self, index) }
56	}
57}
58
59impl<T: Copy> RandomAccessSequence<T> for &mut [T] {
60	#[inline(always)]
61	fn len(&self) -> usize {
62		<[T]>::len(self)
63	}
64
65	#[inline(always)]
66	fn get(&self, index: usize) -> T {
67		self[index]
68	}
69
70	#[inline(always)]
71	unsafe fn get_unchecked(&self, index: usize) -> T {
72		unsafe { *<[T]>::get_unchecked(self, index) }
73	}
74}
75
76impl<T: Copy> RandomAccessSequenceMut<T> for &mut [T] {
77	#[inline(always)]
78	fn set(&mut self, index: usize, value: T) {
79		self[index] = value;
80	}
81
82	#[inline(always)]
83	unsafe fn set_unchecked(&mut self, index: usize, value: T) {
84		unsafe {
85			*<[T]>::get_unchecked_mut(self, index) = value;
86		}
87	}
88}
89
90/// Power-of-two aligned vertical slice of a sequence when viewed as a row-major matrix.
91/// This is useful access pattern for algorithms like 4-step NTT or switchover.
92#[derive(Clone)]
93pub struct MatrixVertSliceSubrange<'a, T: Copy, Inner: RandomAccessSequence<T>> {
94	inner: &'a Inner,
95	log_cols: usize,
96	log_slice: usize,
97	slice_index: usize,
98	len: usize,
99	_marker: std::marker::PhantomData<T>,
100}
101
102impl<'a, T: Copy, Inner: RandomAccessSequence<T>> MatrixVertSliceSubrange<'a, T, Inner> {
103	/// Rearrange the `inner` sequence into a row-major matrix with sides `2^log_rows` and
104	/// `2^log_cols`, then take an aligned vertical slice of size `2^log_slice` with index
105	/// `slice_index` (which ranges from 0 to `2^(log_cols - log_slice)`, non-inclusive), and
106	/// present that slice as a view.
107	#[inline(always)]
108	pub fn new(
109		inner: &'a Inner,
110		log_rows: usize,
111		log_cols: usize,
112		log_slice: usize,
113		slice_index: usize,
114	) -> Self {
115		assert_eq!(
116			1 << (log_rows + log_cols),
117			inner.len(),
118			"matrix dimensions do not match inner sequence"
119		);
120		assert!(log_slice <= log_cols && slice_index < 1 << (log_cols - log_slice));
121
122		let len = 1 << (log_slice + log_rows);
123
124		Self {
125			inner,
126			log_cols,
127			log_slice,
128			slice_index,
129			len,
130			_marker: std::marker::PhantomData,
131		}
132	}
133}
134
135impl<T: Copy, Inner: RandomAccessSequence<T>> RandomAccessSequence<T>
136	for MatrixVertSliceSubrange<'_, T, Inner>
137{
138	#[inline(always)]
139	fn len(&self) -> usize {
140		self.len
141	}
142
143	#[inline(always)]
144	unsafe fn get_unchecked(&self, index: usize) -> T {
145		let row = index >> self.log_slice;
146		let col = index ^ (row << self.log_slice);
147		let inner_index = row << self.log_cols | self.slice_index << self.log_slice | col;
148		unsafe { self.inner.get_unchecked(inner_index) }
149	}
150}
151
152#[cfg(test)]
153mod tests {
154	use std::fmt::Debug;
155
156	use rand::prelude::*;
157
158	use super::*;
159
160	fn check_collection<T: Copy + Eq + Debug>(
161		collection: &impl RandomAccessSequence<T>,
162		expected: &[T],
163	) {
164		assert_eq!(collection.len(), expected.len());
165
166		for (i, v) in expected.iter().enumerate() {
167			assert_eq!(&collection.get(i), v);
168			assert_eq!(&unsafe { collection.get_unchecked(i) }, v);
169		}
170	}
171
172	fn check_collection_get_set<T: Eq + Copy + Debug>(
173		collection: &mut impl RandomAccessSequenceMut<T>,
174		random: &mut impl FnMut() -> T,
175	) {
176		for i in 0..collection.len() {
177			let value = random();
178			collection.set(i, value);
179			assert_eq!(collection.get(i), value);
180			assert_eq!(unsafe { collection.get_unchecked(i) }, value);
181		}
182	}
183
184	#[test]
185	fn check_slice() {
186		let slice: &[usize] = &[];
187		check_collection::<usize>(&slice, slice);
188
189		let slice: &[usize] = &[1usize, 2, 3];
190		check_collection(&slice, slice);
191	}
192
193	#[test]
194	fn check_slice_mut() {
195		let mut rng = StdRng::seed_from_u64(0);
196		let mut random = || -> usize { rng.random::<u64>() as usize };
197
198		let mut slice: &mut [usize] = &mut [];
199
200		check_collection(&slice, slice);
201		check_collection_get_set(&mut slice, &mut random);
202
203		let mut slice: &mut [usize] = &mut [1, 2, 3];
204		check_collection(&slice, slice);
205		check_collection_get_set(&mut slice, &mut random);
206	}
207}