binius_math/
binary_subspace.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use binius_field::BinaryField;
4use binius_utils::{bail, iter::IterExtensions};
5
6use super::error::Error;
7
8/// An $F_2$-linear subspace of a binary field.
9///
10/// The subspace is defined by a basis of elements from a binary field. The basis elements are
11/// ordered, which implies an ordering on the subspace elements.
12#[derive(Debug, Clone)]
13pub struct BinarySubspace<F: BinaryField> {
14	basis: Vec<F>,
15}
16
17impl<F: BinaryField> BinarySubspace<F> {
18	/// Creates a new subspace from a vector of ordered basis elements.
19	///
20	/// This constructor does not check that the basis elements are linearly independent.
21	pub const fn new_unchecked(basis: Vec<F>) -> Self {
22		Self { basis }
23	}
24
25	/// Creates a new subspace of this binary subspace with the given dimension.
26	///
27	/// This creates a new sub-subspace using a prefix of the default $\mathbb{F}_2$ basis elements
28	/// of the field.
29	///
30	/// ## Throws
31	///
32	/// * `Error::DomainSizeTooLarge` if `dim` is greater than this subspace's dimension.
33	pub fn with_dim(dim: usize) -> Result<Self, Error> {
34		let basis = (0..dim)
35			.map(|i| F::basis(i).map_err(|_| Error::DomainSizeTooLarge))
36			.collect::<Result<_, _>>()?;
37		Ok(Self { basis })
38	}
39
40	/// Creates a new subspace of this binary subspace with reduced dimension.
41	///
42	/// This creates a new sub-subspace using a prefix of the ordered basis elements.
43	///
44	/// ## Throws
45	///
46	/// * `Error::DomainSizeTooLarge` if `dim` is greater than this subspace's dimension.
47	pub fn reduce_dim(&self, dim: usize) -> Result<Self, Error> {
48		if dim > self.dim() {
49			bail!(Error::DomainSizeTooLarge);
50		}
51		Ok(Self {
52			basis: self.basis[..dim].to_vec(),
53		})
54	}
55
56	/// Creates a new subspace isomorphic to the given one.
57	pub fn isomorphic<FIso>(&self) -> BinarySubspace<FIso>
58	where
59		FIso: BinaryField + From<F>,
60	{
61		BinarySubspace {
62			basis: self.basis.iter().copied().map(Into::into).collect(),
63		}
64	}
65
66	/// Returns the dimension of the subspace.
67	pub fn dim(&self) -> usize {
68		self.basis.len()
69	}
70
71	/// Returns the slice of ordered basis elements.
72	pub fn basis(&self) -> &[F] {
73		&self.basis
74	}
75
76	pub fn get(&self, index: usize) -> F {
77		self.basis
78			.iter()
79			.take(usize::BITS as usize)
80			.enumerate()
81			.fold(F::ZERO, |acc, (i, basis_i)| {
82				if (index >> i) & 1 != 0 {
83					acc + basis_i
84				} else {
85					acc
86				}
87			})
88	}
89
90	pub fn get_checked(&self, index: usize) -> Result<F, Error> {
91		if index >= 1 << self.basis.len() {
92			bail!(Error::ArgumentRangeError {
93				arg: "index".to_string(),
94				range: 0..1 << self.basis.len(),
95			});
96		}
97		Ok(self.get(index))
98	}
99
100	/// Returns an iterator over all elements of the subspace in order.
101	///
102	/// This has a limitation that the iterator only yields the first `2^usize::BITS` elements.
103	pub fn iter(&self) -> impl Iterator<Item = F> + '_ {
104		let last = if self.basis.len() < usize::BITS as usize {
105			(1 << self.basis.len()) - 1
106		} else {
107			usize::MAX
108		};
109		(0..=last).map_skippable(|i| self.get(i))
110	}
111}
112
113impl<F: BinaryField> Default for BinarySubspace<F> {
114	fn default() -> Self {
115		let basis = (0..F::DEGREE)
116			.map(|i| F::basis(i).expect("index is in range"))
117			.collect();
118		Self { basis }
119	}
120}
121
122#[cfg(test)]
123mod tests {
124	use assert_matches::assert_matches;
125	use binius_field::{BinaryField128b, BinaryField8b};
126
127	use super::*;
128
129	#[test]
130	fn test_default_binary_subspace_iterates_elements() {
131		let subspace = BinarySubspace::<BinaryField8b>::default();
132		for i in 0..=255 {
133			assert_eq!(subspace.get(i), BinaryField8b::new(i as u8));
134		}
135	}
136
137	#[test]
138	fn test_binary_subspace_range_error() {
139		let subspace = BinarySubspace::<BinaryField8b>::default();
140		assert_matches!(subspace.get_checked(256), Err(Error::ArgumentRangeError { .. }));
141	}
142
143	#[test]
144	fn test_default_large_binary_subspace_iterates_elements() {
145		let subspace = BinarySubspace::<BinaryField128b>::default();
146		for i in 0..=255 {
147			assert_eq!(subspace.get(i), BinaryField128b::new(i as u128));
148		}
149	}
150
151	#[test]
152	fn test_default_binary_subspace() {
153		let subspace = BinarySubspace::<BinaryField8b>::default();
154		assert_eq!(subspace.dim(), 8);
155		assert_eq!(subspace.basis().len(), 8);
156
157		assert_eq!(
158			subspace.basis(),
159			[
160				BinaryField8b::new(0b00000001),
161				BinaryField8b::new(0b00000010),
162				BinaryField8b::new(0b00000100),
163				BinaryField8b::new(0b00001000),
164				BinaryField8b::new(0b00010000),
165				BinaryField8b::new(0b00100000),
166				BinaryField8b::new(0b01000000),
167				BinaryField8b::new(0b10000000)
168			]
169		);
170
171		let expected_elements: [u8; 256] = (0..=255).collect::<Vec<_>>().try_into().unwrap();
172
173		for (i, &expected) in expected_elements.iter().enumerate() {
174			assert_eq!(subspace.get(i), BinaryField8b::new(expected));
175		}
176	}
177
178	#[test]
179	fn test_with_dim_valid() {
180		let subspace = BinarySubspace::<BinaryField8b>::with_dim(3).unwrap();
181		assert_eq!(subspace.dim(), 3);
182		assert_eq!(subspace.basis().len(), 3);
183
184		assert_eq!(
185			subspace.basis(),
186			[
187				BinaryField8b::new(0b001),
188				BinaryField8b::new(0b010),
189				BinaryField8b::new(0b100)
190			]
191		);
192
193		let expected_elements: [u8; 8] = [0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111];
194
195		for (i, &expected) in expected_elements.iter().enumerate() {
196			assert_eq!(subspace.get(i), BinaryField8b::new(expected));
197		}
198	}
199
200	#[test]
201	fn test_with_dim_invalid() {
202		let result = BinarySubspace::<BinaryField8b>::with_dim(10);
203		assert_matches!(result, Err(Error::DomainSizeTooLarge));
204	}
205
206	#[test]
207	fn test_reduce_dim_valid() {
208		let subspace = BinarySubspace::<BinaryField8b>::with_dim(6).unwrap();
209		let reduced = subspace.reduce_dim(4).unwrap();
210		assert_eq!(reduced.dim(), 4);
211		assert_eq!(reduced.basis().len(), 4);
212
213		assert_eq!(
214			reduced.basis(),
215			[
216				BinaryField8b::new(0b0001),
217				BinaryField8b::new(0b0010),
218				BinaryField8b::new(0b0100),
219				BinaryField8b::new(0b1000)
220			]
221		);
222
223		let expected_elements: [u8; 16] = (0..16).collect::<Vec<_>>().try_into().unwrap();
224
225		for (i, &expected) in expected_elements.iter().enumerate() {
226			assert_eq!(reduced.get(i), BinaryField8b::new(expected));
227		}
228	}
229
230	#[test]
231	fn test_reduce_dim_invalid() {
232		let subspace = BinarySubspace::<BinaryField8b>::with_dim(4).unwrap();
233		let result = subspace.reduce_dim(6);
234		assert_matches!(result, Err(Error::DomainSizeTooLarge));
235	}
236
237	#[test]
238	fn test_isomorphic_conversion() {
239		let subspace = BinarySubspace::<BinaryField8b>::with_dim(3).unwrap();
240		let iso_subspace: BinarySubspace<BinaryField128b> = subspace.isomorphic();
241		assert_eq!(iso_subspace.dim(), 3);
242		assert_eq!(iso_subspace.basis().len(), 3);
243
244		assert_eq!(
245			iso_subspace.basis(),
246			[
247				BinaryField128b::from(BinaryField8b::new(0b001)),
248				BinaryField128b::from(BinaryField8b::new(0b010)),
249				BinaryField128b::from(BinaryField8b::new(0b100))
250			]
251		);
252	}
253
254	#[test]
255	fn test_get_checked_valid() {
256		let subspace = BinarySubspace::<BinaryField8b>::default();
257		for i in 0..256 {
258			assert!(subspace.get_checked(i).is_ok());
259		}
260	}
261
262	#[test]
263	fn test_get_checked_invalid() {
264		let subspace = BinarySubspace::<BinaryField8b>::default();
265		assert_matches!(subspace.get_checked(256), Err(Error::ArgumentRangeError { .. }));
266	}
267
268	#[test]
269	fn test_iterate_subspace() {
270		let subspace = BinarySubspace::<BinaryField8b>::with_dim(3).unwrap();
271		let elements: Vec<_> = subspace.iter().collect();
272		assert_eq!(elements.len(), 8);
273
274		let expected_elements: [u8; 8] = [0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111];
275
276		for (i, &expected) in expected_elements.iter().enumerate() {
277			assert_eq!(elements[i], BinaryField8b::new(expected));
278		}
279	}
280}