Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
polynomial.test.cpp
Go to the documentation of this file.
1#include <cstddef>
2#include <gtest/gtest.h>
3
6
7// Simple test/demonstration of shifted functionality
8TEST(Polynomial, Shifted)
9{
10 using FF = bb::fr;
11 using Polynomial = bb::Polynomial<FF>;
12 const size_t SIZE = 10;
13 auto poly = Polynomial::random(SIZE, /*shiftable*/ 1);
14
15 // Instantiate the shift via the shited method
16 auto poly_shifted = poly.shifted();
17
18 EXPECT_EQ(poly_shifted.size(), poly.size());
19
20 // The shift is indeed the shift
21 for (size_t i = 0; i < poly_shifted.size() - 1; ++i) {
22 EXPECT_EQ(poly_shifted.get(i), poly.get(i + 1));
23 }
24
25 // If I change the original polynomial, the shift is updated accordingly
26 poly.at(3) = 25;
27 for (size_t i = 0; i < poly_shifted.size() - 1; ++i) {
28 EXPECT_EQ(poly_shifted.get(i), poly.get(i + 1));
29 }
30}
31
32// Simple test/demonstration of reverse functionality
33TEST(Polynomial, Reversed)
34{
35 using FF = bb::fr;
36 using Polynomial = bb::Polynomial<FF>;
37 const size_t SIZE = 10;
38 const size_t VIRTUAL_SIZE = 20;
39 const size_t START_IDX = 2;
40 const size_t END_IDX = SIZE + START_IDX;
41 auto poly = Polynomial::random(SIZE, VIRTUAL_SIZE, START_IDX);
42
43 // Instantiate the shift via the reverse method
44 auto poly_reversed = poly.reverse();
45
46 EXPECT_EQ(poly_reversed.size(), poly.size());
47 EXPECT_EQ(poly_reversed.virtual_size(), poly.end_index());
48
49 // The reversed is indeed the reversed
50 for (size_t i = 0; i < END_IDX; ++i) {
51 EXPECT_EQ(poly_reversed.get(END_IDX - 1 - i), poly.get(i));
52 }
53
54 // If I change the original polynomial, the reversed polynomial is not updated
55 FF initial_value = poly.at(3);
56 poly.at(3) = 25;
57 EXPECT_EQ(poly_reversed.at(END_IDX - 4), initial_value);
58}
59
60// Simple test/demonstration of share functionality
61TEST(Polynomial, Share)
62{
63 using FF = bb::fr;
64 using Polynomial = bb::Polynomial<FF>;
65 const size_t SIZE = 10;
66 auto poly = Polynomial::random(SIZE);
67
68 // "clone" the poly via the share method
69 auto poly_clone = poly.share();
70
71 // The two are indeed equal
72 EXPECT_EQ(poly_clone, poly);
73
74 // Changing one changes the other
75 poly.at(3) = 25;
76 EXPECT_EQ(poly_clone, poly);
77
78 poly_clone.at(2) = 13;
79 EXPECT_EQ(poly_clone, poly);
80
81 // If reset the original poly, it will no longer be equal to the clone made earlier
82 // Note: if we had not made a clone, the memory from the original poly would be leaked
83 auto poly2 = Polynomial::random(SIZE);
84 poly = poly2.share();
85
86 EXPECT_NE(poly_clone, poly);
87}
88
89// Simple test/demonstration of various edge conditions
90TEST(Polynomial, Indices)
91{
92 auto poly = bb::Polynomial<bb::fr>::random(100, /*offset*/ 1);
93 EXPECT_TRUE(poly.is_shiftable());
94 EXPECT_EQ((*poly.indices().begin()), poly.start_index());
95 EXPECT_EQ(std::get<0>(*poly.indexed_values().begin()), poly.start_index());
96 EXPECT_EQ(std::get<1>(*poly.indexed_values().begin()), poly[poly.start_index()]);
97}
98
99TEST(Polynomial, AddScaledVectorizedMatchesScalar)
100{
101 using FF = bb::fr;
102 using Poly = bb::Polynomial<FF>;
103
104 // self: logical indices [2, 32); other: logical indices [5, 18).
105 // Overlap region is other's range [5, 18) — 13 elements, not a multiple
106 // of 5 (exercises both the bulk and the tail of vectorized_for).
107 constexpr size_t SELF_SIZE = 30;
108 constexpr size_t SELF_VSIZE = 32;
109 constexpr size_t SELF_START = 2;
110 constexpr size_t OTHER_SIZE = 13;
111 constexpr size_t OTHER_VSIZE = 32;
112 constexpr size_t OTHER_START = 5;
113
114 Poly self(SELF_SIZE, SELF_VSIZE, SELF_START);
115 Poly other(OTHER_SIZE, OTHER_VSIZE, OTHER_START);
116 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
117 self.at(i) = FF((i * 11) + 1);
118 }
119 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
120 other.at(i) = FF((i * 17) + 3);
121 }
122 Poly self_ref = self;
123 FF scalar = FF(7);
124
125 self.add_scaled(other, scalar);
126
127 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
128 self_ref.at(i) = self_ref.at(i) + scalar * other.at(i);
129 }
130 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
131 EXPECT_EQ(self.at(i), self_ref.at(i)) << "i=" << i;
132 }
133}
134
135// Parity tests for the vectorized operator+=, operator-=, operator*=
136// migrations. Each pairs an in-place operation against an iterated scalar
137// reference over a span whose length is deliberately not a multiple of
138// VECTOR_FIELD_WIDTH=5, so both bulk and tail paths fire.
139TEST(Polynomial, AddAssignVectorizedMatchesScalar)
140{
141 using FF = bb::fr;
142 using Poly = bb::Polynomial<FF>;
143 constexpr size_t SELF_SIZE = 30;
144 constexpr size_t SELF_VSIZE = 32;
145 constexpr size_t SELF_START = 2;
146 constexpr size_t OTHER_SIZE = 13;
147 constexpr size_t OTHER_VSIZE = 32;
148 constexpr size_t OTHER_START = 5;
149
150 Poly self(SELF_SIZE, SELF_VSIZE, SELF_START);
151 Poly other(OTHER_SIZE, OTHER_VSIZE, OTHER_START);
152 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
153 self.at(i) = FF((i * 11) + 1);
154 }
155 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
156 other.at(i) = FF((i * 17) + 3);
157 }
158 Poly self_ref = self;
159
160 self += other;
161
162 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
163 self_ref.at(i) = self_ref.at(i) + other.at(i);
164 }
165 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
166 EXPECT_EQ(self.at(i), self_ref.at(i)) << "i=" << i;
167 }
168}
169
170TEST(Polynomial, SubtractAssignVectorizedMatchesScalar)
171{
172 using FF = bb::fr;
173 using Poly = bb::Polynomial<FF>;
174 constexpr size_t SELF_SIZE = 30;
175 constexpr size_t SELF_VSIZE = 32;
176 constexpr size_t SELF_START = 2;
177 constexpr size_t OTHER_SIZE = 13;
178 constexpr size_t OTHER_VSIZE = 32;
179 constexpr size_t OTHER_START = 5;
180
181 Poly self(SELF_SIZE, SELF_VSIZE, SELF_START);
182 Poly other(OTHER_SIZE, OTHER_VSIZE, OTHER_START);
183 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
184 self.at(i) = FF((i * 11) + 1);
185 }
186 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
187 other.at(i) = FF((i * 17) + 3);
188 }
189 Poly self_ref = self;
190
191 self -= other;
192
193 for (size_t i = OTHER_START; i < OTHER_START + OTHER_SIZE; ++i) {
194 self_ref.at(i) = self_ref.at(i) - other.at(i);
195 }
196 for (size_t i = SELF_START; i < SELF_START + SELF_SIZE; ++i) {
197 EXPECT_EQ(self.at(i), self_ref.at(i)) << "i=" << i;
198 }
199}
200
201TEST(Polynomial, MultiplyAssignVectorizedMatchesScalar)
202{
203 using FF = bb::fr;
204 using Poly = bb::Polynomial<FF>;
205 // 17 elements covers a full 5-wide block plus a non-trivial tail (12 → 2),
206 // and the start offset keeps the buffer's contiguous range unaligned.
207 constexpr size_t SIZE = 17;
208 constexpr size_t VSIZE = 24;
209 constexpr size_t START = 3;
210
211 Poly p(SIZE, VSIZE, START);
212 for (size_t i = START; i < START + SIZE; ++i) {
213 p.at(i) = FF((i * 13) + 5);
214 }
215 Poly p_ref = p;
216 FF scalar = FF(11);
217
218 p *= scalar;
219
220 for (size_t i = START; i < START + SIZE; ++i) {
221 p_ref.at(i) = p_ref.at(i) * scalar;
222 }
223 for (size_t i = START; i < START + SIZE; ++i) {
224 EXPECT_EQ(p.at(i), p_ref.at(i)) << "i=" << i;
225 }
226}
227
228// evaluate_mle on a 0-variable MLE returns the constant coefficient.
229TEST(Polynomial, EvaluateMleSingleCoefficientEmptyPoints)
230{
231 using FF = bb::fr;
232 bb::Polynomial<FF> poly(1);
233 poly.at(0) = FF(42);
234 std::vector<FF> u; // empty — zero-variable MLE
235 EXPECT_EQ(poly.evaluate_mle(u), FF(42));
236}
237
238// evaluate_mle({}) must be paired with virtual_size() == 1, mirroring the
239// `virtual_size == 1 << n` precondition enforced for n > 0. A multi-coefficient polynomial
240// evaluated at zero points is a dimension mismatch, not a meaningful constant.
241TEST(Polynomial, EvaluateMleEmptyPointsRejectsMultiCoefficient)
242{
243 GTEST_FLAG_SET(death_test_style, "threadsafe");
244 using FF = bb::fr;
245 bb::Polynomial<FF> poly(2); // virtual_size == 2
246 poly.at(0) = FF(1);
247 poly.at(1) = FF(2);
248 std::vector<FF> u; // empty — caller incorrectly treats poly as 0-variable
249 ASSERT_THROW_OR_ABORT(poly.evaluate_mle(u), ".*");
250}
251
252// full() materializes a shifted polynomial with virtual zeros on both sides into a dense 0..virtual_size buffer;
253// coefficient values outside the original [start_index, end_index) must be zero.
254TEST(Polynomial, FullPreservesCoefficients)
255{
256 using FF = bb::fr;
257 const size_t virtual_size = 16;
258 const size_t start = 3;
259 const size_t size = 5;
260 auto poly = bb::Polynomial<FF>(size, virtual_size, start);
261 for (size_t i = 0; i < size; ++i) {
262 poly.at(start + i) = FF(i + 100);
263 }
264 auto full = poly.full();
265 EXPECT_EQ(full.start_index(), 0UL);
266 EXPECT_EQ(full.end_index(), virtual_size);
267 for (size_t i = 0; i < virtual_size; ++i) {
268 const bool in_backed_range = i >= start && i < start + size;
269 const FF expected = in_backed_range ? FF(i - start + 100) : FF(0);
270 EXPECT_EQ(full[i], expected) << "mismatch at index " << i;
271 }
272}
273
274#ifndef NDEBUG
275// Only run in an assert-enabled test suite.
276TEST(Polynomial, AddScaledEdgeConditions)
277{
278 // Suppress warnings about fork(), we're OK with the edge cases.
279 GTEST_FLAG_SET(death_test_style, "threadsafe");
280 using FF = bb::fr;
281 auto test_subset_good = []() {
282 // Contained within poly
283 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
284 poly.add_scaled(bb::Polynomial<FF>::random(4, /*start index*/ 1), 1);
285 };
286 ASSERT_NO_FATAL_FAILURE(test_subset_good());
287 auto test_subset_bad1 = []() {
288 // Not contained within poly
289 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 1);
290 poly.add_scaled(bb::Polynomial<FF>::random(4, /*start index*/ 0), 1);
291 };
292 ASSERT_THROW_OR_ABORT(test_subset_bad1(), ".*start_index.*other.start_index.*");
293 auto test_subset_bad2 = []() {
294 // Not contained within poly
295 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
296 poly.add_scaled(bb::Polynomial<FF>::random(5, /*start index*/ 0), 1);
297 };
298 ASSERT_THROW_OR_ABORT(test_subset_bad2(), ".*end_index.*other.end_index.*");
299}
300
301TEST(Polynomial, OperatorAddEdgeConditions)
302{
303 // Suppress warnings about fork(), we're OK with the edge cases.
304 GTEST_FLAG_SET(death_test_style, "threadsafe");
305 using FF = bb::fr;
306 auto test_subset_good = []() {
307 // Contained within poly
308 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
309 poly += bb::Polynomial<FF>::random(4, /*start index*/ 1);
310 };
311 ASSERT_NO_FATAL_FAILURE(test_subset_good());
312 auto test_subset_bad1 = []() {
313 // Not contained within poly
314 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 1);
315 poly += bb::Polynomial<FF>::random(4, /*start index*/ 0);
316 };
317 ASSERT_THROW_OR_ABORT(test_subset_bad1(), ".*start_index.*other.start_index.*");
318 auto test_subset_bad2 = []() {
319 // Not contained within poly
320 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
321 poly += bb::Polynomial<FF>::random(5, /*start index*/ 0);
322 };
323 ASSERT_THROW_OR_ABORT(test_subset_bad2(), ".*end_index.*other.end_index.*");
324}
325
326TEST(Polynomial, OperatorSubtractEdgeConditions)
327{
328 // Suppress warnings about fork(), we're OK with the edge cases.
329 GTEST_FLAG_SET(death_test_style, "threadsafe");
330 using FF = bb::fr;
331 auto test_subset_good = []() {
332 // Contained within poly
333 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
334 poly -= bb::Polynomial<FF>::random(4, /*start index*/ 1);
335 };
336 ASSERT_NO_FATAL_FAILURE(test_subset_good());
337 auto test_subset_bad1 = []() {
338 // Not contained within poly
339 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 1);
340 poly -= bb::Polynomial<FF>::random(4, /*start index*/ 0);
341 };
342 ASSERT_THROW_OR_ABORT(test_subset_bad1(), ".*start_index.*other.start_index.*");
343 auto test_subset_bad2 = []() {
344 // Not contained within poly
345 auto poly = bb::Polynomial<FF>::random(4, /*start index*/ 0);
346 poly -= bb::Polynomial<FF>::random(5, /*start index*/ 0);
347 };
348 ASSERT_THROW_OR_ABORT(test_subset_bad2(), ".*end_index.*other.end_index.*");
349}
350
351// Makes a vector fully of the virtual_size aka degree + 1
352TEST(Polynomial, Full)
353{
354 // Suppress warnings about fork(), we're OK with the edge cases.
355 GTEST_FLAG_SET(death_test_style, "threadsafe");
356 using FF = bb::fr;
357 size_t degree_plus_1 = 10;
358 auto full_good = [=]() {
359 auto poly = bb::Polynomial<FF>::random(1, degree_plus_1, /*start index*/ degree_plus_1 - 1);
360 poly = poly.full();
361 poly -= bb::Polynomial<FF>::random(degree_plus_1, /*start index*/ 0);
362 };
363 ASSERT_NO_FATAL_FAILURE(full_good());
364 auto no_full_bad = [=]() {
365 auto poly = bb::Polynomial<FF>::random(1, degree_plus_1, /*start index*/ degree_plus_1 - 1);
366 poly -= bb::Polynomial<FF>::random(degree_plus_1, /*start index*/ 0);
367 };
368 ASSERT_THROW_OR_ABORT(no_full_bad(), ".*start_index.*other.start_index.*");
369}
370
371#endif
372
373// Polynomial::random asserts when start_index > size (would underflow the subtraction).
374TEST(Polynomial, RandomStartIndexExceedsSizeAsserts)
375{
376 GTEST_FLAG_SET(death_test_style, "threadsafe");
377 using FF = bb::fr;
378 ASSERT_THROW_OR_ABORT(bb::Polynomial<FF>::random(/*size=*/4, /*start_index=*/10), ".*");
379}
380
381// shrink_end_index asserts when the new end is below start_index (would underflow size()).
382TEST(Polynomial, ShrinkEndIndexBelowStartIndexAsserts)
383{
384 GTEST_FLAG_SET(death_test_style, "threadsafe");
385 using FF = bb::fr;
386 // Polynomial with start_index=2, end_index=10.
387 auto poly = bb::Polynomial<FF>(/*size=*/8, /*virtual_size=*/16, /*start_index=*/2);
388 ASSERT_THROW_OR_ABORT(poly.shrink_end_index(1), ".*");
389}
#define ASSERT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:222
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
static Polynomial random(size_t size, size_t start_index=0)
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[...
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
TEST(Polynomial, Shifted)