Skip to main content

binius_field/
linear_transformation.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use std::{marker::PhantomData, ops::BitXor};
4
5use crate::{UnderlierType, WithUnderlier, underlier::Divisible, util::expand_subset_xors};
6
7/// Generic transformation trait that is used both for scalars and packed fields
8pub trait Transformation<Input, Output>: Sync {
9	fn transform(&self, data: &Input) -> Output;
10}
11
12const LOG_BITS_PER_BYTE: usize = 3;
13const BITS_PER_BYTE: usize = 1 << LOG_BITS_PER_BYTE;
14
15/// Linear transformation using precomputed byte-indexed lookup tables.
16///
17/// This implementation uses the [Method of Four Russians] to optimize the computation by
18/// precomputing lookup tables for each byte position and using bitwise chunks of the words.
19///
20/// [Method of Four Russians]: <https://en.wikipedia.org/wiki/Method_of_Four_Russians>
21#[derive(Debug)]
22pub struct BytewiseLookupTransformation<UIn, UOut> {
23	lookup: Vec<[UOut; 1 << BITS_PER_BYTE]>,
24	_uin_marker: PhantomData<UIn>,
25}
26
27impl<UIn, UOut> BytewiseLookupTransformation<UIn, UOut>
28where
29	UIn: UnderlierType + Divisible<u8>,
30	UOut: UnderlierType,
31{
32	pub fn new(cols: &[UOut]) -> Self {
33		assert!(LOG_BITS_PER_BYTE <= UIn::LOG_BITS);
34		assert_eq!(cols.len(), UIn::BITS);
35
36		let lookup = cols
37			.chunks(BITS_PER_BYTE)
38			.map(|cols| {
39				let cols: [_; BITS_PER_BYTE] = cols.try_into().expect(
40					"chunk size is BITS_PER_BYTE; \
41					cols.len() is a multiple of BITS_PER_BYTE",
42				);
43				expand_subset_xors(cols)
44			})
45			.collect();
46
47		Self {
48			lookup,
49			_uin_marker: PhantomData,
50		}
51	}
52}
53
54impl<UIn, UOut> Transformation<UIn, UOut> for BytewiseLookupTransformation<UIn, UOut>
55where
56	UIn: UnderlierType + Divisible<u8>,
57	UOut: UnderlierType,
58{
59	#[inline]
60	fn transform(&self, data: &UIn) -> UOut {
61		Divisible::<u8>::ref_iter(data)
62			.enumerate()
63			.take(1 << (UIn::LOG_BITS - LOG_BITS_PER_BYTE))
64			.map(|(i, byte)| {
65				// Safety:
66				// - lookup.len() == 2^(UIn::LOG_BITS - LOG_BITS_PER_BYTE) by struct invariant
67				// - take limits iteration calls to 2^(UIn::LOG_BITS - LOG_BITS_PER_BYTE)
68				let lookup = unsafe { self.lookup.get_unchecked(i) };
69				lookup[byte as usize]
70			})
71			.reduce(BitXor::bitxor)
72			.unwrap_or(UOut::ZERO)
73	}
74}
75
76/// Factory for creating bytewise lookup transformations.
77#[derive(Debug)]
78pub struct BytewiseLookupTransformationFactory;
79
80/// Factory trait for creating linear transformations from column data.
81pub trait LinearTransformationFactory<Input, Output> {
82	type Transform: Transformation<Input, Output>;
83
84	fn create(&self, cols: &[Output]) -> Self::Transform;
85}
86
87impl<UIn, UOut> LinearTransformationFactory<UIn, UOut> for BytewiseLookupTransformationFactory
88where
89	UIn: UnderlierType + Divisible<u8>,
90	UOut: UnderlierType,
91{
92	type Transform = BytewiseLookupTransformation<UIn, UOut>;
93
94	fn create(&self, cols: &[UOut]) -> Self::Transform {
95		BytewiseLookupTransformation::new(cols)
96	}
97}
98
99/// Wraps a transformation on underliers to operate on types with underliers.
100#[derive(Debug)]
101pub struct OutputWrappingTransformation<Inner, Input, Output> {
102	inner: Inner,
103	_marker: PhantomData<(Input, Output)>,
104}
105
106impl<Inner, Input, Output> Transformation<Input, Output>
107	for OutputWrappingTransformation<Inner, Input, Output>
108where
109	Inner: Transformation<Input, Output::Underlier>,
110	Input: Sync,
111	Output: WithUnderlier,
112{
113	#[inline]
114	fn transform(&self, data: &Input) -> Output {
115		Output::from_underlier(self.inner.transform(data))
116	}
117}
118
119/// Factory that wraps an underlier transformation factory to work with types that have underliers.
120#[derive(Debug)]
121pub struct OutputWrappingTransformationFactory<Inner, Input, Output> {
122	inner: Inner,
123	_marker: PhantomData<(Input, Output)>,
124}
125
126impl<Inner, Input, Output> OutputWrappingTransformationFactory<Inner, Input, Output>
127where
128	Inner: LinearTransformationFactory<Input, Output::Underlier>,
129	Input: Sync,
130	Output: WithUnderlier,
131{
132	pub const fn new(inner: Inner) -> Self {
133		Self {
134			inner,
135			_marker: PhantomData,
136		}
137	}
138}
139
140impl<Inner, Input, Output> LinearTransformationFactory<Input, Output>
141	for OutputWrappingTransformationFactory<Inner, Input, Output>
142where
143	Inner: LinearTransformationFactory<Input, Output::Underlier>,
144	Input: Sync,
145	Output: WithUnderlier,
146{
147	type Transform = OutputWrappingTransformation<Inner::Transform, Input, Output>;
148
149	#[inline]
150	fn create(&self, cols: &[Output]) -> Self::Transform {
151		OutputWrappingTransformation {
152			inner: self.inner.create(Output::to_underliers_ref(cols)),
153			_marker: PhantomData,
154		}
155	}
156}
157
158/// Wraps a transformation on underliers to accept inputs with underliers.
159#[derive(Debug)]
160pub struct InputWrappingTransformation<Inner, Input, Output> {
161	inner: Inner,
162	_marker: PhantomData<(Input, Output)>,
163}
164
165impl<Inner, Input, Output> Transformation<Input, Output>
166	for InputWrappingTransformation<Inner, Input, Output>
167where
168	Inner: Transformation<Input::Underlier, Output>,
169	Input: WithUnderlier,
170	Output: Sync,
171{
172	#[inline]
173	fn transform(&self, data: &Input) -> Output {
174		self.inner.transform(&data.to_underlier())
175	}
176}
177
178/// Factory that wraps an underlier transformation factory to accept inputs with underliers.
179#[derive(Debug)]
180pub struct InputWrappingTransformationFactory<Inner, Input, Output> {
181	inner: Inner,
182	_marker: PhantomData<(Input, Output)>,
183}
184
185impl<Inner, Input, Output> InputWrappingTransformationFactory<Inner, Input, Output>
186where
187	Inner: LinearTransformationFactory<Input::Underlier, Output>,
188	Input: WithUnderlier,
189	Output: Sync,
190{
191	pub const fn new(inner: Inner) -> Self {
192		Self {
193			inner,
194			_marker: PhantomData,
195		}
196	}
197}
198
199impl<Inner, Input, Output> LinearTransformationFactory<Input, Output>
200	for InputWrappingTransformationFactory<Inner, Input, Output>
201where
202	Inner: LinearTransformationFactory<Input::Underlier, Output>,
203	Input: WithUnderlier,
204	Output: Sync,
205{
206	type Transform = InputWrappingTransformation<Inner::Transform, Input, Output>;
207
208	#[inline]
209	fn create(&self, cols: &[Output]) -> Self::Transform {
210		InputWrappingTransformation {
211			inner: self.inner.create(cols),
212			_marker: PhantomData,
213		}
214	}
215}
216
217/// Transformation that wraps both input and output, converting between types with underliers.
218pub type WrappingTransformation<Inner, Input, Output> = OutputWrappingTransformation<
219	InputWrappingTransformation<Inner, Input, <Output as WithUnderlier>::Underlier>,
220	Input,
221	Output,
222>;