Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
polynomial.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Nishat], commit: 94f596f8b3bbbc216f9ad7dc33253256141156b2 }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
17#include "evaluation_domain.hpp"
19#include <cstddef>
20#include <fstream>
21#include <ranges>
22namespace bb {
23
24/* Span class with a start index offset.
25 * We conceptually have a span like a_0 + a_1 x ... a_n x^n and then multiply by x^start_index.
26 * This allows more efficient representation than a fully defined span for 'islands' of zeroes. */
27template <typename Fr> struct PolynomialSpan {
34 size_t end_index() const { return start_index + size(); }
35 Fr* data() { return span.data(); }
36 size_t size() const { return span.size(); }
43 const Fr& operator[](size_t index) const
44 {
47 return span[index - start_index];
48 }
49 PolynomialSpan subspan(size_t offset, size_t length)
50 {
51 if (offset > span.size()) { // Return a null span
52 return { 0, span.subspan(span.size()) };
53 }
54 size_t new_length = std::min(length, span.size() - offset);
55 return { start_index + offset, span.subspan(offset, new_length) };
56 }
58};
59
75template <typename Fr> class Polynomial {
76 public:
77 using FF = Fr;
78 enum class DontZeroMemory { FLAG };
79
80 Polynomial(size_t size, size_t virtual_size, size_t start_index = 0);
81 // Intended just for plonk, where size == virtual_size always
83 : Polynomial(size, size) {};
84
85 // Constructor that does not initialize values, use with caution to save time.
95
96 // Explicit move constructor zeroes the source's scalar bookkeeping so that a moved-from
97 // polynomial reports size() == 0 / virtual_size() == 0, matching the default-constructed
98 // empty state. SharedShiftedVirtualZeroesArray's compiler-synthesized move copies its
99 // scalar fields while only the BackingMemory is moved out, leaving size() > 0 with
100 // data() == nullptr — UB on next use.
102 : coefficients_(std::move(other.coefficients_))
103 {
104 other.coefficients_.start_ = 0;
105 other.coefficients_.end_ = 0;
106 other.coefficients_.virtual_size_ = 0;
107 }
108
109 Polynomial(std::span<const Fr> coefficients, size_t virtual_size);
110
112 : Polynomial(coefficients, coefficients.size())
113 {}
114
119 static Polynomial shiftable(size_t virtual_size, bool masked = false)
120 {
121 auto p = Polynomial(
122 /*actual size*/ virtual_size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS);
123 if (masked) {
124 p.add_masking();
125 }
126 return p;
127 }
132 static Polynomial shiftable(size_t size, size_t virtual_size, bool masked = false)
133 {
134 auto p = Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS);
135 if (masked) {
136 p.add_masking();
137 }
138 return p;
139 }
146 {
147 return Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS, flag);
148 }
149 // Allow polynomials to be entirely reset/dormant
150 Polynomial() = default;
151
160
161 // move assignment; mirrors the move constructor above (see comment there).
163 {
164 if (this != &other) {
165 coefficients_ = std::move(other.coefficients_);
166 other.coefficients_.start_ = 0;
167 other.coefficients_.end_ = 0;
168 other.coefficients_.virtual_size_ = 0;
169 }
170 return *this;
171 }
173 ~Polynomial() = default;
174
178 Polynomial share() const;
179
184 bool is_zero() const
185 {
186 if (is_empty()) {
187 throw_or_abort("Checking is_zero on an empty Polynomial!");
188 }
189 for (size_t i = 0; i < size(); i++) {
190 if (coefficients_.data()[i] != 0) {
191 return false;
192 }
193 }
194 return true;
195 }
196
197 bool operator==(Polynomial const& rhs) const;
198
206 const Fr& get(size_t i, size_t virtual_padding = 0) const { return coefficients_.get(i, virtual_padding); };
207
208 bool is_empty() const { return coefficients_.size() == 0; }
209
216 Polynomial shifted() const;
217
225 Polynomial reverse() const;
226
241 Fr evaluate_mle(std::span<const Fr> evaluation_points, bool shift = false) const;
242
254
255 Fr evaluate(const Fr& z) const;
256
264
266
273
280
287
288 void multiply_chunk(const ThreadChunk& chunk, const Fr& scaling_factor);
289
290 std::size_t size() const { return coefficients_.size(); }
291 std::size_t virtual_size() const { return coefficients_.virtual_size(); }
292 void increase_virtual_size(const size_t size_in) { coefficients_.increase_virtual_size(size_in); };
293
294 Fr* data() { return coefficients_.data(); }
295 const Fr* data() const { return coefficients_.data(); }
296
305 Fr& at(size_t index) { return coefficients_[index]; }
306 const Fr& at(size_t index) const { return coefficients_[index]; }
307
308 const Fr& operator[](size_t i) { return get(i); }
309 const Fr& operator[](size_t i) const { return get(i); }
310
311 static Polynomial random(size_t size, size_t start_index = 0)
312 {
313 BB_BENCH_NAME("generate random polynomial");
314
317 }
318
319 static Polynomial random(size_t size, size_t virtual_size, size_t start_index)
320 {
323 size,
324 [&](size_t i) { p.coefficients_.data()[i] = Fr::random_element(); },
326 return p;
327 }
328
336
342 void shrink_end_index(const size_t new_end_index);
343
350 Polynomial full() const;
351
352 // The extents of the actual memory-backed polynomial region
353 size_t start_index() const { return coefficients_.start_; }
354 size_t end_index() const { return coefficients_.end_; }
355 bool is_shiftable() const { return start_index() == NUM_ZERO_ROWS; }
356
365 std::span<Fr> coeffs(size_t offset = 0) { return { data() + offset, data() + size() }; }
366 std::span<const Fr> coeffs(size_t offset = 0) const { return { data() + offset, data() + size() }; }
371 operator PolynomialSpan<Fr>() { return { start_index(), coeffs() }; }
372
377 operator PolynomialSpan<const Fr>() const { return { start_index(), coeffs() }; }
378
379 auto indices() const { return std::ranges::iota_view(start_index(), end_index()); }
380 auto indexed_values() { return zip_view(indices(), coeffs()); }
381 auto indexed_values() const { return zip_view(indices(), coeffs()); }
385 bool is_valid_set_index(size_t index) const { return (index >= start_index() && index < end_index()); }
389 void set_if_valid_index(size_t index, const Fr& value)
390 {
393 at(index) = value;
394 }
395 }
396
407 template <typename T> void copy_vector(const std::vector<T>& vec)
408 {
409 BB_ASSERT_LTE(vec.size(), end_index());
410 BB_ASSERT_LTE(vec.size() - start_index(), size());
411 for (size_t i = start_index(); i < vec.size(); i++) {
412 at(i) = vec[i];
413 }
414 }
415
420 {
421 for (size_t j = 0; j < NUM_MASKED_ROWS; j++) {
422 at(NUM_ZERO_ROWS + j) = Fr::random_element();
423 }
424 }
425
426 private:
427 // allocate a fresh memory pointer for backing memory
428 // DOES NOT initialize memory
429 void allocate_backing_memory(size_t size, size_t virtual_size, size_t start_index);
430
431 // The underlying memory, with a bespoke (but minimal) shared array struct that fits our needs.
432 // Namely, it supports polynomial shifts and 'virtual' zeroes past a size up until a 'virtual' size.
434};
435
446template <typename Fr>
448 std::span<const PolynomialSpan<const Fr>> sources,
449 std::span<const Fr> scalars);
450
451// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
452template <typename Fr> std::shared_ptr<Fr[]> _allocate_aligned_memory(size_t n_elements)
453{
454 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
455 return std::make_shared<Fr[]>(n_elements);
456}
457
462template <typename Fr_>
464 const SharedShiftedVirtualZeroesArray<Fr_>& coefficients,
465 bool shift)
466{
467 constexpr bool is_native = IsAnyOf<Fr_, bb::fr, grumpkin::fr>;
468 // shift ==> native
469 BB_ASSERT(!shift || is_native);
470
471 if (coefficients.size() == 0) {
472 return Fr_(0);
473 }
474
475 const size_t n = evaluation_points.size();
476 // A 0-variable MLE is the constant polynomial; return the single coefficient directly.
477 if (n == 0) {
478 BB_ASSERT_EQ(coefficients.virtual_size(), 1UL);
479 return coefficients.get(0);
480 }
481 const size_t dim = numeric::get_msb(coefficients.end_ - 1) + 1; // Round up to next power of 2
482
483 // To simplify handling of edge cases, we assume that the index space is always a power of 2
484 BB_ASSERT_EQ(coefficients.virtual_size(), 1UL << n);
485
486 // We first fold over dim rounds l = 0,...,dim-1.
487 // in round l, n_l is the size of the buffer containing the Polynomial partially evaluated
488 // at u₀,..., u_l.
489 // In round 0, this is half the size of dim
490 size_t n_l = 1UL << (dim - 1);
491
492 // temporary buffer of half the size of the Polynomial
493 auto tmp_ptr = _allocate_aligned_memory<Fr_>(n_l);
494 auto tmp = tmp_ptr.get();
495
496 size_t offset = 0;
497 if constexpr (is_native) {
498 if (shift) {
499 BB_ASSERT_EQ(coefficients.get(0), Fr_::zero());
500 offset++;
501 }
502 }
503
504 Fr_ u_l = evaluation_points[0];
505
506 // Note below: i * 2 + 1 + offset might equal virtual_size. This used to subtlely be handled by extra capacity
507 // padding (and there used to be no assert time checks, which this constant helps with).
508 const size_t ALLOW_ONE_PAST_READ = 1;
509 for (size_t i = 0; i < n_l; ++i) {
510 // curr[i] = (Fr(1) - u_l) * prev[i * 2] + u_l * prev[(i * 2) + 1];
511 tmp[i] = coefficients.get(i * 2 + offset) +
512 u_l * (coefficients.get(i * 2 + 1 + offset, ALLOW_ONE_PAST_READ) - coefficients.get(i * 2 + offset));
513 }
514
515 // partially evaluate the dim-1 remaining points
516 for (size_t l = 1; l < dim; ++l) {
517 n_l = 1UL << (dim - l - 1);
518 u_l = evaluation_points[l];
519 for (size_t i = 0; i < n_l; ++i) {
520 tmp[i] = tmp[i * 2] + u_l * (tmp[(i * 2) + 1] - tmp[i * 2]);
521 }
522 }
523 auto result = tmp[0];
524
525 // We handle the "trivial" dimensions which are full of zeros.
526 for (size_t i = dim; i < n; i++) {
527 result *= (Fr_(1) - evaluation_points[i]);
528 }
529
530 return result;
531}
532
536template <typename Fr_>
538 const SharedShiftedVirtualZeroesArray<Fr_>& coefficients)
539{
540 return _evaluate_mle(evaluation_points, coefficients, false);
541}
542
543template <typename Fr> inline std::ostream& operator<<(std::ostream& os, const Polynomial<Fr>& p)
544{
545 if (p.size() == 0) {
546 return os << "[]";
547 }
548 if (p.size() == 1) {
549 return os << "[ data " << p[0] << "]";
550 }
551 return os << "[ data\n"
552 << " " << p[0] << ",\n"
553 << " " << p[1] << ",\n"
554 << " ... ,\n"
555 << " " << p[p.size() - 2] << ",\n"
556 << " " << p[p.size() - 1] << ",\n"
557 << "]";
558}
559
560template <typename Poly, typename... Polys> auto zip_polys(Poly&& poly, Polys&&... polys)
561{
562 // Ensure all polys have the same start_index() and end_index() as poly
563 // Use fold expression to check all polys exactly match our size
564 // Wrap BB_ASSERT_EQ_RELEASE in a lambda to make it usable in a fold expression
565 auto check_indices = [&](const auto& other) {
566 BB_ASSERT_EQ(poly.start_index(), other.start_index());
567 BB_ASSERT_EQ(poly.end_index(), other.end_index());
568 };
569 // Apply the lambda to each poly in the parameter pack
570 (check_indices(polys), ...);
571 return zip_view(poly.indices(), poly.coeffs(), polys.coeffs()...);
572}
573} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_NO_WASM(expression,...)
Definition assert.hpp:180
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Polynomial(size_t size, size_t virtual_size, DontZeroMemory flag)
Polynomial shifted() const
Returns a Polynomial the left-shift of self.
size_t start_index() const
bool is_empty() const
static Polynomial random(size_t size, size_t start_index=0)
Polynomial()=default
static Polynomial shiftable(size_t size, size_t virtual_size, DontZeroMemory flag)
Overload of shiftable that leaves the backing memory uninitialized.
std::size_t virtual_size() const
Polynomial(Polynomial &&other) noexcept
bool is_shiftable() const
SharedShiftedVirtualZeroesArray< Fr > coefficients_
Polynomial(const Polynomial &other)
void increase_virtual_size(const size_t size_in)
std::span< Fr > coeffs(size_t offset=0)
Strictly iterates the defined region of the polynomial. We keep this explicit, instead of having an i...
void copy_vector(const std::vector< T > &vec)
Copy over values from a vector that is of a convertible type.
Polynomial & operator*=(const Fr &scaling_factor)
sets this = p(X) to s⋅p(X)
auto indices() const
auto indexed_values() const
void add_scaled(PolynomialSpan< const Fr > other, const Fr &scaling_factor)
adds the polynomial q(X) 'other', multiplied by a scaling factor.
Polynomial & operator=(const Polynomial &other)
void add_scaled_chunk(const ThreadChunk &chunk, PolynomialSpan< const Fr > other, const Fr &scaling_factor)
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,...
void add_masking()
Write random ZK masking values at positions {1, 2, 3} (the disabled head region after the zero row).
Polynomial(const Polynomial &other, size_t target_size)
static Polynomial shiftable(size_t size, size_t virtual_size, bool masked=false)
Utility to create a shiftable polynomial of given size and virtual size.
static Polynomial shiftable(size_t virtual_size, bool masked=false)
Utility to create a shiftable polynomial of given virtual size.
size_t end_index() const
const Fr & get(size_t i, size_t virtual_padding=0) const
Retrieves the value at the specified index.
Polynomial(size_t size)
Polynomial & operator-=(PolynomialSpan< const Fr > other)
subtracts the polynomial q(X) 'other'.
Polynomial share() const
static Polynomial random(size_t size, size_t virtual_size, size_t start_index)
Polynomial reverse() const
Returns the polynomial equal to the reverse of self.
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
bool operator==(Polynomial const &rhs) const
void shrink_end_index(const size_t new_end_index)
The end_index of the polynomial is decreased without any memory de-allocation. This is a very fast wa...
Polynomial & operator+=(PolynomialSpan< const Fr > other)
adds the polynomial q(X) 'other'.
const Fr & at(size_t index) const
~Polynomial()=default
const Fr * data() const
Polynomial(size_t size, DontZeroMemory flag)
static Polynomial create_non_parallel_zero_init(size_t size, size_t virtual_size)
A factory to construct a polynomial where parallel initialization is not possible (e....
void factor_roots(const Fr &root)
Divides p(X) by (X-r) in-place. Assumes that p(rⱼ)=0 for all j.
void allocate_backing_memory(size_t size, size_t virtual_size, size_t start_index)
void set_if_valid_index(size_t index, const Fr &value)
Like setting with at(), but allows zeroes to result in no set.
std::size_t size() const
bool is_zero() const
Check whether or not a polynomial is identically zero.
std::span< const Fr > coeffs(size_t offset=0) const
bool is_valid_set_index(size_t index) const
Is this index valid for a set? i.e. calling poly.at(index) = value.
const Fr & operator[](size_t i) const
Polynomial & operator=(Polynomial &&other) noexcept
void multiply_chunk(const ThreadChunk &chunk, const Fr &scaling_factor)
Polynomial full() const
Copys the polynomial, but with the whole address space usable. The value of the polynomial remains th...
auto indexed_values()
const Fr & operator[](size_t i)
Polynomial(std::span< const Fr > coefficients)
ssize_t offset
Definition engine.cpp:62
std::ostream & operator<<(std::ostream &os, uint256_t const &a)
Definition uint256.hpp:258
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
void factor_roots(std::span< Fr > polynomial, const Fr &root)
Divides p(X) by (X-r) in-place.
constexpr size_t ALWAYS_MULTITHREAD
Definition thread.hpp:146
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
std::shared_ptr< Fr[]> _allocate_aligned_memory(size_t n_elements)
void add_scaled_batch(Polynomial< Fr > &dst, std::span< const PolynomialSpan< const Fr > > sources, std::span< const Fr > scalars)
Fused parallel batched add: dst += sum_i scalars[i] * sources[i].
auto zip_polys(Poly &&poly, Polys &&... polys)
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
Definition thread.cpp:171
Fr_ _evaluate_mle(std::span< const Fr_ > evaluation_points, const SharedShiftedVirtualZeroesArray< Fr_ > &coefficients, bool shift)
Internal implementation to support both native and stdlib circuit field types.
Fr_ generic_evaluate_mle(std::span< const Fr_ > evaluation_points, const SharedShiftedVirtualZeroesArray< Fr_ > &coefficients)
Static exposed implementation to support both native and stdlib circuit field types.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
A shared pointer array template that represents a virtual array filled with zeros up to virtual_size_...
const T & get(size_t index, size_t virtual_padding=0) const
Retrieves the value at the specified index, or 'zero'. Optimizes for e.g. 256-bit fields by storing a...
size_t end_
The ending index of the memory-backed range.
PolynomialSpan subspan(size_t offset, size_t length)
size_t size() const
Fr & operator[](size_t index)
std::span< Fr > span
size_t end_index() const
PolynomialSpan(size_t start_index, std::span< Fr > span)
const Fr & operator[](size_t index) const
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr bool is_zero() const noexcept
void throw_or_abort(std::string const &err)