1use std::{
29 iter,
30 marker::PhantomData,
31 ops::{Deref, DerefMut, Range},
32 slice,
33};
34
35use binius_field::PackedField;
36use binius_utils::rayon::{iter::Either, prelude::*, slice::ParallelSlice};
37
38use super::{FieldBuffer, FieldSlice, FieldSliceData, FieldSliceMut};
39
40impl<P: PackedField, Data: Deref<Target = [P]>> FieldBuffer<P, Data> {
41 #[inline]
51 #[track_caller]
52 pub fn chunk(&self, log_chunk_size: usize, chunk_index: usize) -> FieldSlice<'_, P> {
53 assert!(
54 log_chunk_size <= self.log_len,
55 "precondition: log_chunk_size must be at most log_len"
56 );
57
58 let chunk_count = 1 << (self.log_len - log_chunk_size);
59 assert!(
60 chunk_index < chunk_count,
61 "precondition: chunk_index must be less than chunk_count"
62 );
63
64 let words = if log_chunk_size >= P::LOG_WIDTH {
65 let words_per_chunk = 1 << (log_chunk_size - P::LOG_WIDTH);
67 FieldSliceData::Slice(&self.words[chunk_index * words_per_chunk..][..words_per_chunk])
68 } else {
69 FieldSliceData::Single(
71 SubWordChunk::new(log_chunk_size, chunk_index).repack(&self.words),
72 )
73 };
74
75 FieldBuffer {
76 log_len: log_chunk_size,
77 words,
78 }
79 }
80
81 #[track_caller]
91 pub fn chunks(&self, log_chunk_size: usize) -> Chunks<'_, P> {
92 assert!(
93 log_chunk_size <= self.log_len,
94 "precondition: log_chunk_size must be at most log_len"
95 );
96
97 let chunk_count = 1 << (self.log_len - log_chunk_size);
98 Chunks::new(self.as_ref(), log_chunk_size, chunk_count)
99 }
100
101 #[track_caller]
107 pub fn par_chunks(
108 &self,
109 log_chunk_size: usize,
110 ) -> impl IndexedParallelIterator<Item = FieldSlice<'_, P>> {
111 assert!(
112 log_chunk_size <= self.log_len,
113 "precondition: log_chunk_size must be at most log_len"
114 );
115
116 if log_chunk_size >= P::LOG_WIDTH {
117 let packed_chunk_size = 1 << (log_chunk_size - P::LOG_WIDTH);
119 Either::Left(
120 self.as_ref()
121 .par_chunks(packed_chunk_size)
122 .map(move |chunk| FieldBuffer {
123 log_len: log_chunk_size,
124 words: FieldSliceData::Slice(chunk),
125 }),
126 )
127 } else {
128 let chunk_count = 1 << (self.log_len - log_chunk_size);
130 let words = self.as_ref();
131 Either::Right(
132 (0..chunk_count)
133 .into_par_iter()
134 .map(move |chunk_index| FieldBuffer {
135 log_len: log_chunk_size,
136 words: FieldSliceData::Single(
137 SubWordChunk::new(log_chunk_size, chunk_index).repack(words),
138 ),
139 }),
140 )
141 }
142 }
143
144 #[track_caller]
166 pub fn par_chunk_scalars(
167 &self,
168 log_chunk_size: usize,
169 ) -> impl IndexedParallelIterator<Item: Iterator<Item = P::Scalar> + Send + Clone + '_> {
170 assert!(
171 log_chunk_size <= self.log_len,
172 "precondition: log_chunk_size must be at most log_len"
173 );
174
175 let words = self.as_ref();
176 if log_chunk_size >= P::LOG_WIDTH {
177 let words_per_chunk = 1 << (log_chunk_size - P::LOG_WIDTH);
184 Either::Left(
185 words
186 .par_chunks(words_per_chunk)
187 .map(|chunk| Either::Left(P::iter_slice(chunk))),
188 )
189 } else {
190 let chunk_count = 1 << (self.log_len - log_chunk_size);
197
198 Either::Right((0..chunk_count).into_par_iter().map(move |chunk_index| {
199 let chunk = SubWordChunk::new(log_chunk_size, chunk_index);
200 Either::Right(chunk.scalars(words[chunk.word_index()]))
201 }))
202 }
203 }
204}
205
206impl<P: PackedField, Data: DerefMut<Target = [P]>> FieldBuffer<P, Data> {
207 #[track_caller]
223 pub fn chunks_mut(&mut self, log_chunk_size: usize) -> ChunksMut<'_, P> {
224 assert!(
225 log_chunk_size >= P::LOG_WIDTH && log_chunk_size <= self.log_len,
226 "precondition: log_chunk_size must be in range [P::LOG_WIDTH, log_len]"
227 );
228
229 let chunk_count = 1 << (self.log_len - log_chunk_size);
230 ChunksMut::new(self.as_mut(), log_chunk_size, chunk_count)
231 }
232
233 #[track_caller]
250 pub fn chunk_mut(&mut self, log_chunk_size: usize, chunk_index: usize) -> ChunkMut<'_, P> {
251 assert!(
252 log_chunk_size <= self.log_len,
253 "precondition: log_chunk_size must be at most log_len"
254 );
255
256 let chunk_count = 1 << (self.log_len - log_chunk_size);
257 assert!(
258 chunk_index < chunk_count,
259 "precondition: chunk_index must be less than chunk_count"
260 );
261
262 ChunkMut::new(log_chunk_size, chunk_index, &mut self.words)
265 }
266}
267
268#[derive(Debug, Clone, Copy)]
286struct SubWordChunk<P> {
287 word_index: usize,
289 lane_offset: usize,
291 log_len: usize,
293 packing: PhantomData<P>,
295}
296
297impl<P: PackedField> SubWordChunk<P> {
298 #[inline]
302 const fn new(log_chunk_size: usize, chunk_index: usize) -> Self {
303 let log_chunks_per_word = P::LOG_WIDTH - log_chunk_size;
304 let chunk_subindex = chunk_index & ((1 << log_chunks_per_word) - 1);
305 Self {
306 word_index: chunk_index >> log_chunks_per_word,
307 lane_offset: chunk_subindex << log_chunk_size,
308 log_len: log_chunk_size,
309 packing: PhantomData,
310 }
311 }
312
313 #[inline]
315 const fn word_index(self) -> usize {
316 self.word_index
317 }
318
319 #[inline]
321 const fn log_len(self) -> usize {
322 self.log_len
323 }
324
325 #[inline]
327 fn scalars(self, word: P) -> impl Iterator<Item = P::Scalar> + Send + Clone {
328 (0..1 << self.log_len).map(move |i| word.get(self.lane_offset | i))
329 }
330
331 #[inline]
335 fn repack(self, words: &[P]) -> P {
336 P::from_scalars(self.scalars(words[self.word_index]))
337 }
338
339 #[inline]
353 fn merge_into(self, word: &mut P, chunk: &P) {
354 for i in 0..1 << self.log_len {
356 word.set(self.lane_offset | i, chunk.get(i));
359 }
360 }
361}
362
363#[derive(Clone)]
370enum ChunkSource<'a, P: PackedField> {
371 Words(iter::Take<slice::Chunks<'a, P>>),
373 Lanes {
375 words: &'a [P],
376 indices: Range<usize>,
377 },
378}
379
380pub struct Chunks<'a, P: PackedField> {
386 source: ChunkSource<'a, P>,
388 log_chunk_size: usize,
390}
391
392impl<'a, P: PackedField> Chunks<'a, P> {
393 #[inline]
395 fn new(words: &'a [P], log_chunk_size: usize, chunk_count: usize) -> Self {
396 let source = if log_chunk_size >= P::LOG_WIDTH {
397 let words_per_chunk = 1 << (log_chunk_size - P::LOG_WIDTH);
398 ChunkSource::Words(words.chunks(words_per_chunk).take(chunk_count))
399 } else {
400 ChunkSource::Lanes {
401 words,
402 indices: 0..chunk_count,
403 }
404 };
405 Self {
406 source,
407 log_chunk_size,
408 }
409 }
410}
411
412impl<'a, P: PackedField> Iterator for Chunks<'a, P> {
413 type Item = FieldSlice<'a, P>;
414
415 #[inline]
416 fn next(&mut self) -> Option<Self::Item> {
417 let words = match &mut self.source {
418 ChunkSource::Words(runs) => FieldSliceData::Slice(runs.next()?),
419 ChunkSource::Lanes { words, indices } => FieldSliceData::Single(
420 SubWordChunk::<P>::new(self.log_chunk_size, indices.next()?).repack(words),
421 ),
422 };
423 Some(FieldBuffer {
424 log_len: self.log_chunk_size,
425 words,
426 })
427 }
428
429 #[inline]
430 fn size_hint(&self) -> (usize, Option<usize>) {
431 match &self.source {
432 ChunkSource::Words(runs) => runs.size_hint(),
433 ChunkSource::Lanes { indices, .. } => indices.size_hint(),
434 }
435 }
436}
437
438impl<P: PackedField> ExactSizeIterator for Chunks<'_, P> {}
439
440impl<P: PackedField> Clone for Chunks<'_, P> {
441 fn clone(&self) -> Self {
442 Self {
443 source: self.source.clone(),
444 log_chunk_size: self.log_chunk_size,
445 }
446 }
447}
448
449pub struct ChunksMut<'a, P: PackedField> {
454 runs: iter::Take<slice::ChunksMut<'a, P>>,
456 log_chunk_size: usize,
458}
459
460impl<'a, P: PackedField> ChunksMut<'a, P> {
461 #[inline]
463 fn new(words: &'a mut [P], log_chunk_size: usize, chunk_count: usize) -> Self {
464 let words_per_chunk = 1 << (log_chunk_size - P::LOG_WIDTH);
465 Self {
466 runs: words.chunks_mut(words_per_chunk).take(chunk_count),
467 log_chunk_size,
468 }
469 }
470}
471
472impl<'a, P: PackedField> Iterator for ChunksMut<'a, P> {
473 type Item = FieldSliceMut<'a, P>;
474
475 #[inline]
476 fn next(&mut self) -> Option<Self::Item> {
477 self.runs.next().map(|run| FieldBuffer {
478 log_len: self.log_chunk_size,
479 words: run,
480 })
481 }
482
483 #[inline]
484 fn size_hint(&self) -> (usize, Option<usize>) {
485 self.runs.size_hint()
486 }
487}
488
489impl<P: PackedField> ExactSizeIterator for ChunksMut<'_, P> {}
490
491#[derive(Debug)]
523pub struct ChunkMut<'a, P: PackedField>(ChunkMutInner<'a, P>);
524
525impl<'a, P: PackedField> ChunkMut<'a, P> {
526 fn new(log_chunk_size: usize, chunk_index: usize, words: &'a mut [P]) -> Self {
528 if log_chunk_size >= P::LOG_WIDTH {
529 let words_per_chunk = 1 << (log_chunk_size - P::LOG_WIDTH);
531 let chunk = &mut words[chunk_index * words_per_chunk..][..words_per_chunk];
532 return Self(ChunkMutInner::Borrowed {
533 log_len: log_chunk_size,
534 chunk,
535 });
536 }
537
538 let location = SubWordChunk::new(log_chunk_size, chunk_index);
544 let chunk = location.repack(words);
545
546 let parent = &mut words[location.word_index()];
548 Self(ChunkMutInner::Detached {
549 location,
550 chunk,
551 parent,
552 })
553 }
554
555 pub const fn chunk(&mut self) -> FieldSliceMut<'_, P> {
560 match &mut self.0 {
561 ChunkMutInner::Detached {
564 location,
565 chunk,
566 parent: _,
567 } => FieldBuffer {
568 log_len: location.log_len(),
569 words: slice::from_mut(chunk),
570 },
571 ChunkMutInner::Borrowed { log_len, chunk } => FieldBuffer {
573 log_len: *log_len,
574 words: chunk,
575 },
576 }
577 }
578}
579
580impl<P: PackedField> Drop for ChunkMut<'_, P> {
581 fn drop(&mut self) {
582 match &mut self.0 {
583 ChunkMutInner::Detached {
585 location,
586 chunk,
587 parent,
588 } => location.merge_into(parent, chunk),
589 ChunkMutInner::Borrowed { .. } => {}
591 }
592 }
593}
594
595#[derive(Debug)]
597enum ChunkMutInner<'a, P: PackedField> {
598 Detached {
600 location: SubWordChunk<P>,
602 chunk: P,
604 parent: &'a mut P,
606 },
607 Borrowed {
609 log_len: usize,
611 chunk: &'a mut [P],
613 },
614}
615
616#[cfg(test)]
617mod tests {
618 use proptest::prelude::*;
619 use rand::{SeedableRng, rngs::StdRng};
620
621 use super::*;
622 use crate::test_utils::{B128, Packed128b, random_field_buffer};
623
624 type P = Packed128b;
625 type F = B128;
626
627 #[test]
628 fn chunk() {
629 let log_len = 8;
630 let values: Vec<F> = (0..1 << log_len).map(F::new).collect();
631 let buffer = FieldBuffer::<P>::from_values(&values);
632
633 for log_chunk_size in 0..=log_len {
634 let chunk_count = 1 << (log_len - log_chunk_size);
635
636 for chunk_index in 0..chunk_count {
637 let chunk = buffer.chunk(log_chunk_size, chunk_index);
638 for i in 0..1 << log_chunk_size {
639 assert_eq!(chunk.get(i), buffer.get(chunk_index << log_chunk_size | i));
640 }
641 }
642 }
643 }
644
645 #[test]
646 #[should_panic(expected = "precondition")]
647 fn chunk_invalid_size() {
648 let log_len = 8;
649 let values: Vec<F> = (0..1 << log_len).map(F::new).collect();
650 let buffer = FieldBuffer::<P>::from_values(&values);
651 let _ = buffer.chunk(log_len + 1, 0);
652 }
653
654 #[test]
655 #[should_panic(expected = "precondition")]
656 fn chunk_invalid_index() {
657 let log_len = 8;
658 let values: Vec<F> = (0..1 << log_len).map(F::new).collect();
659 let buffer = FieldBuffer::<P>::from_values(&values);
660 let _ = buffer.chunk(4, 1 << (log_len - 4)); }
662
663 #[test]
664 fn chunk_mut() {
665 let log_len = 4;
674 let values: Vec<F> = (0..1u128 << log_len).map(F::new).collect();
675
676 for log_chunk_size in 0..=log_len {
677 let mut buffer = FieldBuffer::<P>::from_values(&values);
678 let chunk_count = 1 << (log_len - log_chunk_size);
679
680 for chunk_index in 0..chunk_count {
683 let mut guard = buffer.chunk_mut(log_chunk_size, chunk_index);
684 let mut chunk = guard.chunk();
685
686 assert_eq!(chunk.len(), 1 << log_chunk_size);
688 for i in 0..1 << log_chunk_size {
689 let old = u128::from(chunk.get(i).val());
690 chunk.set(i, F::new(old * 10));
691 }
692 }
693
694 for index in 0..1 << log_len {
696 assert_eq!(
697 buffer.get(index),
698 F::new(index as u128 * 10),
699 "log_chunk_size={log_chunk_size}, index={index}"
700 );
701 }
702 }
703
704 let mut buffer = FieldBuffer::<P>::from_values(&values);
709 {
710 let mut guard = buffer.chunk_mut(1, 1);
711 let mut chunk = guard.chunk();
712 chunk.set(0, F::new(70));
713 chunk.set(1, F::new(80));
714 }
715 assert_eq!(buffer.get(0), F::new(0));
716 assert_eq!(buffer.get(1), F::new(1));
717 assert_eq!(buffer.get(2), F::new(70));
718 assert_eq!(buffer.get(3), F::new(80));
719
720 for index in 4..16 {
722 assert_eq!(buffer.get(index), F::new(index as u128));
723 }
724
725 let mut small = FieldBuffer::<P>::from_values(&[F::new(10), F::new(20)]);
728 {
729 let mut guard = small.chunk_mut(0, 1);
730 guard.chunk().set(0, F::new(21));
731 }
732 assert_eq!(small.len(), 2);
733 assert_eq!(small.iter_scalars().collect::<Vec<_>>(), vec![F::new(10), F::new(21)]);
734
735 let mut buffer = FieldBuffer::<P>::from_values(&values);
737 {
738 let mut guard = buffer.chunk_mut(3, 1); let mut chunk = guard.chunk();
740 for i in 0..8 {
741 chunk.set(i, F::new(100 + i as u128));
742 }
743 }
744 for index in 0..8 {
745 assert_eq!(buffer.get(index), F::new(index as u128));
746 }
747 for index in 8..16 {
748 assert_eq!(buffer.get(index), F::new(100 + (index - 8) as u128));
749 }
750 }
751
752 #[test]
753 #[should_panic(expected = "precondition")]
754 fn chunk_mut_invalid_size() {
755 let mut buffer = FieldBuffer::<P>::zeros(4);
756 let _ = buffer.chunk_mut(5, 0);
758 }
759
760 #[test]
761 #[should_panic(expected = "precondition")]
762 fn chunk_mut_invalid_index() {
763 let mut buffer = FieldBuffer::<P>::zeros(4);
764 let _ = buffer.chunk_mut(2, 4);
766 }
767
768 #[test]
769 fn chunks() {
770 let values: Vec<F> = (0..16).map(F::new).collect();
771 let buffer = FieldBuffer::<P>::from_values(&values);
772
773 let chunks: Vec<_> = buffer.chunks(2).collect();
775 assert_eq!(chunks.len(), 4);
776
777 for (chunk_idx, chunk) in chunks.into_iter().enumerate() {
778 assert_eq!(chunk.len(), 4);
779 for i in 0..4 {
780 let expected = F::new((chunk_idx * 4 + i) as u128);
781 assert_eq!(chunk.get(i), expected);
782 }
783 }
784 }
785
786 #[test]
787 #[should_panic(expected = "precondition")]
788 fn chunks_invalid_size_too_large() {
789 let values: Vec<F> = (0..16).map(F::new).collect();
790 let buffer = FieldBuffer::<P>::from_values(&values);
791 let _ = buffer.chunks(5).collect::<Vec<_>>();
792 }
793
794 #[test]
795 fn chunks_below_packing_width() {
796 let values: Vec<F> = (0..16).map(F::new).collect();
800 let buffer = FieldBuffer::<P>::from_values(&values);
801
802 let chunks: Vec<_> = buffer.chunks(1).collect();
803 assert_eq!(chunks.len(), 8);
804
805 for (chunk_idx, chunk) in chunks.into_iter().enumerate() {
807 assert_eq!(chunk.len(), 2);
808 assert_eq!(chunk.get(0), F::new((chunk_idx * 2) as u128));
809 assert_eq!(chunk.get(1), F::new((chunk_idx * 2 + 1) as u128));
810 }
811
812 let singles: Vec<_> = buffer.chunks(0).collect();
814 assert_eq!(singles.len(), 16);
815 for (index, chunk) in singles.into_iter().enumerate() {
816 assert_eq!(chunk.len(), 1);
817 assert_eq!(chunk.get(0), F::new(index as u128));
818 }
819 }
820
821 #[test]
822 fn par_chunks() {
823 let values: Vec<F> = (0..16).map(F::new).collect();
824 let buffer = FieldBuffer::<P>::from_values(&values);
825
826 let chunks: Vec<_> = buffer.par_chunks(2).collect();
828 assert_eq!(chunks.len(), 4);
829
830 for (chunk_idx, chunk) in chunks.into_iter().enumerate() {
831 assert_eq!(chunk.len(), 4);
832 for i in 0..4 {
833 let expected = F::new((chunk_idx * 4 + i) as u128);
834 assert_eq!(chunk.get(i), expected);
835 }
836 }
837
838 let chunks: Vec<_> = buffer.par_chunks(1).collect();
842 assert_eq!(chunks.len(), 8);
843 for (chunk_idx, chunk) in chunks.into_iter().enumerate() {
844 assert_eq!(chunk.len(), 2);
845 for i in 0..2 {
846 let expected = F::new((chunk_idx * 2 + i) as u128);
847 assert_eq!(chunk.get(i), expected);
848 }
849 }
850
851 let chunks: Vec<_> = buffer.par_chunks(0).collect();
853 assert_eq!(chunks.len(), 16);
854 for (chunk_idx, chunk) in chunks.into_iter().enumerate() {
855 assert_eq!(chunk.len(), 1);
856 let expected = F::new(chunk_idx as u128);
857 assert_eq!(chunk.get(0), expected);
858 }
859 }
860
861 #[test]
862 fn par_chunk_scalars_ignores_dead_lanes() {
863 let values: Vec<F> = (0..2).map(F::new).collect();
867 let buffer = FieldBuffer::<P>::from_values(&values);
868
869 let chunks: Vec<Vec<F>> = buffer
871 .par_chunk_scalars(0)
872 .map(|chunk| chunk.collect())
873 .collect();
874 assert_eq!(chunks, vec![vec![values[0]], vec![values[1]]]);
875 }
876
877 #[test]
878 #[should_panic(expected = "precondition")]
879 fn par_chunks_invalid_size() {
880 let values: Vec<F> = (0..16).map(F::new).collect();
881 let buffer = FieldBuffer::<P>::from_values(&values);
882 let _ = buffer.par_chunks(5).collect::<Vec<_>>();
883 }
884
885 #[test]
886 fn chunks_mut() {
887 let mut buffer = FieldBuffer::<P>::zeros(4); let mut chunks: Vec<_> = buffer.chunks_mut(2).collect();
891 assert_eq!(chunks.len(), 4);
892
893 for (chunk_idx, chunk) in chunks.iter_mut().enumerate() {
894 for i in 0..chunk.len() {
895 chunk.set(i, F::new((chunk_idx * 10 + i) as u128));
896 }
897 }
898
899 for chunk_idx in 0..4 {
901 for i in 0..4 {
902 let expected = F::new((chunk_idx * 10 + i) as u128);
903 assert_eq!(buffer.get(chunk_idx * 4 + i), expected);
904 }
905 }
906 }
907
908 #[test]
909 #[should_panic(expected = "precondition")]
910 fn chunks_mut_invalid_size() {
911 let mut buffer = FieldBuffer::<P>::zeros(4); let _ = buffer.chunks_mut(0).collect::<Vec<_>>();
913 }
914
915 proptest! {
916 #[test]
917 fn par_chunk_scalars_partitions_the_buffer(
918 (log_len, log_chunk_size) in (0usize..=6).prop_flat_map(|n| (Just(n), 0usize..=n)),
919 ) {
920 let values: Vec<F> = (0..1u128 << log_len).map(F::new).collect();
923 let buffer = FieldBuffer::<P>::from_values(&values);
924
925 let chunks: Vec<Vec<F>> = buffer
926 .par_chunk_scalars(log_chunk_size)
927 .map(|chunk| chunk.collect())
928 .collect();
929
930 prop_assert_eq!(chunks.len(), 1 << (log_len - log_chunk_size));
932
933 for (index, scalars) in chunks.iter().enumerate() {
935 let expected: Vec<F> = buffer.chunk(log_chunk_size, index).iter_scalars().collect();
936 prop_assert_eq!(scalars, &expected);
937 }
938
939 prop_assert_eq!(chunks.concat(), values);
941 }
942
943 #[test]
944 fn chunk_mut_merges_like_direct_writes(
945 (log_len, log_chunk_size, chunk_index) in (0usize..=6)
946 .prop_flat_map(|log_len| (Just(log_len), 0usize..=log_len))
947 .prop_flat_map(|(log_len, log_chunk_size)| {
948 (Just(log_len), Just(log_chunk_size), 0usize..1 << (log_len - log_chunk_size))
949 }),
950 seed in any::<u64>(),
951 ) {
952 let mut rng = StdRng::seed_from_u64(seed);
958 let original = random_field_buffer::<P>(&mut rng, log_len);
959
960 let replacements: Vec<F> = (0..1u128 << log_chunk_size)
962 .map(|i| F::new(i * 7 + 1))
963 .collect();
964
965 let mut guarded = original.clone();
967 {
968 let mut guard = guarded.chunk_mut(log_chunk_size, chunk_index);
969 let mut chunk = guard.chunk();
970 for (i, &value) in replacements.iter().enumerate() {
971 chunk.set(i, value);
972 }
973 }
974
975 let mut direct = original;
977 for (i, &value) in replacements.iter().enumerate() {
978 direct.set(chunk_index << log_chunk_size | i, value);
979 }
980
981 prop_assert_eq!(guarded, direct);
983 }
984
985 #[test]
986 fn serial_and_parallel_chunks_agree(
987 (log_len, log_chunk_size) in (0usize..=8).prop_flat_map(|n| (Just(n), 0usize..=n)),
988 ) {
989 let values: Vec<F> = (0..1u128 << log_len).map(F::new).collect();
992 let buffer = FieldBuffer::<P>::from_values(&values);
993
994 let serial: Vec<Vec<F>> = buffer
995 .chunks(log_chunk_size)
996 .map(|chunk| chunk.iter_scalars().collect())
997 .collect();
998 let parallel: Vec<Vec<F>> = buffer
999 .par_chunks(log_chunk_size)
1000 .map(|chunk| chunk.iter_scalars().collect())
1001 .collect();
1002
1003 prop_assert_eq!(serial.len(), 1 << (log_len - log_chunk_size));
1005 prop_assert_eq!(&serial, ¶llel);
1006
1007 prop_assert_eq!(serial.concat(), values);
1009 }
1010 }
1011}