binius_math/multilinear/
fold.rs1use std::ops::Deref;
18
19use binius_compute::{Allocator, BufferData};
20use binius_field::PackedField;
21
22use super::hypercube::{self, OneCube};
23use crate::{FieldBuffer, FieldVec};
24
25pub fn fold_highest_var_inplace<P: PackedField, Data: BufferData<P>>(
38 values: &mut FieldBuffer<P, Data>,
39 scalar: P::Scalar,
40) {
41 hypercube::fold_highest_var_inplace::<OneCube, P, Data>(values, scalar);
42}
43
44pub fn fold_highest_var<A: Allocator, P: PackedField, Data: Deref<Target = [P]>>(
54 alloc: &A,
55 values: &FieldBuffer<P, Data>,
56 scalar: P::Scalar,
57) -> FieldVec<P, A> {
58 hypercube::fold_highest_var::<OneCube, A, P, Data>(alloc, values, scalar)
59}
60
61#[cfg(test)]
62mod tests {
63 use binius_compute::GlobalAllocator;
64 use binius_utils::rayon::task_size::{WorkPerItem, min_len_for_work};
65 use proptest::prelude::*;
66 use rand::prelude::*;
67
68 use super::*;
69 use crate::{
70 line::extrapolate_line,
71 test_utils::{B128, Packed128b, random_field_buffer, random_scalars},
72 };
73
74 type P = Packed128b;
75 type F = B128;
76
77 const MAX_VARS: usize = 8;
79
80 #[test]
81 fn fold_splits_above_the_task_threshold() {
82 let mut rng = StdRng::seed_from_u64(0);
83
84 let min_len = min_len_for_work(WorkPerItem::FieldMuls);
92 let n_vars = (2 * min_len * P::WIDTH).next_power_of_two().ilog2() as usize + 1;
93 let half = 1 << (n_vars - 1);
94 let original = random_field_buffer::<P>(&mut rng, n_vars);
95 let challenge = random_scalars::<F>(&mut rng, 1)[0];
96
97 let mut folded = original.clone();
98 fold_highest_var_inplace(&mut folded, challenge);
99 assert_eq!(folded.log_len(), n_vars - 1);
100
101 for i in 0..half {
103 let expected = extrapolate_line(original.get(i), original.get(i | half), challenge);
104 assert_eq!(folded.get(i), expected, "mismatch at index {i}");
105 }
106 }
107
108 #[test]
109 fn folding_a_slice_backed_buffer_matches_folding_an_owned_one() {
110 let mut rng = StdRng::seed_from_u64(0);
111
112 let original = random_field_buffer::<P>(&mut rng, 5);
121 let scalar = random_scalars::<F>(&mut rng, 1)[0];
122
123 let mut expected = original.clone();
124 fold_highest_var_inplace(&mut expected, scalar);
125
126 let mut owned = original;
127 let mut slice = owned.as_mut_view();
128 fold_highest_var_inplace(&mut slice, scalar);
129
130 assert_eq!(slice.log_len(), 4);
131 assert_eq!(slice, expected.as_mut_view());
132 }
133
134 proptest! {
135 #[test]
136 fn the_two_folds_agree(
137 n_vars in 1..=MAX_VARS,
138 seed: u64,
139 ) {
140 let mut rng = StdRng::seed_from_u64(seed);
141 let original = random_field_buffer::<P>(&mut rng, n_vars);
142 let scalar = random_scalars::<F>(&mut rng, 1)[0];
143
144 let out_of_place = fold_highest_var(&GlobalAllocator, &original, scalar);
146
147 let mut in_place = original;
149 fold_highest_var_inplace(&mut in_place, scalar);
150
151 prop_assert_eq!(out_of_place.log_len(), n_vars - 1);
152 prop_assert_eq!(out_of_place, in_place);
153 }
154 }
155}