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: 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
14
17#include <typeinfo>
18
19namespace bb {
20
21// TODO(luke): This contains utilities for grand product computation and is not specific to the permutation grand
22// product. Update comments accordingly.
71template <typename Flavor, typename GrandProdRelation>
72void compute_grand_product(typename Flavor::ProverPolynomials& full_polynomials,
74 size_t size_override = 0,
75 const ActiveRegionData& active_region_data = ActiveRegionData{})
76{
77 PROFILE_THIS_NAME("compute_grand_product");
78
79 using FF = typename Flavor::FF;
80 using Polynomial = typename Flavor::Polynomial;
82
83 const bool has_active_ranges = active_region_data.size() > 0;
84
85 // Set the domain over which the grand product must be computed. This may be less than the dyadic circuit size, e.g
86 // the permutation grand product does not need to be computed beyond the index of the last active wire
87 size_t domain_size = size_override == 0 ? full_polynomials.get_polynomial_size() : size_override;
88
89 // Returns the ith active index if specified, otherwise acts as the identity map on the input
90 auto get_active_range_poly_idx = [&](size_t i) { return has_active_ranges ? active_region_data.get_idx(i) : i; };
91
92 size_t active_domain_size = has_active_ranges ? active_region_data.size() : domain_size;
93
94 // The size of the iteration domain is one less than the number of active rows since the final value of the
95 // grand product is constructed only in the relation and not explicitly in the polynomial
96 const MultithreadData active_range_thread_data = calculate_thread_data(active_domain_size - 1);
97
98 // Allocate numerator/denominator polynomials that will serve as scratch space
99 // TODO(zac) we can re-use the permutation polynomial as the numerator polynomial. Reduces readability
100 Polynomial numerator{ active_domain_size };
101 Polynomial denominator{ active_domain_size };
102
103 // Step (1)
104 // Populate `numerator` and `denominator` with the algebra described by Relation
105 parallel_for(active_range_thread_data.num_threads, [&](size_t thread_idx) {
106 const size_t start = active_range_thread_data.start[thread_idx];
107 const size_t end = active_range_thread_data.end[thread_idx];
108 typename Flavor::AllValues row;
109 for (size_t i = start; i < end; ++i) {
110 // TODO(https://github.com/AztecProtocol/barretenberg/issues/940):consider avoiding get_row if possible.
111 auto row_idx = get_active_range_poly_idx(i);
112 if constexpr (IsUltraOrMegaHonk<Flavor>) {
113 row = full_polynomials.get_row_for_permutation_arg(row_idx);
114 } else {
115 row = full_polynomials.get_row(row_idx);
116 }
117 numerator.at(i) =
118 GrandProdRelation::template compute_grand_product_numerator<Accumulator>(row, relation_parameters);
119 denominator.at(i) =
120 GrandProdRelation::template compute_grand_product_denominator<Accumulator>(row, relation_parameters);
121 }
122 });
123
124 DEBUG_LOG_ALL(numerator.coeffs());
125 DEBUG_LOG_ALL(denominator.coeffs());
126
127 // Step (2)
128 // Compute the accumulating product of the numerator and denominator terms.
129 // This step is split into three parts for efficient multithreading:
130 // (i) compute ∏ A(j), ∏ B(j) subproducts for each thread
131 // (ii) compute scaling factor required to convert each subproduct into a single running product
132 // (ii) combine subproducts into a single running product
133 //
134 // For example, consider 4 threads and a size-8 numerator { a0, a1, a2, a3, a4, a5, a6, a7 }
135 // (i) Each thread computes 1 element of N = {{ a0, a0a1 }, { a2, a2a3 }, { a4, a4a5 }, { a6, a6a7 }}
136 // (ii) Take partial products P = { 1, a0a1, a2a3, a4a5 }
137 // (iii) Each thread j computes N[i][j]*P[j]=
138 // {{a0,a0a1},{a0a1a2,a0a1a2a3},{a0a1a2a3a4,a0a1a2a3a4a5},{a0a1a2a3a4a5a6,a0a1a2a3a4a5a6a7}}
139 std::vector<FF> partial_numerators(active_range_thread_data.num_threads);
140 std::vector<FF> partial_denominators(active_range_thread_data.num_threads);
141
142 parallel_for(active_range_thread_data.num_threads, [&](size_t thread_idx) {
143 const size_t start = active_range_thread_data.start[thread_idx];
144 const size_t end = active_range_thread_data.end[thread_idx];
145 for (size_t i = start; i < end - 1; ++i) {
146 numerator.at(i + 1) *= numerator[i];
147 denominator.at(i + 1) *= denominator[i];
148 }
149 partial_numerators[thread_idx] = numerator[end - 1];
150 partial_denominators[thread_idx] = denominator[end - 1];
151 });
152
153 DEBUG_LOG_ALL(partial_numerators);
154 DEBUG_LOG_ALL(partial_denominators);
155
156 parallel_for(active_range_thread_data.num_threads, [&](size_t thread_idx) {
157 const size_t start = active_range_thread_data.start[thread_idx];
158 const size_t end = active_range_thread_data.end[thread_idx];
159 if (thread_idx > 0) {
160 FF numerator_scaling = 1;
161 FF denominator_scaling = 1;
162
163 for (size_t j = 0; j < thread_idx; ++j) {
164 numerator_scaling *= partial_numerators[j];
165 denominator_scaling *= partial_denominators[j];
166 }
167 for (size_t i = start; i < end; ++i) {
168 numerator.at(i) = numerator[i] * numerator_scaling;
169 denominator.at(i) = denominator[i] * denominator_scaling;
170 }
171 }
172
173 // Final step: invert denominator
174 FF::batch_invert(std::span{ &denominator.data()[start], end - start });
175 });
176
177 DEBUG_LOG_ALL(numerator.coeffs());
178 DEBUG_LOG_ALL(denominator.coeffs());
179
180 // Step (3) Compute z_perm[i] = numerator[i] / denominator[i]
181 auto& grand_product_polynomial = GrandProdRelation::get_grand_product_polynomial(full_polynomials);
182 // We have a 'virtual' 0 at the start (as this is a to-be-shifted polynomial)
183 BB_ASSERT_EQ(grand_product_polynomial.start_index(), 1U);
184
185 // For Ultra/Mega, the first row is an inactive zero row thus the grand prod takes value 1 at both i = 0 and i = 1
186 if constexpr (IsUltraOrMegaHonk<Flavor>) {
187 grand_product_polynomial.at(1) = 1;
188 }
189
190 // Compute grand product values corresponding only to the active regions of the trace
191 parallel_for(active_range_thread_data.num_threads, [&](size_t thread_idx) {
192 const size_t start = active_range_thread_data.start[thread_idx];
193 const size_t end = active_range_thread_data.end[thread_idx];
194 for (size_t i = start; i < end; ++i) {
195 const auto poly_idx = get_active_range_poly_idx(i + 1);
196 grand_product_polynomial.at(poly_idx) = numerator[i] * denominator[i];
197 }
198 });
199
200 // Final step: If active/inactive regions have been specified, the value of the grand product in the inactive
201 // regions have not yet been set. The polynomial takes an already computed constant value across each inactive
202 // region (since no copy constraints are present there) equal to the value of the grand product at the first index
203 // of the subsequent active region.
204 if (has_active_ranges) {
205 MultithreadData full_domain_thread_data = calculate_thread_data(domain_size);
206 parallel_for(full_domain_thread_data.num_threads, [&](size_t thread_idx) {
207 const size_t start = full_domain_thread_data.start[thread_idx];
208 const size_t end = full_domain_thread_data.end[thread_idx];
209 for (size_t i = start; i < end; ++i) {
210 for (size_t j = 0; j < active_region_data.num_ranges() - 1; ++j) {
211 const size_t previous_range_end = active_region_data.get_range(j).second;
212 const size_t next_range_start = active_region_data.get_range(j + 1).first;
213 // Set the value of the polynomial if the index falls in an inactive region
214 if (i >= previous_range_end && i < next_range_start) {
215 grand_product_polynomial.at(i) = grand_product_polynomial[next_range_start];
216 break;
217 }
218 }
219 }
220 });
221 }
222
223 DEBUG_LOG_ALL(grand_product_polynomial.coeffs());
224}
225
230template <typename Flavor>
231void compute_grand_products(typename Flavor::ProverPolynomials& full_polynomials,
233 const size_t size_override = 0)
234{
235 using GrandProductRelations = typename Flavor::GrandProductRelations;
236
237 constexpr size_t NUM_RELATIONS = std::tuple_size<GrandProductRelations>{};
238 bb::constexpr_for<0, NUM_RELATIONS, 1>([&]<size_t i>() {
239 using GrandProdRelation = typename std::tuple_element<i, GrandProductRelations>::type;
240
241 compute_grand_product<Flavor, GrandProdRelation>(full_polynomials, relation_parameters, size_override);
242 });
243}
244
245} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
std::tuple< ECCVMSetRelation< FF > > GrandProductRelations
A container for the prover polynomials handles.
Curve::ScalarField FF
bb::Polynomial< FF > Polynomial
std::size_t size() const
typename Flavor::Polynomial Polynomial
#define DEBUG_LOG_ALL(container)
Base class templates for structures that contain data parameterized by the fundamental polynomials of...
Entry point for Barretenberg command-line interface.
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:173
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 compute_grand_product(typename Flavor::ProverPolynomials &full_polynomials, bb::RelationParameters< typename Flavor::FF > &relation_parameters, size_t size_override=0, const ActiveRegionData &active_region_data=ActiveRegionData{})
Compute a permutation grand product polynomial Z_perm(X)
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:72
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
#define PROFILE_THIS_NAME(name)
Definition op_count.hpp:16
Container for parameters used by the grand product (permutation, lookup) Honk relations.
static void batch_invert(std::span< field > coeffs) noexcept