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: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
13
14#include <cstddef>
15#include <vector>
16namespace bb {
17
18template <typename FF> struct GateSeparatorPolynomial {
23 std::vector<FF> betas;
24
41 size_t periodicity = 2;
50
57 GateSeparatorPolynomial(const std::vector<FF>& betas, const size_t log_num_monomials)
58 : betas(betas)
59 , beta_products(compute_beta_products(betas, log_num_monomials))
60 {}
61
68 GateSeparatorPolynomial(const std::vector<FF>& betas)
69 : betas(betas)
70 {}
71
77 GateSeparatorPolynomial(const std::vector<FF>& betas, const std::vector<FF>& challenge)
78 : betas(betas)
79 {
80 if (!betas.empty()) {
81 for (const auto& u_k : challenge) {
83 }
84 }
85 }
86
93 FF const& operator[](size_t idx) const
94 {
95 // At round i, we only iterate over beta_products of indices that are multiples of 2^i,
96 // Hence for the idx-th element we need to get the (idx * 2^i)-th element in #beta_products.
97 return beta_products.at((idx >> 1) * periodicity);
98 }
105 {
106 if (betas.empty()) {
107 return FF(1);
108 };
110 }
111
115 FF univariate_eval(FF challenge) const { return (FF(1) + (challenge * (betas[current_element_idx] - FF(1)))); };
116
123 void partially_evaluate(FF challenge)
124 {
125 if (!betas.empty()) {
126 FF current_univariate_eval = univariate_eval(challenge);
127 partial_evaluation_result *= current_univariate_eval;
129 periodicity *= 2;
130 }
131 }
132
141 void partially_evaluate(const FF& challenge, const FF& indicator)
142 {
143 if (!betas.empty()) {
144 FF current_univariate_eval = univariate_eval(challenge);
145 // If dummy round, make no update to the partial_evaluation_result
147 indicator * partial_evaluation_result * current_univariate_eval;
149 periodicity *= 2;
150 }
151 }
152
162 const size_t log_num_monomials,
163 const FF& scaling_factor = FF(1))
164 {
165 if (betas.empty()) {
166 Polynomial<FF> out(1);
167 return out;
168 }
169
170 BB_BENCH_NAME("GateSeparatorPolynomial::compute_beta_products");
171 size_t pow_size = 1 << log_num_monomials;
173
174 // Determine number of threads for multithreading.
175 // Note: Multithreading is "on" for every round but we reduce the number of threads from the max available based
176 // on a specified minimum number of iterations per thread. This eventually leads to the use of a single thread.
177 // For now we use a power of 2 number of threads simply to ensure the round size is evenly divided.
178 size_t max_num_threads = get_num_cpus_pow2(); // number of available threads (power of 2)
179 size_t min_iterations_per_thread = 1 << 6; // min number of iterations for which we'll spin up a unique thread
180 size_t desired_num_threads = pow_size / min_iterations_per_thread;
181 size_t num_threads = std::min(desired_num_threads, max_num_threads); // fewer than max if justified
182 num_threads = num_threads > 0 ? num_threads : 1; // ensure num threads is >= 1
183 size_t iterations_per_thread = pow_size / num_threads; // actual iterations per thread
184 const size_t num_betas_per_thread = numeric::get_msb(iterations_per_thread);
185
186 // Explanations of the algorithm:
187 // The product of the betas at index i (beta_products[i]) contains the multiplicative factor betas[j] if and
188 // only if the jth bit of i is 1 (j starting with 0 for the least significant bit). For instance, i = 13 = 1101
189 // in binary, so the product is betas[0] * betas[2] * betas[3]. Observe that if we toggle the kth bit of i (0 to
190 // 1), i.e., we add 2^k to i, then the product is multiplied by betas[k]: beta_products[i + 2^k] =
191 // beta_products[i] * betas[k]. If we know the products for the interval of indices [0, 2^k), we can compute all
192 // the products for the interval of indices [2^k, 2^(k+1)) by multiplying each element by betas[k]. Iteratively,
193 // starting with beta_products[0] = 1, we can double the number of computed products at each iteration by
194 // multiplying the previous products by betas[k]. We first multiply beta_products[0] = 1 by betas[0], then we
195 // multiply beta_products[0] and beta_products[1] by betas[1], etc...
196 //
197 // We distribute the computation of the beta_products evenly across threads, i.e., thread number
198 // thread_idx will handle the interval of indices [thread_idx * iterations_per_thread, (thread_idx + 1) *
199 // iterations_per_thread). Note that for a given thread, all the processed indices have the same
200 // prefix in binary. Therefore, each beta_product of the thread is a multiple of this "prefix product". The
201 // successive products are then populated by the above algorithm whereby we double the interval at each
202 // iteration and multiply by the new beta to process the suffix bits. The difference is that we initialize the
203 // first product with this "prefix product" instead of 1.
204
205 // Compute the prefix products for each thread
206 std::vector<FF> thread_prefix_beta_products(num_threads);
207 thread_prefix_beta_products.at(0) = scaling_factor;
208
209 // Same algorithm applies for the prefix products. The difference is that we start at a beta which is not the
210 // first one (index 0), but the one at index num_betas_per_thread. We process the high bits only.
211 // Example: If num_betas_per_thread = 3, we compute after the first iteration:
212 // (1, beta_3)
213 // 2nd iteration: (1, beta_3, beta_4, beta_3 * beta_4)
214 // 3nd iteration: (1, beta_3, beta_4, beta_3 * beta_4, beta_5, beta_3 * beta_5, beta_4 * beta_5, beta_3 * beta_4
215 // * beta_5)
216 // etc ....
217 for (size_t beta_idx = num_betas_per_thread, window_size = 1; beta_idx < log_num_monomials;
218 beta_idx++, window_size <<= 1) {
219 const auto& beta = betas.at(beta_idx);
220 for (size_t j = 0; j < window_size; j++) {
221 thread_prefix_beta_products.at(window_size + j) = beta * thread_prefix_beta_products.at(j);
222 }
223 }
224
225 parallel_for(num_threads, [&](size_t thread_idx) {
226 size_t start = thread_idx * iterations_per_thread;
227 beta_products.at(start) = thread_prefix_beta_products.at(thread_idx);
228
229 // Compute the suffix products for each thread
230 // Example: Assume we start with the prefix product = beta_3 * beta_5
231 // After the first iteration, we get: (beta_3 * beta_5, beta_0 * beta_3 * beta_5)
232 // 2nd iteration: (beta_3 * beta_5, beta_0 * beta_3 * beta_5, beta_1 * beta_3 * beta_5, beta_0 * beta_1 *
233 // beta_3 * beta_5)
234 // etc ...
235 for (size_t beta_idx = 0, window_size = 1; beta_idx < num_betas_per_thread; beta_idx++, window_size <<= 1) {
236 const auto& beta = betas.at(beta_idx);
237 for (size_t j = 0; j < window_size; j++) {
238 beta_products.at(start + window_size + j) = beta * beta_products.at(start + j);
239 }
240 }
241 });
242
243 return beta_products;
244 }
245};
320} // namespace bb
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:22
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:219
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
#define BB_PROFILE
constexpr T get_msb(const T in)
Definition get_msb.hpp:47
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
size_t get_num_cpus_pow2()
Definition thread.hpp:25
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
Implementation of the methods for the -polynomials used in in Sumcheck.
GateSeparatorPolynomial(const std::vector< FF > &betas)
Construct a new GateSeparatorPolynomial object without expanding to a vector of monomials.
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....
void partially_evaluate(const FF &challenge, const FF &indicator)
Partially evaluate the -polynomial at the new challenge and update .
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 .