Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gate_separator.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Khashayar], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
14
15#include <cstddef>
16#include <vector>
17namespace bb {
18
19template <typename FF> struct GateSeparatorPolynomial {
24 std::vector<FF> betas;
25
42 size_t periodicity = 2;
51
58 GateSeparatorPolynomial(const std::vector<FF>& betas, const size_t log_num_monomials)
59 : betas(betas)
60 , beta_products(compute_beta_products(betas, log_num_monomials))
61 {}
62
69 GateSeparatorPolynomial(const std::vector<FF>& betas)
70 : betas(betas)
71 {}
72
78 GateSeparatorPolynomial(const std::vector<FF>& betas, const std::vector<FF>& challenge)
79 : betas(betas)
80 {
81 if (!betas.empty()) {
82 for (const auto& u_k : challenge) {
84 }
85 }
86 }
87
94 FF const& operator[](size_t idx) const
95 {
96 // At round i (periodicity == 2^{i+1}), the idx-th surviving evaluation lives at beta_products index
97 // (idx >> 1) * periodicity, i.e. the (idx * 2^i)-th element. Sumcheck consumes edges pairwise, so only
98 // even indices are meaningful; an odd idx would silently alias to the idx - 1 slot.
99 BB_ASSERT_DEBUG(idx % 2 == 0, "GateSeparatorPolynomial: edge index must be even");
100 return beta_products.at((idx >> 1) * periodicity);
101 }
102
110 template <typename Element> Element gather(size_t edge_idx) const
111 {
112 return Element::from_lanes([&](size_t j) { return (*this)[edge_idx + (2 * j)]; });
113 }
120 {
121 if (betas.empty()) {
122 return FF(1);
123 };
125 }
126
132 static FF univariate_factor(const FF& challenge, const FF& beta) { return FF(1) + (challenge * (beta - FF(1))); }
133
137 FF univariate_eval(FF challenge) const { return univariate_factor(challenge, betas[current_element_idx]); };
138
145 void partially_evaluate(FF challenge)
146 {
147 if (!betas.empty()) {
148 FF current_univariate_eval = univariate_eval(challenge);
149 partial_evaluation_result *= current_univariate_eval;
151 periodicity *= 2;
152 }
153 }
154
164 const size_t log_num_monomials,
165 const FF& scaling_factor = FF(1))
166 {
167 if (betas.empty()) {
169 return out;
170 }
171
172 BB_BENCH_NAME("GateSeparatorPolynomial::compute_beta_products");
173 size_t pow_size = static_cast<size_t>(1) << log_num_monomials;
175
176 // Explanations of the algorithm:
177 // The product of the betas at index i (beta_products[i]) contains the multiplicative factor betas[j] if and
178 // only if the jth bit of i is 1 (j starting with 0 for the least significant bit). For instance, i = 13 = 1101
179 // in binary, so the product is betas[0] * betas[2] * betas[3].
180 //
181 // Key insight: beta_products[i] = beta_products[predecessor] * betas[lsb_position], where predecessor is i
182 // with the least significant bit cleared. For example:
183 // - i = 6 (binary 110): LSB is at position 1, predecessor = 4 (binary 100)
184 // beta_products[6] = beta_products[4] * betas[1]
185 // - i = 12 (binary 1100): LSB is at position 2, predecessor = 8 (binary 1000)
186 // beta_products[12] = beta_products[8] * betas[2]
187 //
188 // For each index i, if the predecessor falls within our thread's range [start, start + chunk_size), we use
189 // this O(1) recurrence. Otherwise, we compute directly by iterating over all set bits in i, which requires
190 // O(popcount(i)) multiplications. This direct computation handles boundary cases between thread chunks.
191 //
192 // This algorithm works with any number of threads (not just powers of 2), unlike the previous prefix/suffix
193 // approach which required power-of-2 thread counts to ensure even work distribution.
194
195 // Cost per iteration: typically 1 multiplication (when predecessor is in range),
196 // occasionally O(popcount) multiplications at chunk boundaries
197 constexpr size_t iteration_cost = thread_heuristics::FF_MULTIPLICATION_COST;
199 pow_size,
200 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
201 BB_BENCH_TRACY_NAME("GateSeparator::beta_products/chunk");
202 for (size_t i = start; i < end; i++) {
203 if (i == 0) {
204 beta_products.at(0) = scaling_factor;
205 continue;
206 }
207
208 // Find the lowest set bit position and the predecessor index
209 size_t lsb_pos = numeric::get_lsb(i);
210 size_t predecessor = i ^ (static_cast<size_t>(1) << lsb_pos); // clear the lowest set bit
211
212 if (predecessor >= start) {
213 // Predecessor is in our range, O(1) computation
214 beta_products.at(i) = beta_products.at(predecessor) * betas[lsb_pos];
215 } else {
216 // Predecessor is not in our range, compute directly from set bits only
217 FF result = scaling_factor;
218 size_t remaining = i;
219 while (remaining != 0) {
220 size_t bit = numeric::get_lsb(remaining);
221 result *= betas[bit];
222 remaining ^= static_cast<size_t>(1) << bit; // clear this bit
223 }
225 }
226 }
227 },
228 iteration_cost);
229
230 return beta_products;
231 }
232};
233} // namespace bb
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
#define BB_BENCH_TRACY_NAME(name)
Definition bb_bench.hpp:256
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
#define BB_PROFILE
#define BB_UNUSED
constexpr T get_lsb(const T in)
Definition get_msb.hpp:71
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:134
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
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
bb::VectorAffineElementPushSpan< BaseParams > out
Curve::Element Element
static FF univariate_factor(const FF &challenge, const FF &beta)
The pow_β per-variable factor at .
GateSeparatorPolynomial(const std::vector< FF > &betas)
Construct a new GateSeparatorPolynomial object without expanding to a vector of monomials.
Element gather(size_t edge_idx) const
Read Element::SIZE consecutive edge-pair pow_beta factors starting at edge_idx, packed as a single El...
size_t periodicity
In Round of Sumcheck, the periodicity equals to and represents the fixed interval at which elements...
GateSeparatorPolynomial(const std::vector< FF > &betas, const size_t log_num_monomials)
Construct a new GateSeparatorPolynomial.
std::vector< FF > betas
The challenges .
FF current_element() const
Computes the component at index current_element_idx in betas.
FF const & operator[](size_t idx) const
Retruns the element in beta_products at place #idx.
void partially_evaluate(FF challenge)
Partially evaluate the -polynomial at the new challenge and update .
FF univariate_eval(FF challenge) const
Evaluate at the challenge point .
GateSeparatorPolynomial(const std::vector< FF > &betas, const std::vector< FF > &challenge)
Constructs a virtual GateSeparator used by the prover in rounds k > d - 1, and computes its partial e...
FF partial_evaluation_result
The value obtained by partially evaluating one variable in the power polynomial at each round....
size_t current_element_idx
In Round of Sumcheck, it points to the -th element in .
Polynomial< FF > beta_products
The consecutive evaluations for identified with the integers .
static BB_PROFILE Polynomial< FF > compute_beta_products(const std::vector< FF > &betas, const size_t log_num_monomials, const FF &scaling_factor=FF(1))
Given compute for .
VectorField result