1use binius_utils::checked_arithmetics::checked_log_2;
5
6use super::packed::PackedField;
7use crate::{BinaryField, ExtensionField, PackedSubfield, UnderlierView, packed_extension};
8
9pub fn transpose_square_blocks<P: PackedField>(log_n: usize, elems: &mut [P]) {
29 assert!(P::LOG_WIDTH >= log_n, "dimension n of square blocks must divide packing width");
30
31 let size = elems.len();
32 assert!(size.is_power_of_two(), "elems length must be a power of two, got {size}");
33 let log_size = checked_log_2(size);
34 assert!(
35 log_size >= log_n,
36 "elems must have length at least 2^log_n = {}, got {size}",
37 1 << log_n
38 );
39
40 let log_w = log_size - log_n;
41
42 for i in 0..log_n {
45 for j in 0..1 << (log_n - i - 1) {
46 for k in 0..1 << (log_w + i) {
47 let idx0 = (j << (log_w + i + 1)) | k;
48 let idx1 = idx0 | (1 << (log_w + i));
49
50 let v0 = elems[idx0];
51 let v1 = elems[idx1];
52 let (v0, v1) = v0.interleave(v1, i);
53 elems[idx0] = v0;
54 elems[idx1] = v1;
55 }
56 }
57 }
58}
59
60pub fn transpose_square_blocks_array<P: PackedField, const LOG_N: usize, const S: usize>(
83 elems: &mut [P; S],
84) {
85 const {
86 assert!(LOG_N <= P::LOG_WIDTH, "LOG_N must not exceed the packed width");
87 assert!(LOG_N <= checked_log_2(S), "LOG_N must not exceed the array length");
88 }
89
90 let log_size = checked_log_2(S);
91
92 let log_w = log_size - LOG_N;
94
95 for i in 0..LOG_N {
96 for j in 0..1 << (LOG_N - i - 1) {
97 for k in 0..1 << (log_w + i) {
98 let idx0 = (j << (log_w + i + 1)) | k;
100 let idx1 = idx0 | (1 << (log_w + i));
101
102 let (v0, v1) = elems[idx0].interleave(elems[idx1], i);
104 elems[idx0] = v0;
105 elems[idx1] = v1;
106 }
107 }
108 }
109}
110
111pub fn square_transforms_extension_field<F, FE>(values: &mut [FE])
112where
113 F: BinaryField,
114 FE: PackedField<Scalar: ExtensionField<F>> + UnderlierView,
115 PackedSubfield<FE, F>: PackedField<Scalar = F>,
116{
117 transpose_square_blocks(
118 FE::Scalar::LOG_DEGREE,
119 packed_extension::cast_bases_mut::<F, FE>(values),
120 );
121}
122
123#[cfg(test)]
124mod tests {
125 use std::array;
126
127 use proptest::prelude::*;
128 use rand::{SeedableRng, rngs::StdRng};
129
130 use super::*;
131 use crate::{PackedBinaryField64x1b, PackedBinaryField128x1b, PackedField, Random};
132
133 #[test]
134 fn test_transpose_square_blocks_128x1b() {
135 let mut elems = [
136 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
137 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
138 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
139 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
140 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
141 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
142 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
143 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
144 ];
145 transpose_square_blocks(3, &mut elems);
146
147 let expected = [
148 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
149 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
150 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
151 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
152 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
153 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
154 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
155 PackedBinaryField128x1b::from(0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0u128),
156 ];
157 assert_eq!(elems, expected);
158 }
159
160 #[test]
161 fn test_transpose_square_blocks_128x1b_multi_row() {
162 let mut elems = [
163 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
164 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
165 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
166 PackedBinaryField128x1b::from(0x00000000000000000000000000000000u128),
167 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
168 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
169 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
170 PackedBinaryField128x1b::from(0xffffffffffffffffffffffffffffffffu128),
171 ];
172 transpose_square_blocks(1, &mut elems);
173
174 let expected = [
175 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
176 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
177 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
178 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
179 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
180 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
181 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
182 PackedBinaryField128x1b::from(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau128),
183 ];
184 assert_eq!(elems, expected);
185 }
186
187 fn check_forms_agree<P, const LOG_N: usize, const S: usize>(seed: u64)
194 where
195 P: PackedField + Random,
196 {
197 let mut rng = StdRng::seed_from_u64(seed);
198
199 for _ in 0..100 {
201 let input: [P; S] = array::from_fn(|_| P::random(&mut rng));
202
203 let mut runtime = input;
204 transpose_square_blocks(LOG_N, &mut runtime);
205
206 let mut fixed = input;
207 transpose_square_blocks_array::<P, LOG_N, S>(&mut fixed);
208
209 assert_eq!(fixed, runtime, "forms disagree at LOG_N = {LOG_N}, S = {S}");
210 }
211 }
212
213 #[test]
214 fn fixed_size_form_agrees_with_runtime_form() {
215 check_forms_agree::<PackedBinaryField64x1b, 0, 8>(0);
220 check_forms_agree::<PackedBinaryField64x1b, 1, 8>(1);
221 check_forms_agree::<PackedBinaryField64x1b, 3, 8>(2);
222 check_forms_agree::<PackedBinaryField128x1b, 3, 8>(3);
223
224 check_forms_agree::<PackedBinaryField64x1b, 4, 16>(4);
226 check_forms_agree::<PackedBinaryField128x1b, 5, 32>(5);
227 }
228
229 #[test]
230 fn transpose_exchanges_element_axis_with_low_scalar_bits() {
231 let mut rng = StdRng::seed_from_u64(0);
232
233 for _ in 0..100 {
241 let input: [PackedBinaryField128x1b; 8] =
242 array::from_fn(|_| PackedBinaryField128x1b::random(&mut rng));
243 let mut output = input;
244 transpose_square_blocks_array::<_, 3, 8>(&mut output);
245
246 let scalars = |elems: &[PackedBinaryField128x1b; 8]| {
248 elems
249 .iter()
250 .map(|e| e.iter().collect::<Vec<_>>())
251 .collect::<Vec<_>>()
252 };
253 let before = scalars(&input);
254 let after = scalars(&output);
255
256 for i in 0..PackedBinaryField128x1b::WIDTH / 8 {
258 for j in 0..8 {
260 for t in 0..8 {
262 assert_eq!(
263 after[j][i * 8 + t],
264 before[t][i * 8 + j],
265 "i={i}, j={j}, t={t}"
266 );
267 }
268 }
269 }
270 }
271 }
272
273 proptest! {
274 #[test]
275 fn transpose_is_an_involution(values in prop::collection::vec(any::<u128>(), 8)) {
276 let input: [PackedBinaryField128x1b; 8] =
279 array::from_fn(|i| PackedBinaryField128x1b::from(values[i]));
280
281 let mut roundtrip = input;
282 transpose_square_blocks_array::<_, 3, 8>(&mut roundtrip);
283 transpose_square_blocks_array::<_, 3, 8>(&mut roundtrip);
284
285 prop_assert_eq!(roundtrip, input);
286 }
287 }
288}