Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
grand_product_library.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Raju], commit: 21a7e3670e6 }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
15
18
19namespace bb {
20
80template <typename Flavor, typename GrandProdRelation>
81void compute_grand_product(typename Flavor::ProverPolynomials& full_polynomials,
83 size_t size_override = 0,
84 uint32_t* duplicate_count_out = nullptr)
85{
86 BB_BENCH_NAME("compute_grand_product");
87
88 using FF = typename Flavor::FF;
89 using Polynomial = typename Flavor::Polynomial;
91
92 // Set the domain over which the grand product must be computed. This may be less than the dyadic circuit size, e.g
93 // the permutation grand product does not need to be computed beyond the index of the last active wire
94 size_t domain_size = size_override == 0 ? full_polynomials.get_polynomial_size() : size_override;
95
96 // Grand product starts after the disabled/reserved head rows (where lagrange_first lives).
97 constexpr size_t gp_start = Flavor::TRACE_OFFSET;
98
99 const size_t active_size = domain_size - gp_start;
100
101 // The size of the iteration domain is one less than the active domain since the final value of the
102 // grand product is constructed only in the relation and not explicitly in the polynomial
103 const MultithreadData thread_data = calculate_thread_data(active_size - 1);
104
105 // Allocate numerator/denominator polynomials that will serve as scratch space
106 // TODO: we can re-use the permutation polynomial as the numerator polynomial (reduces readability)
107 Polynomial numerator{ active_size };
108 Polynomial denominator{ active_size };
109
110 // Optionally count rows whose per-row ratio is 1 (numerator == denominator): there z_perm is
111 // unchanged across the row, i.e. z_perm[i] == z_perm[i-1]. These are exactly the adjacent
112 // duplicate coefficients the MSM dedup pre-pass strips. Counting them here (the per-row values
113 // are already in hand) avoids a second full pass over z_perm in the caller.
114 const bool count_duplicates = duplicate_count_out != nullptr;
115 std::vector<uint32_t> thread_duplicate_counts(count_duplicates ? thread_data.num_threads : 0, 0);
116
117 // Step (1)
118 // Populate `numerator` and `denominator` with the algebra described by Relation
119 parallel_for(thread_data.num_threads, [&](size_t thread_idx) {
120 BB_BENCH_TRACY_NAME("GrandProduct::step1_numerator_denominator");
121 const size_t start = thread_data.start[thread_idx];
122 const size_t end = thread_data.end[thread_idx];
123 typename Flavor::AllValues row;
124 uint32_t local_duplicates = 0;
125 for (size_t i = start; i < end; ++i) {
126 const size_t poly_idx = i + gp_start;
127 // TODO: consider avoiding get_row if possible.
128 if constexpr (IsUltraOrMegaHonk<Flavor>) {
129 row = full_polynomials.get_row_for_permutation_arg(poly_idx);
130 } else {
131 row = full_polynomials.get_row(poly_idx);
132 }
133 numerator.at(i) =
134 GrandProdRelation::template compute_grand_product_numerator<Accumulator>(row, relation_parameters);
135 denominator.at(i) =
136 GrandProdRelation::template compute_grand_product_denominator<Accumulator>(row, relation_parameters);
137 if (count_duplicates && numerator[i] == denominator[i]) {
138 ++local_duplicates;
139 }
140 }
141 if (count_duplicates) {
142 thread_duplicate_counts[thread_idx] = local_duplicates;
143 }
144 });
145
146 if (count_duplicates) {
147 uint32_t total_duplicates = 0;
148 for (const uint32_t c : thread_duplicate_counts) {
149 total_duplicates += c;
150 }
151 *duplicate_count_out = total_duplicates;
152 }
153
154 DEBUG_LOG_ALL(numerator.coeffs());
155 DEBUG_LOG_ALL(denominator.coeffs());
156
157 // Step (2)
158 // Compute the accumulating product of the numerator and denominator terms.
159 // This step is split into three parts for efficient multithreading:
160 // (i) compute ∏ A(j), ∏ B(j) subproducts for each thread
161 // (ii) compute scaling factor required to convert each subproduct into a single running product
162 // (ii) combine subproducts into a single running product
163 //
164 // For example, consider 4 threads and a size-8 numerator { a0, a1, a2, a3, a4, a5, a6, a7 }
165 // (i) Each thread computes 1 element of N = {{ a0, a0a1 }, { a2, a2a3 }, { a4, a4a5 }, { a6, a6a7 }}
166 // (ii) Take partial products P = { 1, a0a1, a2a3, a4a5 }
167 // (iii) Each thread j computes N[i][j]*P[j]=
168 // {{a0,a0a1},{a0a1a2,a0a1a2a3},{a0a1a2a3a4,a0a1a2a3a4a5},{a0a1a2a3a4a5a6,a0a1a2a3a4a5a6a7}}
169 std::vector<FF> partial_numerators(thread_data.num_threads);
170 std::vector<FF> partial_denominators(thread_data.num_threads);
171
172 parallel_for(thread_data.num_threads, [&](size_t thread_idx) {
173 BB_BENCH_TRACY_NAME("GrandProduct::step2a_subproducts");
174 const size_t start = thread_data.start[thread_idx];
175 const size_t end = thread_data.end[thread_idx];
176 for (size_t i = start; i < end - 1; ++i) {
177 numerator.at(i + 1) *= numerator[i];
178 denominator.at(i + 1) *= denominator[i];
179 }
180 partial_numerators[thread_idx] = numerator[end - 1];
181 partial_denominators[thread_idx] = denominator[end - 1];
182 });
183
184 DEBUG_LOG_ALL(partial_numerators);
185 DEBUG_LOG_ALL(partial_denominators);
186
187 parallel_for(thread_data.num_threads, [&](size_t thread_idx) {
188 BB_BENCH_TRACY_NAME("GrandProduct::step2b_scale_and_invert");
189 const size_t start = thread_data.start[thread_idx];
190 const size_t end = thread_data.end[thread_idx];
191 if (thread_idx > 0) {
192 FF numerator_scaling = 1;
193 FF denominator_scaling = 1;
194
195 for (size_t j = 0; j < thread_idx; ++j) {
196 numerator_scaling *= partial_numerators[j];
197 denominator_scaling *= partial_denominators[j];
198 }
199 for (size_t i = start; i < end; ++i) {
200 numerator.at(i) = numerator[i] * numerator_scaling;
201 denominator.at(i) = denominator[i] * denominator_scaling;
202 }
203 }
204
205 // Final step: invert denominator
206 FF::batch_invert(std::span{ &denominator.data()[start], end - start });
207 });
208
209 DEBUG_LOG_ALL(numerator.coeffs());
210 DEBUG_LOG_ALL(denominator.coeffs());
211
212 // Step (3) Compute grand_product_polynomial[i] = numerator[i] / denominator[i]
213 auto& grand_product_polynomial = GrandProdRelation::get_grand_product_polynomial(full_polynomials);
214 // The grand_product_polynomial must be shiftable for the permutation argument
215 BB_ASSERT(grand_product_polynomial.is_shiftable());
216
217 // Initialize grand product: z_perm[gp_start] = 1 (the first active row after disabled region)
218 // For non-ZK, gp_start = NUM_ZERO_ROWS = 1, which matches z_perm[1] = num[0] * inv_den[0] implicitly
219 // (since z_perm[0] = 0, the relation at lagrange_first uses z_perm + 1).
220 // For ZK with top masking, z_perm must be 0 at lagrange_first (row NUM_DISABLED_ROWS_IN_SUMCHECK)
221 // and the product starts from gp_start.
222
223 // Compute grand product values: z_perm[gp_start + i + 1] = numerator[i] / denominator[i]
224 parallel_for(thread_data.num_threads, [&](size_t thread_idx) {
225 BB_BENCH_TRACY_NAME("GrandProduct::step3_quotient");
226 const size_t start = thread_data.start[thread_idx];
227 const size_t end = thread_data.end[thread_idx];
228 for (size_t i = start; i < end; ++i) {
229 grand_product_polynomial.at(gp_start + i + 1) = numerator[i] * denominator[i];
230 }
231 });
232
233 DEBUG_LOG_ALL(grand_product_polynomial.coeffs());
234}
235
240template <typename Flavor>
241void compute_grand_products(typename Flavor::ProverPolynomials& full_polynomials,
243 const size_t size_override = 0)
244{
245 using GrandProductRelations = typename Flavor::GrandProductRelations;
246
247 constexpr size_t NUM_RELATIONS = std::tuple_size<GrandProductRelations>{};
248 bb::constexpr_for<0, NUM_RELATIONS, 1>([&]<size_t i>() {
249 using GrandProdRelation = typename std::tuple_element<i, GrandProductRelations>::type;
250
251 compute_grand_product<Flavor, GrandProdRelation>(full_polynomials, relation_parameters, size_override);
252 });
253}
254
255} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
A container for the prover polynomials.
typename Curve::ScalarField FF
bb::Polynomial< FF > Polynomial
std::tuple< ECCVMSetRelation< FF > > GrandProductRelations
static constexpr size_t TRACE_OFFSET
#define DEBUG_LOG_ALL(container)
Base class templates shared across Honk flavors.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
MultithreadData calculate_thread_data(size_t num_iterations, size_t min_iterations_per_thread)
Calculates number of threads and index bounds for each thread.
Definition thread.cpp:208
void compute_grand_product(typename Flavor::ProverPolynomials &full_polynomials, bb::RelationParameters< typename Flavor::FF > &relation_parameters, size_t size_override=0, uint32_t *duplicate_count_out=nullptr)
Compute a grand product polynomial, grand_product_polynomial, which for historical reasons is sometim...
void compute_grand_products(typename Flavor::ProverPolynomials &full_polynomials, bb::RelationParameters< typename Flavor::FF > &relation_parameters, const size_t size_override=0)
Compute the grand product corresponding to each grand-product relation defined in the Flavor.
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Container for parameters used by the grand product (permutation, lookup) Honk relations.
static void batch_invert(C &coeffs) noexcept
Batch invert a collection of field elements using Montgomery's trick.