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
21#include "evaluation_domain.hpp"
23#include <array>
24#include <cstddef>
25#include <fstream>
26#include <ranges>
27#include <vector>
28namespace bb {
29
30/* Span class with a start index offset.
31 * We conceptually have a span like a_0 + a_1 x ... a_n x^n and then multiply by x^start_index.
32 * This allows more efficient representation than a fully defined span for 'islands' of zeroes. */
33template <typename Fr> struct PolynomialSpan {
40 size_t end_index() const { return start_index + size(); }
41 Fr* data() { return span.data(); }
42 size_t size() const { return span.size(); }
49 const Fr& operator[](size_t index) const
50 {
53 return span[index - start_index];
54 }
55
56 // Read-only token-indexed accessors, mirroring Polynomial's scalar/vector
57 // read overloads. No write proxies: a PolynomialSpan<const Fr> is
58 // read-only, and the non-const PolynomialSpan<Fr> use sites we care
59 // about all pass through the implicit conversion to
60 // PolynomialSpan<const Fr> before reaching the kernel.
62 element_type operator[](ScalarIndex ctx) const { return (*this)[ctx.i]; }
63 template <size_t N, typename U = element_type> VectorField<typename U::Params> operator[](VectorIndex<N> ctx) const
64 {
65 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
66 return VectorField<typename U::Params>::gather(this->span.data(), ctx.idx, this->start_index);
67 }
68
69 // Contiguous vector read: lane L = this->span[base - start_index + L].
70 // Routes through the linear-memory `VectorField(const Field*)` ctor —
71 // the loop abstraction's primary load primitive, which uses SIMD
72 // shuffles to AoS→interleaved transpose without the per-lane scalar
73 // loads `gather` does.
74 template <size_t N, typename U = element_type>
76 {
77 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
78 return VectorField<typename U::Params>(this->span.data() + (ctx.base - this->start_index));
79 }
80
81 PolynomialSpan subspan(size_t offset, size_t length)
82 {
83 if (offset > span.size()) { // Return a null span
84 return { 0, span.subspan(span.size()) };
85 }
86 size_t new_length = std::min(length, span.size() - offset);
87 return { start_index + offset, span.subspan(offset, new_length) };
88 }
90};
91
107// Hidden-friend binary-operator wall stamped on both vector proxy types
108// (VectorWriteProxyT and ContiguousVectorWriteProxyT) so kernels written as
109// p[ctx] = p[ctx] + other[ctx] * scalar;
110// resolve every operand combination (proxy×proxy, proxy×Value+reversed,
111// proxy×Scalar+reversed) through ADL on the proxy types. Defined as a
112// macro because there is no shared base — the two proxies materialise the
113// `Value` differently (gather vs. linear-memory ctor) and live in distinct
114// scopes inside the Polynomial template.
115//
116// Marked [[gnu::always_inline]] on every line: under -Oz V8/TurboFan leaves
117// unmarked proxy ops as standalone WASM functions, defeating the linear-
118// memory / gather primitives and adding ~0.7ms per 65k-field pass.
119#define BB_VECTOR_PROXY_BINARY_OPS(Proxy, Value, Scalar) \
120 [[gnu::always_inline]] friend Value operator+(const Proxy& a, const Proxy& b) noexcept \
121 { \
122 return Value(a) + Value(b); \
123 } \
124 [[gnu::always_inline]] friend Value operator-(const Proxy& a, const Proxy& b) noexcept \
125 { \
126 return Value(a) - Value(b); \
127 } \
128 [[gnu::always_inline]] friend Value operator*(const Proxy& a, const Proxy& b) noexcept \
129 { \
130 return Value(a) * Value(b); \
131 } \
132 [[gnu::always_inline]] friend Value operator+(const Proxy& a, const Value& b) noexcept \
133 { \
134 return Value(a) + b; \
135 } \
136 [[gnu::always_inline]] friend Value operator+(const Value& a, const Proxy& b) noexcept \
137 { \
138 return a + Value(b); \
139 } \
140 [[gnu::always_inline]] friend Value operator-(const Proxy& a, const Value& b) noexcept \
141 { \
142 return Value(a) - b; \
143 } \
144 [[gnu::always_inline]] friend Value operator-(const Value& a, const Proxy& b) noexcept \
145 { \
146 return a - Value(b); \
147 } \
148 [[gnu::always_inline]] friend Value operator*(const Proxy& a, const Value& b) noexcept \
149 { \
150 return Value(a) * b; \
151 } \
152 [[gnu::always_inline]] friend Value operator*(const Value& a, const Proxy& b) noexcept \
153 { \
154 return a * Value(b); \
155 } \
156 [[gnu::always_inline]] friend Value operator+(const Proxy& a, const Scalar& s) noexcept \
157 { \
158 return Value(a) + s; \
159 } \
160 [[gnu::always_inline]] friend Value operator+(const Scalar& s, const Proxy& a) noexcept \
161 { \
162 return s + Value(a); \
163 } \
164 [[gnu::always_inline]] friend Value operator-(const Proxy& a, const Scalar& s) noexcept \
165 { \
166 return Value(a) - s; \
167 } \
168 [[gnu::always_inline]] friend Value operator-(const Scalar& s, const Proxy& a) noexcept \
169 { \
170 return s - Value(a); \
171 } \
172 [[gnu::always_inline]] friend Value operator*(const Proxy& a, const Scalar& s) noexcept \
173 { \
174 return Value(a) * s; \
175 } \
176 [[gnu::always_inline]] friend Value operator*(const Scalar& s, const Proxy& a) noexcept \
177 { \
178 return s * Value(a); \
179 }
180
181template <typename Fr> class Polynomial {
182 public:
183 using FF = Fr;
184 enum class DontZeroMemory { FLAG };
185
186 Polynomial(size_t size, size_t virtual_size, size_t start_index = 0);
187 // Intended just for plonk, where size == virtual_size always
189 : Polynomial(size, size) {};
190
191 // Constructor that does not initialize values, use with caution to save time.
199 Polynomial(const Polynomial& other);
200 Polynomial(const Polynomial& other, size_t target_size);
201
202 // Explicit move constructor zeroes the source's scalar bookkeeping so that a moved-from
203 // polynomial reports size() == 0 / virtual_size() == 0, matching the default-constructed
204 // empty state. SharedShiftedVirtualZeroesArray's compiler-synthesized move copies its
205 // scalar fields while only the BackingMemory is moved out, leaving size() > 0 with
206 // data() == nullptr — UB on next use.
207 Polynomial(Polynomial&& other) noexcept
208 : coefficients_(std::move(other.coefficients_))
209 {
210 other.coefficients_.start_ = 0;
211 other.coefficients_.end_ = 0;
212 other.coefficients_.virtual_size_ = 0;
213 }
214
215 Polynomial(std::span<const Fr> coefficients, size_t virtual_size);
216
218 : Polynomial(coefficients, coefficients.size())
219 {}
220
225 static Polynomial shiftable(size_t virtual_size, bool masked = false)
226 {
227 auto p = Polynomial(
228 /*actual size*/ virtual_size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS);
229 if (masked) {
230 p.add_masking();
231 }
232 return p;
233 }
238 static Polynomial shiftable(size_t size, size_t virtual_size, bool masked = false)
239 {
240 auto p = Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS);
241 if (masked) {
242 p.add_masking();
243 }
244 return p;
245 }
252 {
253 return Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS, flag);
254 }
255 // Allow polynomials to be entirely reset/dormant
256 Polynomial() = default;
257
266
267 // move assignment; mirrors the move constructor above (see comment there).
268 Polynomial& operator=(Polynomial&& other) noexcept
269 {
270 if (this != &other) {
271 coefficients_ = std::move(other.coefficients_);
272 other.coefficients_.start_ = 0;
273 other.coefficients_.end_ = 0;
274 other.coefficients_.virtual_size_ = 0;
275 }
276 return *this;
277 }
279 ~Polynomial() = default;
280
284 Polynomial share() const;
285
290 bool is_zero() const
291 {
292 if (is_empty()) {
293 throw_or_abort("Checking is_zero on an empty Polynomial!");
294 }
295 for (size_t i = 0; i < size(); i++) {
296 if (coefficients_.data()[i] != 0) {
297 return false;
298 }
299 }
300 return true;
301 }
302
303 bool operator==(Polynomial const& rhs) const;
304
312 const Fr& get(size_t i, size_t virtual_padding = 0) const { return coefficients_.get(i, virtual_padding); };
313
314 bool is_empty() const { return coefficients_.size() == 0; }
315
322 Polynomial shifted() const;
323
331 Polynomial reverse() const;
332
347 Fr evaluate_mle(std::span<const Fr> evaluation_points, bool shift = false) const;
348
360
361 Fr evaluate(const Fr& z) const;
362
369 [[gnu::always_inline]] void add_scaled(PolynomialSpan<const Fr> other, const Fr& scaling_factor);
370
371 [[gnu::always_inline]] void add_scaled_chunk(const ThreadChunk& chunk,
373 const Fr& scaling_factor);
374
380 [[gnu::always_inline]] Polynomial& operator+=(PolynomialSpan<const Fr> other);
381
382 [[gnu::always_inline]] void add_chunk(const ThreadChunk& chunk, PolynomialSpan<const Fr> other);
383
389 [[gnu::always_inline]] Polynomial& operator-=(PolynomialSpan<const Fr> other);
390
391 [[gnu::always_inline]] void subtract_chunk(const ThreadChunk& chunk, PolynomialSpan<const Fr> other);
392
398 [[gnu::always_inline]] Polynomial& operator*=(const Fr& scaling_factor);
399
400 [[gnu::always_inline]] void multiply_chunk(const ThreadChunk& chunk, const Fr& scaling_factor);
401
402 std::size_t size() const { return coefficients_.size(); }
403 std::size_t virtual_size() const { return coefficients_.virtual_size(); }
404 void increase_virtual_size(const size_t size_in) { coefficients_.increase_virtual_size(size_in); };
405
406 Fr* data() { return coefficients_.data(); }
407 const Fr* data() const { return coefficients_.data(); }
408
417 Fr& at(size_t index) { return coefficients_[index]; }
418 const Fr& at(size_t index) const { return coefficients_[index]; }
419
420 const Fr& operator[](size_t i) { return get(i); }
421 const Fr& operator[](size_t i) const { return get(i); }
422
423 // ---- Index-token overloads (see vectorized_for.hpp) ----
424 //
425 // Gated on Fr exposing a Params typedef (true for native field<Params>,
426 // not for stdlib::field_t). This avoids pulling VectorField into circuit
427 // instantiations and also dodges an include cycle risk for stdlib types.
428
429 // Write-proxies: returned by non-const operator[] on index tokens so
430 // assignment routes through at() (the correct mutable accessor that
431 // respects the virtual/shifted buffer layout). Each proxy also implicitly
432 // converts back to the read type so a kernel written against a mutable
433 // Polynomial can read and write through the same token:
434 // p[ctx] = p[ctx] + p[ctx];
435 // The assignment picks operator= and the RHS uses of p[ctx] convert to
436 // the value type.
439 size_t i;
441 {
442 self->at(i) = v;
443 return *this;
444 }
445 operator Fr() const { return self->at(i); }
446
447 // Hidden-friend binary operators that take the proxy directly.
448 //
449 // Fr's binary operators are class members and are not candidates
450 // when the LHS is a proxy. Defining free operators that accept the
451 // proxy type explicitly gives overload resolution a candidate
452 // reachable via ADL from proxy arguments. Each forwards by reading
453 // the Fr value through `at()` (via the operator Fr() conversion)
454 // and delegating to Fr's member operator.
455 friend Fr operator+(const ScalarWriteProxy& a, const ScalarWriteProxy& b) noexcept { return Fr(a) + Fr(b); }
456 friend Fr operator-(const ScalarWriteProxy& a, const ScalarWriteProxy& b) noexcept { return Fr(a) - Fr(b); }
457 friend Fr operator*(const ScalarWriteProxy& a, const ScalarWriteProxy& b) noexcept { return Fr(a) * Fr(b); }
458 friend Fr operator+(const ScalarWriteProxy& a, const Fr& b) noexcept { return Fr(a) + b; }
459 friend Fr operator+(const Fr& a, const ScalarWriteProxy& b) noexcept { return a + Fr(b); }
460 friend Fr operator-(const ScalarWriteProxy& a, const Fr& b) noexcept { return Fr(a) - b; }
461 friend Fr operator-(const Fr& a, const ScalarWriteProxy& b) noexcept { return a - Fr(b); }
462 friend Fr operator*(const ScalarWriteProxy& a, const Fr& b) noexcept { return Fr(a) * b; }
463 friend Fr operator*(const Fr& a, const ScalarWriteProxy& b) noexcept { return a * Fr(b); }
464 };
465 // Write-proxy for VectorIndex<5> on a mutable Polynomial. Sparse / gather
466 // counterpart to ContiguousVectorWriteProxyT — assignment routes through
467 // `VectorField::scatter` (per-lane scalar stores at random indices), and
468 // the implicit conversion through `VectorField::gather`. Same inline
469 // discipline as the contiguous proxy (see comment above the macro).
470 template <typename Params_> struct VectorWriteProxyT {
472 std::array<size_t, 5> idx;
473 [[gnu::always_inline]] VectorWriteProxyT& operator=(const VectorField<Params_>& v)
474 {
475 v.scatter(self->data(), idx, self->start_index());
476 return *this;
477 }
478 [[gnu::always_inline]] operator VectorField<Params_>() const
479 {
481 }
482
484 };
485
486 // Write-proxy for ContiguousVectorIndex<5> on a mutable Polynomial.
487 // Holds {self, base} and assignment stores via VectorField::store_to,
488 // which uses the SIMD-shuffle path for AoS→interleaved write-back
489 // without the per-lane scalar stores `scatter` does. Also implicitly
490 // converts to VectorField via the linear-memory ctor so a kernel can
491 // read and write through the same token.
492 template <typename Params_> struct ContiguousVectorWriteProxyT {
494 size_t base;
496 {
497 v.store_to(self->data() + (base - self->start_index()));
498 return *this;
499 }
500 [[gnu::always_inline]] operator VectorField<Params_>() const
501 {
503 }
504
506 };
507
508 // Scalar read via token: delegates to the size_t overload.
509 Fr operator[](ScalarIndex ctx) const { return (*this)[ctx.i]; }
510
511 // Vector read via token: lane L = (*this)[ctx.idx[L]].
512 // Templated on U = Fr so that Fr::Params is only resolved lazily when
513 // this member is instantiated — avoids hard errors when Polynomial is
514 // instantiated with a type (e.g. stdlib::field_t) that has no Params.
515 template <size_t N, typename U = Fr> VectorField<typename U::Params> operator[](VectorIndex<N> ctx) const
516 {
517 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
518 return VectorField<typename U::Params>::gather(this->data(), ctx.idx, this->start_index());
519 }
520
521 // Contiguous vector read via token: lane L = (*this)[ctx.base + L].
522 // Routes through the linear-memory `VectorField(const Field*)` ctor.
523 template <size_t N, typename U = Fr>
525 {
526 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
527 return VectorField<typename U::Params>(this->data() + (ctx.base - this->start_index()));
528 }
529
530 // Non-const LHS overloads return write-proxies.
533 {
534 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
535 return VectorWriteProxyT<typename U::Params>{ this, ctx.idx };
536 }
537 template <size_t N, typename U = Fr>
539 {
540 static_assert(N == VECTOR_FIELD_WIDTH, "VectorField is fixed-width; N must equal VECTOR_FIELD_WIDTH");
542 }
543
544 static Polynomial random(size_t size, size_t start_index = 0)
545 {
546 BB_BENCH_NAME("generate random polynomial");
549 }
550
551 static Polynomial random(size_t size, size_t virtual_size, size_t start_index)
552 {
555 size,
556 [&](size_t i) { p.coefficients_.data()[i] = Fr::random_element(); },
558 return p;
559 }
560
568
574 void shrink_end_index(const size_t new_end_index);
575
582 Polynomial full() const;
583
584 // The extents of the actual memory-backed polynomial region
585 size_t start_index() const { return coefficients_.start_; }
586 size_t end_index() const { return coefficients_.end_; }
587 bool is_shiftable() const { return start_index() == NUM_ZERO_ROWS; }
588
597 std::span<Fr> coeffs(size_t offset = 0) { return { data() + offset, data() + size() }; }
598 std::span<const Fr> coeffs(size_t offset = 0) const { return { data() + offset, data() + size() }; }
603 operator PolynomialSpan<Fr>() { return { start_index(), coeffs() }; }
604
609 operator PolynomialSpan<const Fr>() const { return { start_index(), coeffs() }; }
610
611 auto indices() const { return std::ranges::iota_view(start_index(), end_index()); }
612 auto indexed_values() { return zip_view(indices(), coeffs()); }
613 auto indexed_values() const { return zip_view(indices(), coeffs()); }
617 bool is_valid_set_index(size_t index) const { return (index >= start_index() && index < end_index()); }
621 void set_if_valid_index(size_t index, const Fr& value)
622 {
625 at(index) = value;
626 }
627 }
628
639 template <typename T> void copy_vector(const std::vector<T>& vec)
640 {
641 BB_ASSERT_LTE(vec.size(), end_index());
642 BB_ASSERT_LTE(vec.size() - start_index(), size());
643 for (size_t i = start_index(); i < vec.size(); i++) {
644 at(i) = vec[i];
645 }
646 }
647
652 {
653 for (size_t j = 0; j < NUM_MASKED_ROWS; j++) {
654 at(NUM_ZERO_ROWS + j) = Fr::random_element();
655 }
656 }
657
658 private:
659 // allocate a fresh memory pointer for backing memory
660 // DOES NOT initialize memory
661 void allocate_backing_memory(size_t size, size_t virtual_size, size_t start_index);
662
663 // The underlying memory, with a bespoke (but minimal) shared array struct that fits our needs.
664 // Namely, it supports polynomial shifts and 'virtual' zeroes past a size up until a 'virtual' size.
666};
667
668// Inline definitions for `add_scaled` / `add_scaled_chunk`.
669// Bodies live in the header so that callers (notably the V8/TurboFan-jitted
670// WASM code) can inline them and recover the per-iter `vector_field_raw`
671// ceiling — the polynomial.cpp definition added a real WASM function-call
672// boundary that V8 was not eliding.
673
674template <typename Fr>
675[[gnu::always_inline]] inline void Polynomial<Fr>::add_scaled_chunk(const ThreadChunk& chunk,
677 const Fr& scaling_factor)
678{
679 auto range = chunk.range(other.size());
680 if (range.empty()) {
681 return;
682 }
683 const size_t lo = *range.begin() + other.start_index;
684 const size_t hi = lo + range.size();
685
686 // Loop abstraction: `vectorized_for<VECTOR_FIELD_WIDTH>` emits ContiguousVectorIndex<5>
687 // for the bulk pass and ScalarIndex for the tail. The kernel reads/writes
688 // through Polynomial / PolynomialSpan's index-token operator[], which
689 // route through the linear-memory `VectorField(const Field*)` ctor and
690 // its matching `store_to` write method (no gather/scatter). The proxy
691 // ops are [[gnu::always_inline]] so the abstraction lowers to the same
692 // SIMD-shuffle pack/op/unpack sequence as a hand-written tight loop.
693 //
694 // Scaling is wrapped in a `Broadcast<Fr>` so the same `scaling[ctx]`
695 // expression yields a scalar Fr in the tail path and a broadcast
696 // VectorField in the bulk path; both forms are materialized once in
697 // the Broadcast constructor.
698 const Broadcast<Fr> scaling(scaling_factor);
699 vectorized_for<VECTOR_FIELD_WIDTH, Fr>(
700 lo, hi, [&](auto ctx) { (*this)[ctx] = (*this)[ctx] + other[ctx] * scaling[ctx]; });
701}
702
703template <typename Fr>
704[[gnu::always_inline]] inline void Polynomial<Fr>::add_scaled(PolynomialSpan<const Fr> other, const Fr& scaling_factor)
705{
706 BB_ASSERT_LTE(start_index(), other.start_index);
707 BB_ASSERT_GTE(end_index(), other.end_index());
708 // Outer thread split. The template `parallel_for_heuristic` short-circuits
709 // below its work threshold by invoking `func(ThreadChunk{0, 1})` directly,
710 // so the small-range / single-thread case still reaches add_scaled_chunk's
711 // tight `vectorized_for<VECTOR_FIELD_WIDTH>` loop without parallel_for overhead.
713 other.size(),
714 [&other, scaling_factor, this](const ThreadChunk& chunk) {
715 BB_INLINE_STMT add_scaled_chunk(chunk, other, scaling_factor);
716 },
718}
719
720// Inline definitions for `operator+=` / `add_chunk` — pointwise polynomial
721// addition. Bodies in the header for the same V8/TurboFan inlining reason
722// `add_scaled_chunk` was lifted up: a polynomial.cpp definition introduces
723// a real WASM function-call boundary that V8 was not eliding.
724template <typename Fr>
725[[gnu::always_inline]] inline void Polynomial<Fr>::add_chunk(const ThreadChunk& chunk, PolynomialSpan<const Fr> other)
726{
727 auto range = chunk.range(other.size());
728 if (range.empty()) {
729 return;
730 }
731 const size_t lo = *range.begin() + other.start_index;
732 const size_t hi = lo + range.size();
733 vectorized_for<VECTOR_FIELD_WIDTH, Fr>(lo, hi, [&](auto ctx) { (*this)[ctx] = (*this)[ctx] + other[ctx]; });
734}
735
736template <typename Fr>
738{
739 BB_ASSERT_LTE(start_index(), other.start_index);
740 BB_ASSERT_GTE(end_index(), other.end_index());
742 other.size(),
743 [&other, this](const ThreadChunk& chunk) { BB_INLINE_STMT add_chunk(chunk, other); },
745 return *this;
746}
747
748// Inline definitions for `operator-=` / `subtract_chunk`.
749template <typename Fr>
750[[gnu::always_inline]] inline void Polynomial<Fr>::subtract_chunk(const ThreadChunk& chunk,
752{
753 auto range = chunk.range(other.size());
754 if (range.empty()) {
755 return;
756 }
757 const size_t lo = *range.begin() + other.start_index;
758 const size_t hi = lo + range.size();
759 vectorized_for<VECTOR_FIELD_WIDTH, Fr>(lo, hi, [&](auto ctx) { (*this)[ctx] = (*this)[ctx] - other[ctx]; });
760}
761
762template <typename Fr>
764{
765 BB_ASSERT_LTE(start_index(), other.start_index);
766 BB_ASSERT_GTE(end_index(), other.end_index());
768 other.size(),
769 [&other, this](const ThreadChunk& chunk) { BB_INLINE_STMT subtract_chunk(chunk, other); },
771 return *this;
772}
773
774// Inline definitions for `operator*=` / `multiply_chunk` — pointwise scalar
775// multiply. Broadcast<Fr> exposes `scaling_factor` through the same
776// token-dispatched operator[] protocol Polynomial uses.
777template <typename Fr>
778[[gnu::always_inline]] inline void Polynomial<Fr>::multiply_chunk(const ThreadChunk& chunk, const Fr& scaling_factor)
779{
780 auto range = chunk.range(size());
781 if (range.empty()) {
782 return;
783 }
784 const size_t lo = *range.begin() + start_index();
785 const size_t hi = lo + range.size();
786 const Broadcast<Fr> scaling(scaling_factor);
787 vectorized_for<VECTOR_FIELD_WIDTH, Fr>(lo, hi, [&](auto ctx) { (*this)[ctx] = (*this)[ctx] * scaling[ctx]; });
788}
789
790template <typename Fr>
791[[gnu::always_inline]] inline Polynomial<Fr>& Polynomial<Fr>::operator*=(const Fr& scaling_factor)
792{
794 size(),
795 [scaling_factor, this](const ThreadChunk& chunk) { BB_INLINE_STMT multiply_chunk(chunk, scaling_factor); },
797 return *this;
798}
799
810template <typename Fr>
812 std::span<const PolynomialSpan<const Fr>> sources,
813 std::span<const Fr> scalars);
814
815template <typename Fr>
816void add_scaled_batch(Polynomial<Fr>& dst, std::span<const Polynomial<Fr>> sources, std::span<const Fr> scalars)
817{
819 source_spans.reserve(sources.size());
820 for (const auto& source : sources) {
821 source_spans.emplace_back(source);
822 }
823 add_scaled_batch(dst, std::span<const PolynomialSpan<const Fr>>(source_spans), scalars);
824}
825
826template <typename Fr>
827void add_scaled_batch(Polynomial<Fr>& dst, const std::vector<Polynomial<Fr>>& sources, std::span<const Fr> scalars)
828{
829 add_scaled_batch(dst, std::span<const Polynomial<Fr>>(sources), scalars);
830}
831
832// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
833template <typename Fr> std::shared_ptr<Fr[]> _allocate_aligned_memory(size_t n_elements)
834{
835 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
836 return std::make_shared<Fr[]>(n_elements);
837}
838
843template <typename Fr_>
845 const SharedShiftedVirtualZeroesArray<Fr_>& coefficients,
846 bool shift)
847{
848 constexpr bool is_native = IsAnyOf<Fr_, bb::fr, grumpkin::fr>;
849 // shift ==> native
850 BB_ASSERT(!shift || is_native);
851
852 if (coefficients.size() == 0) {
853 return Fr_(0);
854 }
855
856 const size_t n = evaluation_points.size();
857 // A 0-variable MLE is the constant polynomial; return the single coefficient directly.
858 if (n == 0) {
859 BB_ASSERT_EQ(coefficients.virtual_size(), 1UL);
860 return coefficients.get(0);
861 }
862 const size_t dim = numeric::get_msb(coefficients.end_ - 1) + 1; // Round up to next power of 2
863
864 // To simplify handling of edge cases, we assume that the index space is always a power of 2
865 BB_ASSERT_EQ(coefficients.virtual_size(), 1UL << n);
866
867 // We first fold over dim rounds l = 0,...,dim-1.
868 // in round l, n_l is the size of the buffer containing the Polynomial partially evaluated
869 // at u₀,..., u_l.
870 // In round 0, this is half the size of dim
871 size_t n_l = 1UL << (dim - 1);
872
873 // temporary buffer of half the size of the Polynomial
874 auto tmp_ptr = _allocate_aligned_memory<Fr_>(n_l);
875 auto tmp = tmp_ptr.get();
876
877 size_t offset = 0;
878 if constexpr (is_native) {
879 if (shift) {
880 BB_ASSERT_EQ(coefficients.get(0), Fr_::zero());
881 offset++;
882 }
883 }
884
885 Fr_ u_l = evaluation_points[0];
886
887 // Note below: i * 2 + 1 + offset might equal virtual_size. This used to subtlely be handled by extra capacity
888 // padding (and there used to be no assert time checks, which this constant helps with).
889 const size_t ALLOW_ONE_PAST_READ = 1;
890 for (size_t i = 0; i < n_l; ++i) {
891 // curr[i] = (Fr(1) - u_l) * prev[i * 2] + u_l * prev[(i * 2) + 1];
892 tmp[i] = coefficients.get(i * 2 + offset) +
893 u_l * (coefficients.get(i * 2 + 1 + offset, ALLOW_ONE_PAST_READ) - coefficients.get(i * 2 + offset));
894 }
895
896 // partially evaluate the dim-1 remaining points
897 for (size_t l = 1; l < dim; ++l) {
898 n_l = 1UL << (dim - l - 1);
899 u_l = evaluation_points[l];
900 for (size_t i = 0; i < n_l; ++i) {
901 tmp[i] = tmp[i * 2] + u_l * (tmp[(i * 2) + 1] - tmp[i * 2]);
902 }
903 }
904 auto result = tmp[0];
905
906 // We handle the "trivial" dimensions which are full of zeros.
907 for (size_t i = dim; i < n; i++) {
908 result *= (Fr_(1) - evaluation_points[i]);
909 }
910
911 return result;
912}
913
917template <typename Fr_>
919 const SharedShiftedVirtualZeroesArray<Fr_>& coefficients)
920{
921 return _evaluate_mle(evaluation_points, coefficients, false);
922}
923
924template <typename Fr> inline std::ostream& operator<<(std::ostream& os, const Polynomial<Fr>& p)
925{
926 if (p.size() == 0) {
927 return os << "[]";
928 }
929 if (p.size() == 1) {
930 return os << "[ data " << p[0] << "]";
931 }
932 return os << "[ data\n"
933 << " " << p[0] << ",\n"
934 << " " << p[1] << ",\n"
935 << " ... ,\n"
936 << " " << p[p.size() - 2] << ",\n"
937 << " " << p[p.size() - 1] << ",\n"
938 << "]";
939}
940
941template <typename Poly, typename... Polys> auto zip_polys(Poly&& poly, Polys&&... polys)
942{
943 // Ensure all polys have the same start_index() and end_index() as poly
944 // Use fold expression to check all polys exactly match our size
945 // Wrap BB_ASSERT_EQ_RELEASE in a lambda to make it usable in a fold expression
946 auto check_indices = [&](const auto& other) {
947 BB_ASSERT_EQ(poly.start_index(), other.start_index());
948 BB_ASSERT_EQ(poly.end_index(), other.end_index());
949 };
950 // Apply the lambda to each poly in the parameter pack
951 (check_indices(polys), ...);
952 return zip_view(poly.indices(), poly.coeffs(), polys.coeffs()...);
953}
954} // namespace bb
constexpr size_t N
#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
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
ScalarWriteProxy operator[](ScalarIndex ctx)
bool is_empty() const
static Polynomial random(size_t size, size_t start_index=0)
ContiguousVectorWriteProxyT< typename U::Params > operator[](ContiguousVectorIndex< N > ctx)
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
VectorField< typename U::Params > operator[](VectorIndex< N > ctx) const
bool is_shiftable() const
Fr operator[](ScalarIndex ctx) 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.
void subtract_chunk(const ThreadChunk &chunk, PolynomialSpan< const Fr > other)
Polynomial & operator=(const Polynomial &other)
VectorWriteProxyT< typename U::Params > operator[](VectorIndex< N > ctx)
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.
void add_chunk(const ThreadChunk &chunk, PolynomialSpan< const Fr > other)
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
VectorField< typename U::Params > operator[](ContiguousVectorIndex< N > ctx) 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)
#define BB_INLINE_STMT
FF a
FF b
ssize_t offset
Definition engine.cpp:62
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
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:132
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:134
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
std::ostream & operator<<(std::ostream &os, CircuitKind kind)
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].
constexpr size_t VECTOR_FIELD_WIDTH
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:172
constexpr ScalarIndex shift(ScalarIndex ctx, size_t d)
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
#define BB_VECTOR_PROXY_BINARY_OPS(Proxy, Value, Scalar)
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
bb::VectorAffineElementPushSpan< BaseParams > rhs
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.
ContiguousVectorWriteProxyT & operator=(const VectorField< Params_ > &v)
friend Fr operator*(const Fr &a, const ScalarWriteProxy &b) noexcept
friend Fr operator+(const ScalarWriteProxy &a, const ScalarWriteProxy &b) noexcept
friend Fr operator*(const ScalarWriteProxy &a, const Fr &b) noexcept
friend Fr operator+(const ScalarWriteProxy &a, const Fr &b) noexcept
friend Fr operator-(const ScalarWriteProxy &a, const Fr &b) noexcept
friend Fr operator-(const ScalarWriteProxy &a, const ScalarWriteProxy &b) noexcept
friend Fr operator*(const ScalarWriteProxy &a, const ScalarWriteProxy &b) noexcept
friend Fr operator+(const Fr &a, const ScalarWriteProxy &b) noexcept
ScalarWriteProxy & operator=(const Fr &v)
friend Fr operator-(const Fr &a, const ScalarWriteProxy &b) noexcept
VectorWriteProxyT & operator=(const VectorField< Params_ > &v)
std::array< size_t, 5 > idx
PolynomialSpan subspan(size_t offset, size_t length)
size_t size() const
std::remove_const_t< Fr > element_type
Fr & operator[](size_t index)
std::span< Fr > span
size_t end_index() const
VectorField< typename U::Params > operator[](ContiguousVectorIndex< N > ctx) const
PolynomialSpan(size_t start_index, std::span< Fr > span)
element_type operator[](ScalarIndex ctx) const
VectorField< typename U::Params > operator[](VectorIndex< N > ctx) const
const Fr & operator[](size_t index) const
auto range(size_t size, size_t offset=0) const
Definition thread.hpp:152
static VectorField gather(const Field *base, std::array< size_t, 5 > idx, size_t offset=0) noexcept
void store_to(Field *base) const noexcept
void scatter(Field *base, std::array< size_t, 5 > idx, size_t offset=0) const noexcept
std::array< size_t, N > idx
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)
BB_VF_LOAD_LIMBS * this
VectorField result