Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
polynomial_arithmetic.test.cpp
Go to the documentation of this file.
6#include "polynomial.hpp"
7#include <algorithm>
8#include <cstddef>
9#include <gtest/gtest.h>
10#include <utility>
11
12using namespace bb;
13
17TEST(polynomials, evaluate)
18{
19 auto poly1 = Polynomial<fr>(15); // non power of 2
20 auto poly2 = Polynomial<fr>(64);
21 for (size_t i = 0; i < poly1.size(); ++i) {
22 poly1.at(i) = fr::random_element();
23 poly2.at(i) = poly1[i];
24 }
25
26 auto challenge = fr::random_element();
27 auto eval1 = poly1.evaluate(challenge);
28 auto eval2 = poly2.evaluate(challenge);
29
30 EXPECT_EQ(eval1, eval2);
31}
32
33TEST(polynomials, ifft_consistency)
34{
35 constexpr size_t n = 16;
36 auto domain = evaluation_domain(n);
37 domain.compute_lookup_table();
38
39 std::array<fr, n> coeffs;
40 std::array<fr, n> values;
41 std::array<fr, n> values_copy;
42 std::array<fr, n> recovered;
43
44 for (size_t k = 0; k < n; ++k) {
45 coeffs[k] = fr::random_element();
46 values[k] = fr::zero();
47 recovered[k] = fr::zero();
48 }
49
50 // compute values[j] = sum_k coeffs[k] * ω^{j*k}
51 for (size_t j = 0; j < n; ++j) {
52 fr acc = fr::zero();
53 for (size_t k = 0; k < n; ++k) {
54 fr w = domain.root.pow(static_cast<uint64_t>(j * k));
55 acc += coeffs[k] * w;
56 }
57 values[j] = acc;
58 values_copy[j] = values[j];
59 }
60
61 // compute ifft of values, which should recover coeffs
62 polynomial_arithmetic::ifft(values.data(), recovered.data(), domain);
63
64 for (size_t k = 0; k < n; ++k) {
65 EXPECT_EQ(recovered[k], coeffs[k]); // check that ifft recovers coeffs
66 EXPECT_EQ(values[k], values_copy[k]); // check that ifft does not modify input values
67 }
68}
69
70TEST(polynomials, split_polynomial_evaluate)
71{
72 constexpr size_t n = 256;
73 std::array<fr, n> fft_transform;
75
76 for (size_t i = 0; i < n; ++i) {
77 poly[i] = fr::random_element();
78 fr::__copy(poly[i], fft_transform[i]);
79 }
80
81 constexpr size_t num_poly = 4;
82 constexpr size_t n_poly = n / num_poly;
83 fr fft_transform_[num_poly][n_poly];
84 for (size_t i = 0; i < n; ++i) {
85 fft_transform_[i / n_poly][i % n_poly] = poly[i];
86 }
87
90 { fft_transform_[0], fft_transform_[1], fft_transform_[2], fft_transform_[3] }, z, n),
91 polynomial_arithmetic::evaluate(poly.data(), z, n));
92}
93
94TEST(polynomials, linear_poly_product)
95{
96 constexpr size_t n = 64;
98
100 fr expected = 1;
101 for (size_t i = 0; i < n; ++i) {
102 roots[i] = fr::random_element();
103 expected *= (z - roots[i]);
104 }
105
106 fr dest[n + 1];
108 fr result = polynomial_arithmetic::evaluate(dest, z, n + 1);
109
110 EXPECT_EQ(result, expected);
111}
112
113template <typename FF> class PolynomialTests : public ::testing::Test {};
114
115using FieldTypes = ::testing::Types<bb::fr, grumpkin::fr>;
116
118
120{
121 using FF = TypeParam;
122 constexpr size_t n = 256;
123 auto domain = EvaluationDomain<FF>(n);
124
125 EXPECT_EQ(domain.size, 256UL);
126 EXPECT_EQ(domain.log2_size, 8UL);
127}
128
130{
131 using FF = TypeParam;
132 constexpr size_t n = 256;
133 auto domain = EvaluationDomain<FF>(n);
134
135 FF result;
136 FF expected;
137 expected = FF::one();
138 result = domain.root.pow(static_cast<uint64_t>(n));
139
140 EXPECT_EQ((result == expected), true);
141}
142
143TYPED_TEST(PolynomialTests, evaluation_domain_roots)
144{
145 using FF = TypeParam;
146 constexpr size_t n = 16;
147 EvaluationDomain<FF> domain(n);
148 domain.compute_lookup_table();
149 std::vector<FF*> root_table = domain.get_round_roots();
150 std::vector<FF*> inverse_root_table = domain.get_inverse_round_roots();
151 FF* roots = root_table[root_table.size() - 1];
152 FF* inverse_roots = inverse_root_table[inverse_root_table.size() - 1];
153 for (size_t i = 0; i < (n - 1) / 2; ++i) {
154 EXPECT_EQ(roots[i] * domain.root, roots[i + 1]);
155 EXPECT_EQ(inverse_roots[i] * domain.root_inverse, inverse_roots[i + 1]);
156 EXPECT_EQ(roots[i] * inverse_roots[i], FF::one());
157 }
158}
159
160TYPED_TEST(PolynomialTests, compute_efficient_interpolation)
161{
162 using FF = TypeParam;
163 constexpr size_t n = 250;
164 std::array<FF, n> src, poly, x;
165
166 for (size_t i = 0; i < n; ++i) {
167 poly[i] = FF::random_element();
168 }
169
170 for (size_t i = 0; i < n; ++i) {
171 x[i] = FF::random_element();
172 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
173 }
174 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
175
176 for (size_t i = 0; i < n; ++i) {
177 EXPECT_EQ(src[i], poly[i]);
178 }
179}
180// Test efficient Lagrange interpolation when interpolation points contain 0
181TYPED_TEST(PolynomialTests, compute_efficient_interpolation_domain_with_zero)
182{
183 using FF = TypeParam;
184 constexpr size_t n = 15;
185 std::array<FF, n> src, poly, x;
186
187 for (size_t i = 0; i < n; ++i) {
188 poly[i] = FF(i + 1);
189 }
190
191 for (size_t i = 0; i < n; ++i) {
192 x[i] = FF(i);
193 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
194 }
195 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
196
197 for (size_t i = 0; i < n; ++i) {
198 EXPECT_EQ(src[i], poly[i]);
199 }
200 // Test for the domain (-n/2, ..., 0, ... , n/2)
201
202 for (size_t i = 0; i < n; ++i) {
203 poly[i] = FF(i + 54);
204 }
205
206 for (size_t i = 0; i < n; ++i) {
207 x[i] = FF(i - n / 2);
208 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
209 }
210 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
211
212 for (size_t i = 0; i < n; ++i) {
213 EXPECT_EQ(src[i], poly[i]);
214 }
215
216 // Test for the domain (-n+1, ..., 0)
217
218 for (size_t i = 0; i < n; ++i) {
219 poly[i] = FF(i * i + 57);
220 }
221
222 for (size_t i = 0; i < n; ++i) {
223 x[i] = FF(i - (n - 1));
224 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
225 }
226 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
227
228 for (size_t i = 0; i < n; ++i) {
229 EXPECT_EQ(src[i], poly[i]);
230 }
231}
232
233TYPED_TEST(PolynomialTests, interpolation_constructor_single)
234{
235 using FF = TypeParam;
236
237 auto root = std::array{ FF(3) };
238 auto eval = std::array{ FF(4) };
239 Polynomial<FF> t(root, eval, 1);
240 ASSERT_EQ(t.size(), 1);
241 ASSERT_EQ(t[0], eval[0]);
242}
243
244TYPED_TEST(PolynomialTests, interpolation_constructor)
245{
246 using FF = TypeParam;
247
248 constexpr size_t N = 32;
249 std::array<FF, N> roots;
250 std::array<FF, N> evaluations;
251 for (size_t i = 0; i < N; ++i) {
252 roots[i] = FF::random_element();
253 evaluations[i] = FF::random_element();
254 }
255
256 auto roots_copy(roots);
257 auto evaluations_copy(evaluations);
258
259 Polynomial<FF> interpolated(roots, evaluations, N);
260
261 ASSERT_EQ(interpolated.size(), N);
262 ASSERT_EQ(roots, roots_copy);
263 ASSERT_EQ(evaluations, evaluations_copy);
264
265 for (size_t i = 0; i < N; ++i) {
266 FF eval = interpolated.evaluate(roots[i]);
267 ASSERT_EQ(eval, evaluations[i]);
268 }
269}
270
271TYPED_TEST(PolynomialTests, evaluate_mle_legacy)
272{
273 using FF = TypeParam;
274
275 auto test_case = [](size_t N) {
277 const size_t m = numeric::get_msb(N);
278 EXPECT_EQ(N, 1 << m);
279 Polynomial<FF> poly(N);
280 for (size_t i = 1; i < N - 1; ++i) {
281 poly.at(i) = FF::random_element(&engine);
282 }
283 poly.at(N - 1) = FF::zero();
284
285 EXPECT_TRUE(poly[0].is_zero());
286
287 // sample u = (u₀,…,uₘ₋₁)
288 std::vector<FF> u(m);
289 for (size_t l = 0; l < m; ++l) {
290 u[l] = FF::random_element(&engine);
291 }
292
293 std::vector<FF> lagrange_evals(N, FF(1));
294 for (size_t i = 0; i < N; ++i) {
295 auto& coef = lagrange_evals[i];
296 for (size_t l = 0; l < m; ++l) {
297 size_t mask = (1 << l);
298 if ((i & mask) == 0) {
299 coef *= (FF(1) - u[l]);
300 } else {
301 coef *= u[l];
302 }
303 }
304 }
305
306 // check eval by computing scalar product between
307 // lagrange evaluations and coefficients
308 FF real_eval(0);
309 for (size_t i = 0; i < N; ++i) {
310 real_eval += poly[i] * lagrange_evals[i];
311 }
312 FF computed_eval = poly.evaluate_mle(u);
313 EXPECT_EQ(real_eval, computed_eval);
314
315 // also check shifted eval
316 FF real_eval_shift(0);
317 for (size_t i = 1; i < N; ++i) {
318 real_eval_shift += poly[i] * lagrange_evals[i - 1];
319 }
320 FF computed_eval_shift = poly.evaluate_mle(u, true);
321 EXPECT_EQ(real_eval_shift, computed_eval_shift);
322 };
323 test_case(32);
324 test_case(4);
325 test_case(2);
326}
327
332TYPED_TEST(PolynomialTests, move_construct_and_assign)
333{
334 using FF = TypeParam;
335
336 // construct a poly with some arbitrary data
337 size_t num_coeffs = 64;
338 Polynomial<FF> polynomial_a(num_coeffs);
339 for (size_t i = 0; i < num_coeffs; i++) {
340 polynomial_a.at(i) = FF::random_element();
341 }
342
343 // construct a new poly from the original via the move constructor
344 Polynomial<FF> polynomial_b(std::move(polynomial_a));
345
346 // verifiy that source poly is appropriately destroyed
347 EXPECT_EQ(polynomial_a.data(), nullptr);
348
349 // construct another poly; this will also use the move constructor!
350 auto polynomial_c = std::move(polynomial_b);
351
352 // verifiy that source poly is appropriately destroyed
353 EXPECT_EQ(polynomial_b.data(), nullptr);
354
355 // define a poly with some arbitrary coefficients
356 Polynomial<FF> polynomial_d(num_coeffs);
357 for (size_t i = 0; i < num_coeffs; i++) {
358 polynomial_d.at(i) = FF::random_element();
359 }
360
361 // reset its data using move assignment
362 polynomial_d = std::move(polynomial_c);
363
364 // verifiy that source poly is appropriately destroyed
365 EXPECT_EQ(polynomial_c.data(), nullptr);
366}
367
368TYPED_TEST(PolynomialTests, default_construct_then_assign)
369{
370 using FF = TypeParam;
371
372 // construct an arbitrary but non-empty polynomial
373 size_t num_coeffs = 64;
374 Polynomial<FF> interesting_poly(num_coeffs);
375 for (size_t i = 0; i < num_coeffs; i++) {
376 interesting_poly.at(i) = FF::random_element();
377 }
378
379 // construct an empty poly via the default constructor
380 Polynomial<FF> poly;
381
382 EXPECT_EQ(poly.is_empty(), true);
383
384 // fill the empty poly using the assignment operator
385 poly = interesting_poly;
386
387 // coefficients and size should be equal in value
388 for (size_t i = 0; i < num_coeffs; ++i) {
389 EXPECT_EQ(poly[i], interesting_poly[i]);
390 }
391 EXPECT_EQ(poly.size(), interesting_poly.size());
392}
const std::vector< FF * > & get_round_roots() const
const std::vector< FF * > & get_inverse_round_roots() const
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
bool is_empty() const
Fr evaluate(const Fr &z) const
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
std::size_t size() const
numeric::RNG & engine
testing::Types< bb::fr > FieldTypes
constexpr T get_msb(const T in)
Definition get_msb.hpp:49
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:217
void ifft(Fr *coeffs, Fr *target, const EvaluationDomain< Fr > &domain)
void compute_linear_polynomial_product(const Fr *roots, Fr *dest, const size_t n)
Fr evaluate(const Fr *coeffs, const Fr &z, const size_t n)
void compute_efficient_interpolation(const Fr *src, Fr *dest, const Fr *evaluation_points, const size_t n)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
EvaluationDomain< bb::fr > evaluation_domain
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
static BB_INLINE void __copy(const field &a, field &r) noexcept
static constexpr field zero()