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		const {
34			assert!(
35				LOG_BITS_PER_BYTE <= UIn::LOG_BITS,
36				"the underlier must be at least a byte wide"
37			);
38		}
39		assert_eq!(cols.len(), UIn::BITS);
40
41		let lookup = cols
42			.chunks(BITS_PER_BYTE)
43			.map(|cols| {
44				let cols: [_; BITS_PER_BYTE] = cols.try_into().expect(
45					"chunk size is BITS_PER_BYTE; \
46					cols.len() is a multiple of BITS_PER_BYTE",
47				);
48				expand_subset_xors(cols)
49			})
50			.collect();
51
52		Self {
53			lookup,
54			_uin_marker: PhantomData,
55		}
56	}
57}
58
59impl<UIn, UOut> Transformation<UIn, UOut> for BytewiseLookupTransformation<UIn, UOut>
60where
61	UIn: UnderlierType + Divisible<u8>,
62	UOut: UnderlierType,
63{
64	#[inline]
65	fn transform(&self, data: &UIn) -> UOut {
66		Divisible::<u8>::ref_iter(data)
67			.enumerate()
68			.take(1 << (UIn::LOG_BITS - LOG_BITS_PER_BYTE))
69			.map(|(i, byte)| {
70				// Safety:
71				// - lookup.len() == 2^(UIn::LOG_BITS - LOG_BITS_PER_BYTE) by struct invariant
72				// - take limits iteration calls to 2^(UIn::LOG_BITS - LOG_BITS_PER_BYTE)
73				let lookup = unsafe { self.lookup.get_unchecked(i) };
74				lookup[byte as usize]
75			})
76			.reduce(BitXor::bitxor)
77			.unwrap_or(UOut::ZERO)
78	}
79}
80
81/// Factory for creating bytewise lookup transformations.
82#[derive(Debug)]
83pub struct BytewiseLookupTransformationFactory;
84
85/// Factory trait for creating linear transformations from column data.
86pub trait LinearTransformationFactory<Input, Output> {
87	type Transform: Transformation<Input, Output>;
88
89	fn create(&self, cols: &[Output]) -> Self::Transform;
90}
91
92impl<UIn, UOut> LinearTransformationFactory<UIn, UOut> for BytewiseLookupTransformationFactory
93where
94	UIn: UnderlierType + Divisible<u8>,
95	UOut: UnderlierType,
96{
97	type Transform = BytewiseLookupTransformation<UIn, UOut>;
98
99	fn create(&self, cols: &[UOut]) -> Self::Transform {
100		BytewiseLookupTransformation::new(cols)
101	}
102}
103
104/// Wraps a transformation on underliers to operate on types with underliers.
105#[derive(Debug)]
106pub struct OutputWrappingTransformation<Inner, Input, Output> {
107	inner: Inner,
108	_marker: PhantomData<(Input, Output)>,
109}
110
111impl<Inner, Input, Output> Transformation<Input, Output>
112	for OutputWrappingTransformation<Inner, Input, Output>
113where
114	Inner: Transformation<Input, Output::Underlier>,
115	Input: Sync,
116	Output: WithUnderlier,
117{
118	#[inline]
119	fn transform(&self, data: &Input) -> Output {
120		Output::from_underlier(self.inner.transform(data))
121	}
122}
123
124/// Factory that wraps an underlier transformation factory to work with types that have underliers.
125#[derive(Debug)]
126pub struct OutputWrappingTransformationFactory<Inner, Input, Output> {
127	inner: Inner,
128	_marker: PhantomData<(Input, Output)>,
129}
130
131impl<Inner, Input, Output> OutputWrappingTransformationFactory<Inner, Input, Output>
132where
133	Inner: LinearTransformationFactory<Input, Output::Underlier>,
134	Input: Sync,
135	Output: WithUnderlier,
136{
137	pub const fn new(inner: Inner) -> Self {
138		Self {
139			inner,
140			_marker: PhantomData,
141		}
142	}
143}
144
145impl<Inner, Input, Output> LinearTransformationFactory<Input, Output>
146	for OutputWrappingTransformationFactory<Inner, Input, Output>
147where
148	Inner: LinearTransformationFactory<Input, Output::Underlier>,
149	Input: Sync,
150	Output: WithUnderlier,
151{
152	type Transform = OutputWrappingTransformation<Inner::Transform, Input, Output>;
153
154	#[inline]
155	fn create(&self, cols: &[Output]) -> Self::Transform {
156		OutputWrappingTransformation {
157			inner: self.inner.create(Output::to_underliers_ref(cols)),
158			_marker: PhantomData,
159		}
160	}
161}
162
163/// Wraps a transformation on underliers to accept inputs with underliers.
164#[derive(Debug)]
165pub struct InputWrappingTransformation<Inner, Input, Output> {
166	inner: Inner,
167	_marker: PhantomData<(Input, Output)>,
168}
169
170impl<Inner, Input, Output> Transformation<Input, Output>
171	for InputWrappingTransformation<Inner, Input, Output>
172where
173	Inner: Transformation<Input::Underlier, Output>,
174	Input: WithUnderlier,
175	Output: Sync,
176{
177	#[inline]
178	fn transform(&self, data: &Input) -> Output {
179		self.inner.transform(&data.to_underlier())
180	}
181}
182
183/// Factory that wraps an underlier transformation factory to accept inputs with underliers.
184#[derive(Debug)]
185pub struct InputWrappingTransformationFactory<Inner, Input, Output> {
186	inner: Inner,
187	_marker: PhantomData<(Input, Output)>,
188}
189
190impl<Inner, Input, Output> InputWrappingTransformationFactory<Inner, Input, Output>
191where
192	Inner: LinearTransformationFactory<Input::Underlier, Output>,
193	Input: WithUnderlier,
194	Output: Sync,
195{
196	pub const fn new(inner: Inner) -> Self {
197		Self {
198			inner,
199			_marker: PhantomData,
200		}
201	}
202}
203
204impl<Inner, Input, Output> LinearTransformationFactory<Input, Output>
205	for InputWrappingTransformationFactory<Inner, Input, Output>
206where
207	Inner: LinearTransformationFactory<Input::Underlier, Output>,
208	Input: WithUnderlier,
209	Output: Sync,
210{
211	type Transform = InputWrappingTransformation<Inner::Transform, Input, Output>;
212
213	#[inline]
214	fn create(&self, cols: &[Output]) -> Self::Transform {
215		InputWrappingTransformation {
216			inner: self.inner.create(cols),
217			_marker: PhantomData,
218		}
219	}
220}
221
222/// Transformation that wraps both input and output, converting between types with underliers.
223pub type WrappingTransformation<Inner, Input, Output> = OutputWrappingTransformation<
224	InputWrappingTransformation<Inner, Input, <Output as WithUnderlier>::Underlier>,
225	Input,
226	Output,
227>;