diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp index 8a30480d4899..d381154f66f5 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp @@ -356,10 +356,8 @@ template class SmallSubgroupIPAVerifier { /** * @brief Efficient batch evaluation of the challenge polynomial, Lagrange first, and Lagrange last * - * @details It is a modification of \ref bb::polynomial_arithmetic::compute_barycentric_evaluation - * "compute_barycentric_evaluation" method that does not require EvaluationDomain object and outputs the barycentric - * evaluation of a polynomial along with the evaluations of the first and last Lagrange polynomials. The - * interpolation domain is given by \f$ (1, g, g^2, \ldots, g^{|H| -1 } )\f$ + * @details Outputs the barycentric evaluation of a polynomial along with the evaluations of the first and last + * Lagrange polynomials. The interpolation domain is given by \f$ (1, g, g^2, \ldots, g^{|H| -1 } )\f$ * * @param coeffs Coefficients of the polynomial to be evaluated, in our case it is the challenge polynomial * @param r Evaluation point, we are using the Gemini evaluation challenge diff --git a/barretenberg/cpp/src/barretenberg/polynomials/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/polynomials/CMakeLists.txt index e7e54086ac7d..63e288a7c157 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/polynomials/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(polynomials srs numeric ecc crypto_sha256) +barretenberg_module(polynomials srs numeric ecc crypto_sha256 stdlib_primitives circuit_checker) diff --git a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp index eb2d0b720f72..4a823acbdee8 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.hpp @@ -5,37 +5,54 @@ // ===================== #pragma once +#include "barretenberg/common/assert.hpp" #include "barretenberg/ecc/fields/field.hpp" #include -// TODO(#674): We need the functionality of BarycentricData for both field (native) and field_t (stdlib). The former -// is compatible with constexpr operations, and the former is not. The functions for computing the -// pre-computable arrays in BarycentricData need to be constexpr and it takes some trickery to share these functions -// with the non-constexpr setting. Right now everything is more or less duplicated across BarycentricDataCompileTime and -// BarycentricDataRunTime. There should be a way to share more of the logic. +/* Future improvements (see https://github.com/AztecProtocol/barretenberg/issues/10): The code works for its intended + * use but could be improved: Precomputing for all possible size pairs is + * probably feasible and might be a better solution than instantiating many instances separately. Then perhaps we could + * infer input type to `extend`. + */ +namespace bb { -/* TODO(https://github.com/AztecProtocol/barretenberg/issues/10): This could or should be improved in various ways. In - no particular order: - 1) Edge cases are not considered. One non-use case situation (I forget which) leads to a segfault. +/** + * @brief Helper to determine whether input is bb::field type + * @details Needed before BarycentricDataFunctions so that batch_invert can branch on it. + * + * @tparam T + */ +template struct is_field_type { + static constexpr bool value = false; +}; - 2) Precomputing for all possible size pairs is probably feasible and might be a better solution than instantiating - many instances separately. Then perhaps we could infer input type to `extend`. +template struct is_field_type> { + static constexpr bool value = true; +}; - 3) There should be more thorough testing of this class in isolation. - */ -namespace bb { +template inline constexpr bool is_field_type_v = is_field_type::value; /** - * @todo: TODO(https://github.com/AztecProtocol/barretenberg/issues/713) Optimize with lookup tables? + * @brief Shared computation logic for barycentric extension and evaluation data. + * @details All methods are constexpr. For native field types (bb::field) they are evaluated at compile time; + * for non-constexpr types (e.g. stdlib field_t) the constexpr qualifier is silently ignored and they execute at + * runtime. The two thin wrapper classes (BarycentricDataCompileTime, BarycentricDataRunTime) inherit these methods + * and only differ in how they declare their static data members (static constexpr vs inline static const). + * * @tparam domain_end specifies the given evaluation domain {0,..., domain_end - 1} * @tparam num_evals the number of evaluations that are computable with specific barycentric extension formula + * IMPROVEMENT : Can use lookup tables to optimize the computations. */ - -template class BarycentricDataCompileTime { +template class BarycentricDataFunctions { public: static constexpr size_t domain_size = domain_end; static constexpr size_t big_domain_size = std::max(domain_size, num_evals); + static_assert(domain_size > 0, "BarycentricData requires domain_size > 0"); + static_assert(num_evals > 0, "BarycentricData requires num_evals > 0"); + static_assert(num_evals >= domain_end || (!is_field_type_v && num_evals == 1), + "Expected num_evals >= domain_size (or stdlib num_evals==1 special case)"); + /** * Static constexpr methods for computing arrays of precomputable data used for barycentric extension and evaluation */ @@ -65,110 +82,16 @@ template class BarycentricDataCo return result; } - static constexpr std::array batch_invert( - const std::array& coeffs) - { - constexpr size_t n = domain_size * num_evals; - std::array temporaries{}; - std::array skipped{}; - Fr accumulator = 1; - for (size_t i = 0; i < n; ++i) { - temporaries[i] = accumulator; - if (coeffs[i] == 0) { - skipped[i] = true; - } else { - skipped[i] = false; - accumulator *= coeffs[i]; - } - } - accumulator = Fr(1) / accumulator; - std::array result{}; - Fr T0; - for (size_t i = n - 1; i < n; --i) { - if (!skipped[i]) { - T0 = accumulator * temporaries[i]; - accumulator *= coeffs[i]; - result[i] = T0; - } - } - return result; - } - // for each x_k in the big domain, build set of domain size-many denominator inverses - // 1/(d_i*(x_k - x_j)). will multiply against each of these (rather than to divide by something) - // for each barycentric evaluation - static constexpr std::array construct_denominator_inverses( - const auto& big_domain, const auto& lagrange_denominators) - { - std::array result{}; // default init to 0 since below does not init all elements - for (size_t k = domain_size; k < num_evals; ++k) { - for (size_t j = 0; j < domain_size; ++j) { - Fr inv = lagrange_denominators[j]; - inv *= (big_domain[k] - big_domain[j]); - result[k * domain_size + j] = inv; - } - } - return batch_invert(result); - } - - // get full numerator values - // full numerator is M(x) = \prod_{i} (x-x_i) - // these will be zero for i < domain_size, but that's ok because - // at such entries we will already have the evaluations of the polynomial - static constexpr std::array construct_full_numerator_values(const auto& big_domain) - { - std::array result; - for (size_t i = 0; i != num_evals; ++i) { - result[i] = 1; - Fr v_i = i; - for (size_t j = 0; j != domain_size; ++j) { - result[i] *= v_i - big_domain[j]; - } - } - return result; - } - - static constexpr auto big_domain = construct_big_domain(); - static constexpr auto lagrange_denominators = construct_lagrange_denominators(big_domain); - static constexpr auto precomputed_denominator_inverses = - construct_denominator_inverses(big_domain, lagrange_denominators); - static constexpr auto full_numerator_values = construct_full_numerator_values(big_domain); -}; - -template class BarycentricDataRunTime { - public: - static constexpr size_t domain_size = domain_end; - static constexpr size_t big_domain_size = std::max(domain_size, num_evals); - - /** - * Static constexpr methods for computing arrays of precomputable data used for barycentric extension and evaluation - */ - - // build big_domain, currently the set of x_i in {0, ..., big_domain_size - 1 } - static std::array construct_big_domain() + // Non-constexpr helper so we can use BB_ASSERT inside the constexpr batch_invert. + static void assert_constant(const Fr& val) { - std::array result; - for (size_t i = 0; i < big_domain_size; ++i) { - result[i] = static_cast(i); + if constexpr (!is_field_type_v) { + BB_ASSERT(val.is_constant(), "barycentric coeffs must be constants, not witnesses"); } - return result; } - // build set of lagrange_denominators d_i = \prod_{j!=i} x_i - x_j - static std::array construct_lagrange_denominators(const auto& big_domain) - { - std::array result; - for (size_t i = 0; i != domain_size; ++i) { - result[i] = 1; - for (size_t j = 0; j != domain_size; ++j) { - if (j != i) { - result[i] *= big_domain[i] - big_domain[j]; - } - } - } - return result; - } - - static std::array batch_invert(const std::array& coeffs) + static constexpr std::array batch_invert( + const std::array& coeffs) { constexpr size_t n = domain_size * num_evals; std::array temporaries{}; @@ -176,7 +99,19 @@ template class BarycentricDataRu Fr accumulator = 1; for (size_t i = 0; i < n; ++i) { temporaries[i] = accumulator; - if (coeffs[i].get_value() == 0) { + // For native field types, == 0 is a plain constexpr comparison. For stdlib field_t, + // operator== would create circuit constraints and return bool_t, so we use get_value() + // to extract the underlying native value for a plain comparison instead. Note that coeffs[] are derived + // from constants (big_domain[i] = Fr(i)) and products/differences thereof, so they are constants. This + // makes the get_value() based zero check safe. + bool is_zero = false; + if constexpr (is_field_type_v) { + is_zero = (coeffs[i] == 0); + } else { + assert_constant(coeffs[i]); + is_zero = (coeffs[i].get_value() == 0); + } + if (is_zero) { skipped[i] = true; } else { skipped[i] = false; @@ -195,23 +130,26 @@ template class BarycentricDataRu } return result; } + // for each x_k in the big domain, build set of domain size-many denominator inverses // 1/(d_i*(x_k - x_j)). will multiply against each of these (rather than to divide by something) // for each barycentric evaluation - static std::array construct_denominator_inverses(const auto& big_domain, - const auto& lagrange_denominators) + // special case for stdlib path: if num_evals == 1, we output the barycentric weights result[j] = 1 / d_j. + // This case does not arise on the native (compile-time) path. + static constexpr std::array construct_denominator_inverses( + const auto& big_domain, const auto& lagrange_denominators) { std::array result{}; // default init to 0 since below does not init all elements - if constexpr (num_evals == 1) { + if constexpr (!is_field_type_v && num_evals == 1) { result = lagrange_denominators; } else { // Used in Univariate's `extend_to` method to extend univariates given by > 4 evaluations ( deg>3 ) to a - // bigger evaluation domains. + // bigger evaluation domain. for (size_t k = domain_size; k < num_evals; ++k) { for (size_t j = 0; j < domain_size; ++j) { Fr inv = lagrange_denominators[j]; inv *= (big_domain[k] - big_domain[j]); - result[k * domain_size + j] = inv; + result[(k * domain_size) + j] = inv; } } } @@ -222,7 +160,7 @@ template class BarycentricDataRu // full numerator is M(x) = \prod_{i} (x-x_i) // these will be zero for i < domain_size, but that's ok because // at such entries we will already have the evaluations of the polynomial - static std::array construct_full_numerator_values(const auto& big_domain) + static constexpr std::array construct_full_numerator_values(const auto& big_domain) { std::array result; for (size_t i = 0; i != num_evals; ++i) { @@ -234,28 +172,45 @@ template class BarycentricDataRu } return result; } - - inline static const auto big_domain = construct_big_domain(); - inline static const auto lagrange_denominators = construct_lagrange_denominators(big_domain); - inline static const auto precomputed_denominator_inverses = - construct_denominator_inverses(big_domain, lagrange_denominators); - inline static const auto full_numerator_values = construct_full_numerator_values(big_domain); }; /** - * @brief Helper to determine whether input is bberg::field type - * - * @tparam T + * @brief Compile-time variant: precomputed arrays are static constexpr. + * @details Used for native field types (bb::field) where all operations are constexpr-compatible. */ -template struct is_field_type { - static constexpr bool value = false; -}; +template +class BarycentricDataCompileTime : public BarycentricDataFunctions { + using Base = BarycentricDataFunctions; -template struct is_field_type> { - static constexpr bool value = true; + public: + using Base::big_domain_size; + using Base::domain_size; + + static constexpr auto big_domain = Base::construct_big_domain(); + static constexpr auto lagrange_denominators = Base::construct_lagrange_denominators(big_domain); + static constexpr auto precomputed_denominator_inverses = + Base::construct_denominator_inverses(big_domain, lagrange_denominators); + static constexpr auto full_numerator_values = Base::construct_full_numerator_values(big_domain); }; -template inline constexpr bool is_field_type_v = is_field_type::value; +/** + * @brief Run-time variant: precomputed arrays are inline static const. + * @details Used for types whose operations are not constexpr-compatible (e.g. stdlib field_t). + */ +template +class BarycentricDataRunTime : public BarycentricDataFunctions { + using Base = BarycentricDataFunctions; + + public: + using Base::big_domain_size; + using Base::domain_size; + + inline static const auto big_domain = Base::construct_big_domain(); + inline static const auto lagrange_denominators = Base::construct_lagrange_denominators(big_domain); + inline static const auto precomputed_denominator_inverses = + Base::construct_denominator_inverses(big_domain, lagrange_denominators); + inline static const auto full_numerator_values = Base::construct_full_numerator_values(big_domain); +}; /** * @brief Exposes BarycentricData with compile time arrays if the type is bberg::field and runtime arrays otherwise diff --git a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.test.cpp index 3d1703b660c4..cd6e5df3ca1d 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/barycentric.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/barycentric.test.cpp @@ -1,4 +1,7 @@ +#include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" #include "univariate.hpp" #include @@ -97,3 +100,116 @@ TYPED_TEST(BarycentricDataTests, BarycentricData5to6) Univariate expected{ { 1, 3, 25, 109, 321, 751 } }; EXPECT_EQ(ext1, expected); } + +/** + * @brief Tests for the BarycentricDataRunTime path using stdlib field_t + */ +using Builder = bb::UltraCircuitBuilder; +using field_ct = bb::stdlib::field_t; +using witness_ct = bb::stdlib::witness_t; + +// Verify that BarycentricDataRunTime computes the same precomputed arrays as the compile-time native version +TEST(BarycentricDataRunTimeTests, DataArraysMatchCompileTime2to3) +{ + constexpr size_t domain_size = 2; + constexpr size_t num_evals = 3; + using RuntimeData = BarycentricDataRunTime; + using NativeData = BarycentricDataCompileTime; + + for (size_t i = 0; i < RuntimeData::big_domain_size; ++i) { + EXPECT_EQ(RuntimeData::big_domain[i].get_value(), NativeData::big_domain[i]); + } + for (size_t i = 0; i < domain_size; ++i) { + EXPECT_EQ(RuntimeData::lagrange_denominators[i].get_value(), NativeData::lagrange_denominators[i]); + } + for (size_t i = 0; i < domain_size * num_evals; ++i) { + EXPECT_EQ(RuntimeData::precomputed_denominator_inverses[i].get_value(), + NativeData::precomputed_denominator_inverses[i]); + } + for (size_t i = 0; i < num_evals; ++i) { + EXPECT_EQ(RuntimeData::full_numerator_values[i].get_value(), NativeData::full_numerator_values[i]); + } +} + +TEST(BarycentricDataRunTimeTests, DataArraysMatchCompileTime5to6) +{ + constexpr size_t domain_size = 5; + constexpr size_t num_evals = 6; + using RuntimeData = BarycentricDataRunTime; + using NativeData = BarycentricDataCompileTime; + + for (size_t i = 0; i < RuntimeData::big_domain_size; ++i) { + EXPECT_EQ(RuntimeData::big_domain[i].get_value(), NativeData::big_domain[i]); + } + for (size_t i = 0; i < domain_size; ++i) { + EXPECT_EQ(RuntimeData::lagrange_denominators[i].get_value(), NativeData::lagrange_denominators[i]); + } + for (size_t i = 0; i < domain_size * num_evals; ++i) { + EXPECT_EQ(RuntimeData::precomputed_denominator_inverses[i].get_value(), + NativeData::precomputed_denominator_inverses[i]); + } + for (size_t i = 0; i < num_evals; ++i) { + EXPECT_EQ(RuntimeData::full_numerator_values[i].get_value(), NativeData::full_numerator_values[i]); + } +} + +// Evaluate a linear polynomial f(X) = 1 + X at a witness point +TEST(BarycentricDataRunTimeTests, Evaluate) +{ + Builder builder; + constexpr size_t domain_size = 2; + + // f(X) = 1 + X: f(0)=1, f(1)=2 + field_ct v0 = witness_ct(&builder, bb::fr(1)); + field_ct v1 = witness_ct(&builder, bb::fr(2)); + auto f = Univariate(std::array{ v0, v1 }); + + field_ct u = witness_ct(&builder, bb::fr(5)); + auto result = f.evaluate(u); + EXPECT_EQ(result.get_value(), bb::fr(6)); // f(5) = 1 + 5 = 6 + + EXPECT_TRUE(CircuitChecker::check(builder)); +} + +// Extend a degree-1 polynomial from 2 to 10 evaluations +TEST(BarycentricDataRunTimeTests, Extend) +{ + Builder builder; + constexpr size_t domain_size = 2; + constexpr size_t num_evals = 10; + + // X + 1: f(0)=1, f(1)=2 + auto e1 = Univariate( + std::array{ witness_ct(&builder, bb::fr(1)), witness_ct(&builder, bb::fr(2)) }); + auto ext1 = e1.template extend_to(); + + std::array expected = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + for (size_t i = 0; i < num_evals; ++i) { + EXPECT_EQ(ext1.value_at(i).get_value(), expected[i]); + } + + EXPECT_TRUE(CircuitChecker::check(builder)); +} + +// Extend a degree-4 polynomial from 5 to 6 evaluations using the general barycentric path (LENGTH >= 5) +TEST(BarycentricDataRunTimeTests, Extend5to6) +{ + Builder builder; + constexpr size_t domain_size = 5; + constexpr size_t num_evals = 6; + + // X^4 + X^3 + 1: f(0)=1, f(1)=3, f(2)=25, f(3)=109, f(4)=321 + auto e1 = Univariate(std::array{ witness_ct(&builder, bb::fr(1)), + witness_ct(&builder, bb::fr(3)), + witness_ct(&builder, bb::fr(25)), + witness_ct(&builder, bb::fr(109)), + witness_ct(&builder, bb::fr(321)) }); + auto ext1 = e1.template extend_to(); + + std::array expected = { 1, 3, 25, 109, 321, 751 }; + for (size_t i = 0; i < num_evals; ++i) { + EXPECT_EQ(ext1.value_at(i).get_value(), expected[i]); + } + + EXPECT_TRUE(CircuitChecker::check(builder)); +} diff --git a/barretenberg/cpp/src/barretenberg/polynomials/eq_polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/eq_polynomial.hpp index 8ee60384dba9..7140304b60a4 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/eq_polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/eq_polynomial.hpp @@ -223,7 +223,7 @@ template struct VerifierEqPolynomial { // ---- Evaluate eq(X, r) at u ---- FF evaluate(std::span u) const { - assert(u.size() == r.size()); + BB_ASSERT_EQ(u.size(), r.size(), "expect u.size() == r.size()"); FF acc = FF(1); for (size_t i = 0; i < u.size(); ++i) { // term_i = b_i + u_i * a_i @@ -235,7 +235,7 @@ template struct VerifierEqPolynomial { // ---- Compute eq(r, u) without constructing the object ---- static FF eval(std::span r_in, std::span u) { - assert(r_in.size() == u.size()); + BB_ASSERT_EQ(r_in.size(), u.size(), "expect r_in.size() == u.size()"); FF acc = FF(1); for (size_t i = 0; i < r_in.size(); ++i) { const FF ai = r_in[i] + r_in[i] - FF(1); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp index 1fb902b01310..b00ae907decd 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.cpp @@ -35,6 +35,9 @@ void compute_lookup_table_single(const Fr& input_root, Fr* const roots, std::vector& round_roots) { + // num_rounds = 0 results in underflow in the loop below, so we require num_rounds >= 1, which is equivalent to size + // >= 2. + BB_ASSERT(size >= 2); const size_t num_rounds = static_cast(numeric::get_msb(size)); round_roots.emplace_back(&roots[0]); @@ -67,7 +70,6 @@ EvaluationDomain::EvaluationDomain(const size_t domain_size, const size_t ta , domain_inverse(domain.invert()) , generator(Fr::template coset_generator<0>()) , generator_inverse(Fr::template coset_generator<0>().invert()) - , four_inverse(Fr(4).invert()) , roots(nullptr) { // Grumpkin does not have many roots of unity and, given these are not used for Honk, we set it to one. @@ -93,13 +95,12 @@ EvaluationDomain::EvaluationDomain(const EvaluationDomain& other) , log2_thread_size(static_cast(numeric::get_msb(thread_size))) , log2_num_threads(static_cast(numeric::get_msb(num_threads))) , generator_size(other.generator_size) - , root(Fr::get_root_of_unity(log2_size)) - , root_inverse(root.invert()) + , root(other.root) + , root_inverse(other.root_inverse) , domain(Fr{ size, 0, 0, 0 }.to_montgomery_form()) , domain_inverse(domain.invert()) , generator(other.generator) , generator_inverse(other.generator_inverse) - , four_inverse(other.four_inverse) { BB_ASSERT((1UL << log2_size) == size); BB_ASSERT((1UL << log2_thread_size) == thread_size); @@ -130,13 +131,12 @@ EvaluationDomain::EvaluationDomain(EvaluationDomain&& other) , log2_thread_size(static_cast(numeric::get_msb(thread_size))) , log2_num_threads(static_cast(numeric::get_msb(num_threads))) , generator_size(other.generator_size) - , root(Fr::get_root_of_unity(log2_size)) - , root_inverse(root.invert()) + , root(other.root) + , root_inverse(other.root_inverse) , domain(Fr{ size, 0, 0, 0 }.to_montgomery_form()) , domain_inverse(domain.invert()) , generator(other.generator) , generator_inverse(other.generator_inverse) - , four_inverse(other.four_inverse) { roots = other.roots; round_roots = std::move(other.round_roots); @@ -159,8 +159,9 @@ template EvaluationDomain& EvaluationDomain::operator=(Eva Fr::__copy(other.domain_inverse, domain_inverse); Fr::__copy(other.generator, generator); Fr::__copy(other.generator_inverse, generator_inverse); - Fr::__copy(other.four_inverse, four_inverse); roots = nullptr; + round_roots.clear(); + inverse_round_roots.clear(); if (other.roots != nullptr) { roots = other.roots; round_roots = std::move(other.round_roots); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.hpp b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.hpp index fcab81469f11..b802072ea06b 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/evaluation_domain.hpp @@ -27,7 +27,6 @@ template class EvaluationDomain { , domain_inverse(FF::zero()) , generator(FF::zero()) , generator_inverse(FF::zero()) - , four_inverse(FF::zero()) , roots(nullptr) {}; EvaluationDomain(const size_t domain_size, const size_t target_generator_size = 0); @@ -40,7 +39,6 @@ template class EvaluationDomain { ~EvaluationDomain(); void compute_lookup_table(); - void compute_generator_table(const size_t target_generator_size); const std::vector& get_round_roots() const { return round_roots; }; const std::vector& get_inverse_round_roots() const { return inverse_round_roots; } @@ -59,7 +57,6 @@ template class EvaluationDomain { FF domain_inverse; // n^{-1} FF generator; FF generator_inverse; - FF four_inverse; private: std::vector round_roots; // An entry for each of the log(n) rounds: each entry is a pointer to diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp index bbd6860b5a81..e5b9d5ea022e 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp @@ -184,12 +184,6 @@ template Polynomial& Polynomial::operator+=(PolynomialSpan return *this; } -template Fr Polynomial::evaluate(const Fr& z, const size_t target_size) const -{ - BB_ASSERT(size() == virtual_size()); - return polynomial_arithmetic::evaluate(data(), z, target_size); -} - template Fr Polynomial::evaluate(const Fr& z) const { BB_ASSERT(size() == virtual_size()); @@ -201,13 +195,6 @@ template Fr Polynomial::evaluate_mle(std::span evalu return _evaluate_mle(evaluation_points, coefficients_, shift); } -template -Fr Polynomial::compute_barycentric_evaluation(const Fr& z, const EvaluationDomain& domain) - requires polynomial_arithmetic::SupportsFFT -{ - return polynomial_arithmetic::compute_barycentric_evaluation(data(), domain.size, z, domain); -} - template Polynomial& Polynomial::operator-=(PolynomialSpan other) { BB_ASSERT_LTE(start_index(), other.start_index); @@ -285,17 +272,6 @@ template Polynomial Polynomial::shifted() const return result; } -template Polynomial Polynomial::right_shifted(const size_t magnitude) const -{ - // ensure that at least the last magnitude-many coefficients are virtual 0's - BB_ASSERT_LTE((coefficients_.end_ + magnitude), virtual_size()); - Polynomial result; - result.coefficients_ = coefficients_; - result.coefficients_.start_ += magnitude; - result.coefficients_.end_ += magnitude; - return result; -} - template Polynomial Polynomial::reverse() const { const size_t end_index = this->end_index(); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp index df95c5e5f0b8..d8e90bc9fc2f 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -138,8 +138,6 @@ template class Polynomial { */ Polynomial share() const; - void clear() { coefficients_ = SharedShiftedVirtualZeroesArray{}; } - /** * @brief Check whether or not a polynomial is identically zero * @@ -178,12 +176,6 @@ template class Polynomial { */ Polynomial shifted() const; - /** - * @brief Returns a Polynomial equal to the right-shift-by-magnitude of self. - * @note Resulting Polynomial shares the memory of that used to generate it - */ - Polynomial right_shifted(const size_t magnitude) const; - /** * @brief Returns the polynomial equal to the reverse of self * @@ -209,9 +201,6 @@ template class Polynomial { */ Fr evaluate_mle(std::span evaluation_points, bool shift = false) const; - Fr compute_barycentric_evaluation(const Fr& z, const EvaluationDomain& domain) - requires polynomial_arithmetic::SupportsFFT; - /** * @brief Divides p(X) by (X-r) in-place. * Assumes that p(rⱼ)=0 for all j @@ -224,7 +213,6 @@ template class Polynomial { */ void factor_roots(const Fr& root) { polynomial_arithmetic::factor_roots(coeffs(), root); }; - Fr evaluate(const Fr& z, size_t target_size) const; Fr evaluate(const Fr& z) const; /** @@ -385,11 +373,9 @@ template class Polynomial { /** * @brief Copy over values from a vector that is of a convertible type. * - * @details There is an underlying assumption that the relevant start index in the vector - * corresponds to the start_index of the destination polynomial and also that the number of elements we want to copy - * corresponds to the size of the polynomial. This is quirky behavior and we might want to improve the UX. - * - * @todo https://github.com/AztecProtocol/barretenberg/issues/1292 + * @details Assumes that the relevant start index in the vector corresponds to the start_index of the destination + * polynomial and also that the number of elements we want to copy corresponds to the size of the polynomial. It is + * not intended to be a general-purpose method for vector copy and should be used with caution. * * @tparam T a convertible type * @param vec the vector @@ -403,26 +389,11 @@ template class Polynomial { } } - /* - * @brief For quick and dirty comparisons. ONLY for development and log use! - */ - Fr debug_hash() const - { - Fr result{ 0 }; - for (size_t i = start_index(); i < end_index(); i++) { - result += (*this)[i] * i; - } - return result; - } - private: // allocate a fresh memory pointer for backing memory // DOES NOT initialize memory void allocate_backing_memory(size_t size, size_t virtual_size, size_t start_index); - // safety check for in place operations - bool in_place_operation_viable(size_t domain_size) { return (size() >= domain_size); } - // The underlying memory, with a bespoke (but minimal) shared array struct that fits our needs. // Namely, it supports polynomial shifts and 'virtual' zeroes past a size up until a 'virtual' size. SharedShiftedVirtualZeroesArray coefficients_; @@ -464,9 +435,7 @@ Fr_ _evaluate_mle(std::span evaluation_points, size_t n_l = 1 << (dim - 1); // temporary buffer of half the size of the Polynomial - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1096): Make this a Polynomial with - // DontZeroMemory::FLAG - auto tmp_ptr = _allocate_aligned_memory(sizeof(Fr_) * n_l); + auto tmp_ptr = _allocate_aligned_memory(n_l); auto tmp = tmp_ptr.get(); size_t offset = 0; diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp index 8d6f3e52aca7..6fcd1a407b9f 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp @@ -29,36 +29,6 @@ TEST(Polynomial, Shifted) } } -// Simple test/demonstration of right_shifted functionality -TEST(Polynomial, RightShifted) -{ - using FF = bb::fr; - using Polynomial = bb::Polynomial; - const size_t SIZE = 10; - const size_t VIRTUAL_SIZE = 20; - const size_t START_IDX = 2; - const size_t END_IDX = SIZE + START_IDX; - const size_t SHIFT_MAGNITUDE = 5; - auto poly = Polynomial::random(SIZE, VIRTUAL_SIZE, START_IDX); - - // Instantiate the shift via the right_shifted method - auto poly_shifted = poly.right_shifted(SHIFT_MAGNITUDE); - - EXPECT_EQ(poly_shifted.size(), poly.size()); - EXPECT_EQ(poly_shifted.virtual_size(), poly.virtual_size()); - - // The shift is indeed the shift - for (size_t i = 0; i < END_IDX; ++i) { - EXPECT_EQ(poly_shifted.get(i + SHIFT_MAGNITUDE), poly.get(i)); - } - - // If I change the original polynomial, the shift is updated accordingly - poly.at(3) = 25; - for (size_t i = 0; i < END_IDX; ++i) { - EXPECT_EQ(poly_shifted.get(i + SHIFT_MAGNITUDE), poly.get(i)); - } -} - // Simple test/demonstration of reverse functionality TEST(Polynomial, Reversed) { @@ -70,7 +40,7 @@ TEST(Polynomial, Reversed) const size_t END_IDX = SIZE + START_IDX; auto poly = Polynomial::random(SIZE, VIRTUAL_SIZE, START_IDX); - // Instantiate the shift via the right_shifted method + // Instantiate the shift via the reverse method auto poly_reversed = poly.reverse(); EXPECT_EQ(poly_reversed.size(), poly.size()); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp index fd9fbddbd77d..36027de7d004 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.cpp @@ -64,166 +64,6 @@ void scale_by_generator(Fr* coeffs, } }); } -/** - * Compute multiplicative subgroup (g.X)^n. - * - * Compute the subgroup for X in roots of unity of (2^log2_subgroup_size)*n. - * X^n will loop through roots of unity (2^log2_subgroup_size). - * - * @param log2_subgroup_size Log_2 of the subgroup size. - * @param src_domain The domain of size n. - * @param subgroup_roots Pointer to the array for saving subgroup members. - * */ -template - requires SupportsFFT -void compute_multiplicative_subgroup(const size_t log2_subgroup_size, - const EvaluationDomain& src_domain, - Fr* subgroup_roots) -{ - size_t subgroup_size = 1UL << log2_subgroup_size; - // Step 1: get primitive 4th root of unity - Fr subgroup_root = Fr::get_root_of_unity(log2_subgroup_size); - - // Step 2: compute the cofactor term g^n - Fr accumulator = src_domain.generator; - for (size_t i = 0; i < src_domain.log2_size; ++i) { - accumulator.self_sqr(); - } - - // Step 3: fill array with subgroup_size values of (g.X)^n, scaled by the cofactor - subgroup_roots[0] = accumulator; - for (size_t i = 1; i < subgroup_size; ++i) { - subgroup_roots[i] = subgroup_roots[i - 1] * subgroup_root; - } -} - -template - requires SupportsFFT -void fft_inner_parallel(std::vector coeffs, - const EvaluationDomain& domain, - const Fr&, - const std::vector& root_table) -{ - auto scratch_space_ptr = get_scratch_space(domain.size); - auto scratch_space = scratch_space_ptr.get(); - - const size_t num_polys = coeffs.size(); - BB_ASSERT(is_power_of_two(num_polys)); - const size_t poly_size = domain.size / num_polys; - BB_ASSERT(is_power_of_two(poly_size)); - const size_t poly_mask = poly_size - 1; - const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); - - parallel_for(domain.num_threads, [&](size_t j) { - Fr temp_1; - Fr temp_2; - for (size_t i = (j * domain.thread_size); i < ((j + 1) * domain.thread_size); i += 2) { - uint32_t next_index_1 = (uint32_t)reverse_bits((uint32_t)i + 2, (uint32_t)domain.log2_size); - uint32_t next_index_2 = (uint32_t)reverse_bits((uint32_t)i + 3, (uint32_t)domain.log2_size); - __builtin_prefetch(&coeffs[next_index_1]); - __builtin_prefetch(&coeffs[next_index_2]); - - uint32_t swap_index_1 = (uint32_t)reverse_bits((uint32_t)i, (uint32_t)domain.log2_size); - uint32_t swap_index_2 = (uint32_t)reverse_bits((uint32_t)i + 1, (uint32_t)domain.log2_size); - - size_t poly_idx_1 = swap_index_1 >> log2_poly_size; - size_t elem_idx_1 = swap_index_1 & poly_mask; - size_t poly_idx_2 = swap_index_2 >> log2_poly_size; - size_t elem_idx_2 = swap_index_2 & poly_mask; - - Fr::__copy(coeffs[poly_idx_1][elem_idx_1], temp_1); - Fr::__copy(coeffs[poly_idx_2][elem_idx_2], temp_2); - scratch_space[i + 1] = temp_1 - temp_2; - scratch_space[i] = temp_1 + temp_2; - } - }); - - // hard code exception for when the domain size is tiny - we won't execute the next loop, so need to manually - // reduce + copy - if (domain.size <= 2) { - coeffs[0][0] = scratch_space[0]; - coeffs[0][1] = scratch_space[1]; - } - - // outer FFT loop - for (size_t m = 2; m < (domain.size); m <<= 1) { - parallel_for(domain.num_threads, [&](size_t j) { - Fr temp; - - // Ok! So, what's going on here? This is the inner loop of the FFT algorithm, and we want to break it - // out into multiple independent threads. For `num_threads`, each thread will evaluation `domain.size / - // num_threads` of the polynomial. The actual iteration length will be half of this, because we leverage - // the fact that \omega^{n/2} = -\omega (where \omega is a root of unity) - - // Here, `start` and `end` are used as our iterator limits, so that we can use our iterator `i` to - // directly access the roots of unity lookup table - const size_t start = j * (domain.thread_size >> 1); - const size_t end = (j + 1) * (domain.thread_size >> 1); - - // For all but the last round of our FFT, the roots of unity that we need, will be a subset of our - // lookup table. e.g. for a size 2^n FFT, the 2^n'th roots create a multiplicative subgroup of order 2^n - // the 1st round will use the roots from the multiplicative subgroup of order 2 : the 2'th roots of - // unity the 2nd round will use the roots from the multiplicative subgroup of order 4 : the 4'th - // roots of unity - // i.e. each successive FFT round will double the set of roots that we need to index. - // We have already laid out the `root_table` container so that each FFT round's roots are linearly - // ordered in memory. For all FFT rounds, the number of elements we're iterating over is greater than - // the size of our lookup table. We need to access this table in a cyclical fasion - i.e. for a subgroup - // of size x, the first x iterations will index the subgroup elements in order, then for the next x - // iterations, we loop back to the start. - - // We could implement the algorithm by having 2 nested loops (where the inner loop iterates over the - // root table), but we want to flatten this out - as for the first few rounds, the inner loop will be - // tiny and we'll have quite a bit of unneccesary branch checks For each iteration of our flattened - // loop, indexed by `i`, the element of the root table we need to access will be `i % (current round - // subgroup size)` Given that each round subgroup size is `m`, which is a power of 2, we can index the - // root table with a very cheap `i & (m - 1)` Which is why we have this odd `block_mask` variable - const size_t block_mask = m - 1; - - // The next problem to tackle, is we now need to efficiently index the polynomial element in - // `scratch_space` in our flattened loop If we used nested loops, the outer loop (e.g. `y`) iterates - // from 0 to 'domain size', in steps of 2 * m, with the inner loop (e.g. `z`) iterating from 0 to m. We - // have our inner loop indexer with `i & (m - 1)`. We need to add to this our outer loop indexer, which - // is equivalent to taking our indexer `i`, masking out the bits used in the 'inner loop', and doubling - // the result. i.e. polynomial indexer = (i & (m - 1)) + ((i & ~(m - 1)) >> 1) To simplify this, we - // cache index_mask = ~block_mask, meaning that our indexer is just `((i & index_mask) << 1 + (i & - // block_mask)` - const size_t index_mask = ~block_mask; - - // `round_roots` fetches the pointer to this round's lookup table. We use `numeric::get_msb(m) - 1` as - // our indexer, because we don't store the precomputed root values for the 1st round (because they're - // all 1). - const Fr* round_roots = root_table[static_cast(numeric::get_msb(m)) - 1]; - - // Finally, we want to treat the final round differently from the others, - // so that we can reduce out of our 'coarse' reduction and store the output in `coeffs` instead of - // `scratch_space` - if (m != (domain.size >> 1)) { - for (size_t i = start; i < end; ++i) { - size_t k1 = (i & index_mask) << 1; - size_t j1 = i & block_mask; - temp = round_roots[j1] * scratch_space[k1 + j1 + m]; - scratch_space[k1 + j1 + m] = scratch_space[k1 + j1] - temp; - scratch_space[k1 + j1] += temp; - } - } else { - for (size_t i = start; i < end; ++i) { - size_t k1 = (i & index_mask) << 1; - size_t j1 = i & block_mask; - - size_t poly_idx_1 = (k1 + j1) >> log2_poly_size; - size_t elem_idx_1 = (k1 + j1) & poly_mask; - size_t poly_idx_2 = (k1 + j1 + m) >> log2_poly_size; - size_t elem_idx_2 = (k1 + j1 + m) & poly_mask; - - temp = round_roots[j1] * scratch_space[k1 + j1 + m]; - coeffs[poly_idx_2][elem_idx_2] = scratch_space[k1 + j1] - temp; - coeffs[poly_idx_1][elem_idx_1] = scratch_space[k1 + j1] + temp; - } - } - }); - } -} template requires SupportsFFT @@ -249,13 +89,6 @@ void fft_inner_parallel( } }); - // hard code exception for when the domain size is tiny - we won't execute the next loop, so need to manually - // reduce + copy - if (domain.size <= 2) { - coeffs[0] = target[0]; - coeffs[1] = target[1]; - } - // outer FFT loop for (size_t m = 2; m < (domain.size); m <<= 1) { parallel_for(domain.num_threads, [&](size_t j) { @@ -320,37 +153,6 @@ void fft_inner_parallel( } } -template - requires SupportsFFT -void fft(Fr* coeffs, const EvaluationDomain& domain) -{ - fft_inner_parallel({ coeffs }, domain, domain.root, domain.get_round_roots()); -} - -template - requires SupportsFFT -void fft(Fr* coeffs, Fr* target, const EvaluationDomain& domain) -{ - fft_inner_parallel(coeffs, target, domain, domain.root, domain.get_round_roots()); -} - -template - requires SupportsFFT -void fft(std::vector coeffs, const EvaluationDomain& domain) -{ - fft_inner_parallel(coeffs, domain.size, domain.root, domain.get_round_roots()); -} - -template - requires SupportsFFT -void ifft(Fr* coeffs, const EvaluationDomain& domain) -{ - fft_inner_parallel({ coeffs }, domain, domain.root_inverse, domain.get_inverse_round_roots()); - ITERATE_OVER_DOMAIN_START(domain); - coeffs[i] *= domain.domain_inverse; - ITERATE_OVER_DOMAIN_END; -} - template requires SupportsFFT void ifft(Fr* coeffs, Fr* target, const EvaluationDomain& domain) @@ -361,144 +163,6 @@ void ifft(Fr* coeffs, Fr* target, const EvaluationDomain& domain) ITERATE_OVER_DOMAIN_END; } -template - requires SupportsFFT -void ifft(std::vector coeffs, const EvaluationDomain& domain) -{ - fft_inner_parallel(coeffs, domain, domain.root_inverse, domain.get_inverse_round_roots()); - - const size_t num_polys = coeffs.size(); - BB_ASSERT(is_power_of_two(num_polys)); - const size_t poly_size = domain.size / num_polys; - BB_ASSERT(is_power_of_two(poly_size)); - const size_t poly_mask = poly_size - 1; - const size_t log2_poly_size = (size_t)numeric::get_msb(poly_size); - - ITERATE_OVER_DOMAIN_START(domain); - coeffs[i >> log2_poly_size][i & poly_mask] *= domain.domain_inverse; - ITERATE_OVER_DOMAIN_END; -} - -template - requires SupportsFFT -void coset_fft(Fr* coeffs, const EvaluationDomain& domain) -{ - scale_by_generator(coeffs, coeffs, domain, Fr::one(), domain.generator, domain.generator_size); - fft(coeffs, domain); -} - -template - requires SupportsFFT -void coset_fft(Fr* coeffs, Fr* target, const EvaluationDomain& domain) -{ - scale_by_generator(coeffs, target, domain, Fr::one(), domain.generator, domain.generator_size); - fft(coeffs, target, domain); -} - -template - requires SupportsFFT -void coset_fft(std::vector coeffs, const EvaluationDomain& domain) -{ - const size_t num_polys = coeffs.size(); - BB_ASSERT(is_power_of_two(num_polys)); - const size_t poly_size = domain.size / num_polys; - const Fr generator_pow_n = domain.generator.pow(poly_size); - Fr generator_start = 1; - - for (size_t i = 0; i < num_polys; i++) { - scale_by_generator(coeffs[i], coeffs[i], domain, generator_start, domain.generator, poly_size); - generator_start *= generator_pow_n; - } - fft(coeffs, domain); -} - -template - requires SupportsFFT -void coset_fft(Fr* coeffs, - const EvaluationDomain& domain, - const EvaluationDomain&, - const size_t domain_extension) -{ - const size_t log2_domain_extension = static_cast(numeric::get_msb(domain_extension)); - Fr primitive_root = Fr::get_root_of_unity(domain.log2_size + log2_domain_extension); - - // Fr work_root = domain.generator.sqr(); - // work_root = domain.generator.sqr(); - auto scratch_space_ptr = get_scratch_space(domain.size * domain_extension); - auto scratch_space = scratch_space_ptr.get(); - - // Fr* temp_memory = static_cast(aligned_alloc(64, sizeof(Fr) * domain.size * - // domain_extension)); - - std::vector coset_generators(domain_extension); - coset_generators[0] = domain.generator; - for (size_t i = 1; i < domain_extension; ++i) { - coset_generators[i] = coset_generators[i - 1] * primitive_root; - } - for (size_t i = domain_extension - 1; i < domain_extension; --i) { - scale_by_generator(coeffs, coeffs + (i * domain.size), domain, Fr::one(), coset_generators[i], domain.size); - } - - for (size_t i = 0; i < domain_extension; ++i) { - fft_inner_parallel(coeffs + (i * domain.size), - scratch_space + (i * domain.size), - domain, - domain.root, - domain.get_round_roots()); - } - - if (domain_extension == 4) { - parallel_for(domain.num_threads, [&](size_t j) { - const size_t start = j * domain.thread_size; - const size_t end = (j + 1) * domain.thread_size; - for (size_t i = start; i < end; ++i) { - Fr::__copy(scratch_space[i], coeffs[(i << 2UL)]); - Fr::__copy(scratch_space[i + (1UL << domain.log2_size)], coeffs[(i << 2UL) + 1UL]); - Fr::__copy(scratch_space[i + (2UL << domain.log2_size)], coeffs[(i << 2UL) + 2UL]); - Fr::__copy(scratch_space[i + (3UL << domain.log2_size)], coeffs[(i << 2UL) + 3UL]); - } - }); - - for (size_t i = 0; i < domain.size; ++i) { - for (size_t j = 0; j < domain_extension; ++j) { - Fr::__copy(scratch_space[i + (j << domain.log2_size)], coeffs[(i << log2_domain_extension) + j]); - } - } - } else { - for (size_t i = 0; i < domain.size; ++i) { - for (size_t j = 0; j < domain_extension; ++j) { - Fr::__copy(scratch_space[i + (j << domain.log2_size)], coeffs[(i << log2_domain_extension) + j]); - } - } - } -} - -template - requires SupportsFFT -void coset_ifft(Fr* coeffs, const EvaluationDomain& domain) -{ - ifft(coeffs, domain); - scale_by_generator(coeffs, coeffs, domain, Fr::one(), domain.generator_inverse, domain.size); -} - -template - requires SupportsFFT -void coset_ifft(std::vector coeffs, const EvaluationDomain& domain) -{ - ifft(coeffs, domain); - - const size_t num_polys = coeffs.size(); - BB_ASSERT(is_power_of_two(num_polys)); - const size_t poly_size = domain.size / num_polys; - const Fr generator_inv_pow_n = domain.generator_inverse.pow(poly_size); - Fr generator_start = 1; - - for (size_t i = 0; i < num_polys; i++) { - scale_by_generator(coeffs[i], coeffs[i], domain, generator_start, domain.generator_inverse, poly_size); - generator_start *= generator_inv_pow_n; - } -} - template Fr evaluate(const Fr* coeffs, const Fr& z, const size_t n) { const size_t num_threads = get_num_cpus(); @@ -557,59 +221,6 @@ template Fr evaluate(const std::vector coeffs, const Fr& z, c return r; } -// Computes r = \sum_{i=0}^{num_coeffs-1} (L_{i+1}(ʓ).f_i) -// -// (ʓ^n - 1) -// Start with L_1(ʓ) = --------- -// n.(ʓ - 1) -// -// ʓ^n - 1 -// L_i(z) = L_1(ʓ.ω^{1-i}) = ------------------ -// n.(ʓ.ω^{1-i)} - 1) -// -fr compute_barycentric_evaluation(const fr* coeffs, - const size_t num_coeffs, - const fr& z, - const EvaluationDomain& domain) -{ - fr* denominators = static_cast(aligned_alloc(64, sizeof(fr) * num_coeffs)); - - fr numerator = z; - for (size_t i = 0; i < domain.log2_size; ++i) { - numerator.self_sqr(); - } - numerator -= fr::one(); - numerator *= domain.domain_inverse; // (ʓ^n - 1) / n - - denominators[0] = z - fr::one(); - fr work_root = domain.root_inverse; // ω^{-1} - for (size_t i = 1; i < num_coeffs; ++i) { - denominators[i] = - work_root * z; // denominators[i] will correspond to L_[i+1] (since our 'commented maths' notation indexes - // L_i from 1). So ʓ.ω^{-i} = ʓ.ω^{1-(i+1)} is correct for L_{i+1}. - denominators[i] -= fr::one(); // ʓ.ω^{-i} - 1 - work_root *= domain.root_inverse; - } - - fr::batch_invert(denominators, num_coeffs); - - fr result = fr::zero(); - - for (size_t i = 0; i < num_coeffs; ++i) { - fr temp = coeffs[i] * denominators[i]; // f_i * 1/(ʓ.ω^{-i} - 1) - result = result + temp; - } - - result = result * - numerator; // \sum_{i=0}^{num_coeffs-1} f_i * [ʓ^n - 1]/[n.(ʓ.ω^{-i} - 1)] - // = \sum_{i=0}^{num_coeffs-1} f_i * L_{i+1} - // (with our somewhat messy 'commented maths' convention that L_1 corresponds to the 0th coeff). - - aligned_free(denominators); - - return result; -} - // This function computes sum of all scalars in a given array. template Fr compute_sum(const Fr* src, const size_t n) { @@ -775,19 +386,8 @@ void compute_efficient_interpolation(const Fr* src, Fr* dest, const Fr* evaluati template fr evaluate(const fr*, const fr&, const size_t); template fr evaluate(const std::vector, const fr&, const size_t); -template void fft_inner_parallel(std::vector, const EvaluationDomain&, const fr&, const std::vector&); -template void fft(fr*, const EvaluationDomain&); -template void fft(fr*, fr*, const EvaluationDomain&); -template void fft(std::vector, const EvaluationDomain&); -template void coset_fft(fr*, const EvaluationDomain&); -template void coset_fft(fr*, fr*, const EvaluationDomain&); -template void coset_fft(std::vector, const EvaluationDomain&); -template void coset_fft(fr*, const EvaluationDomain&, const EvaluationDomain&, const size_t); -template void ifft(fr*, const EvaluationDomain&); +template void fft_inner_parallel(fr*, fr*, const EvaluationDomain&, const fr&, const std::vector&); template void ifft(fr*, fr*, const EvaluationDomain&); -template void ifft(std::vector, const EvaluationDomain&); -template void coset_ifft(fr*, const EvaluationDomain&); -template void coset_ifft(std::vector, const EvaluationDomain&); template fr compute_sum(const fr*, const size_t); template void compute_linear_polynomial_product(const fr*, fr*, const size_t); template void compute_efficient_interpolation(const fr*, fr*, const fr*, const size_t); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.hpp index 4a6654527c5d..db36d460b5ea 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.hpp @@ -32,64 +32,13 @@ template Fr evaluate(std::span coeffs, const Fr& z) }; template Fr evaluate(const std::vector coeffs, const Fr& z, const size_t large_n); -// 2. Compute a lookup table of the roots of unity, and suffer through cache misses from nonlinear access patterns template requires SupportsFFT -void fft_inner_parallel(std::vector coeffs, - const EvaluationDomain& domain, - const Fr&, - const std::vector& root_table); - -template - requires SupportsFFT -void fft(Fr* coeffs, const EvaluationDomain& domain); -template - requires SupportsFFT -void fft(Fr* coeffs, Fr* target, const EvaluationDomain& domain); -template - requires SupportsFFT -void fft(std::vector coeffs, const EvaluationDomain& domain); - -template - requires SupportsFFT -void coset_fft(Fr* coeffs, const EvaluationDomain& domain); -template - requires SupportsFFT -void coset_fft(Fr* coeffs, Fr* target, const EvaluationDomain& domain); -template - requires SupportsFFT -void coset_fft(std::vector coeffs, const EvaluationDomain& domain); -template - requires SupportsFFT -void coset_fft(Fr* coeffs, - const EvaluationDomain& small_domain, - const EvaluationDomain& large_domain, - const size_t domain_extension); - -template - requires SupportsFFT -void ifft(Fr* coeffs, const EvaluationDomain& domain); +void fft_inner_parallel( + Fr* coeffs, Fr* target, const EvaluationDomain& domain, const Fr&, const std::vector& root_table); template requires SupportsFFT void ifft(Fr* coeffs, Fr* target, const EvaluationDomain& domain); -template - requires SupportsFFT -void ifft(std::vector coeffs, const EvaluationDomain& domain); - -template - requires SupportsFFT -void coset_ifft(Fr* coeffs, const EvaluationDomain& domain); -template - requires SupportsFFT -void coset_ifft(std::vector coeffs, const EvaluationDomain& domain); - -// void populate_with_vanishing_polynomial(Fr* coeffs, const size_t num_non_zero_entries, const EvaluationDomain& -// src_domain, const EvaluationDomain& target_domain); - -fr compute_barycentric_evaluation(const fr* coeffs, - unsigned long num_coeffs, - const fr& z, - const EvaluationDomain& domain); // This function computes sum of all scalars in a given array. template Fr compute_sum(const Fr* src, const size_t n); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp index d56e805600d5..115d4919b443 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial_arithmetic.test.cpp @@ -30,71 +30,48 @@ TEST(polynomials, evaluate) EXPECT_EQ(eval1, eval2); } -TEST(polynomials, fft_with_small_degree) +TEST(polynomials, ifft_consistency) { constexpr size_t n = 16; - fr fft_transform[n]; - fr poly[n]; - - for (size_t i = 0; i < n; ++i) { - poly[i] = fr::random_element(); - fr::__copy(poly[i], fft_transform[i]); - } - auto domain = evaluation_domain(n); domain.compute_lookup_table(); - polynomial_arithmetic::fft(fft_transform, domain); - - fr work_root; - work_root = fr::one(); - fr expected; - for (size_t i = 0; i < n; ++i) { - expected = polynomial_arithmetic::evaluate(poly, work_root, n); - EXPECT_EQ((fft_transform[i] == expected), true); - work_root *= domain.root; - } -} -TEST(polynomials, split_polynomial_fft) -{ - constexpr size_t n = 256; - fr fft_transform[n]; - fr poly[n]; + std::array coeffs; + std::array values; + std::array values_copy; + std::array recovered; - for (size_t i = 0; i < n; ++i) { - poly[i] = fr::random_element(); - fr::__copy(poly[i], fft_transform[i]); + for (size_t k = 0; k < n; ++k) { + coeffs[k] = fr::random_element(); + values[k] = fr::zero(); + recovered[k] = fr::zero(); } - constexpr size_t num_poly = 4; - constexpr size_t n_poly = n / num_poly; - fr fft_transform_[num_poly][n_poly]; - for (size_t i = 0; i < n; ++i) { - fft_transform_[i / n_poly][i % n_poly] = poly[i]; + // compute values[j] = sum_k coeffs[k] * ω^{j*k} + for (size_t j = 0; j < n; ++j) { + fr acc = fr::zero(); + for (size_t k = 0; k < n; ++k) { + fr w = domain.root.pow(static_cast(j * k)); + acc += coeffs[k] * w; + } + values[j] = acc; + values_copy[j] = values[j]; } - auto domain = evaluation_domain(n); - domain.compute_lookup_table(); - polynomial_arithmetic::fft(fft_transform, domain); - polynomial_arithmetic::fft({ fft_transform_[0], fft_transform_[1], fft_transform_[2], fft_transform_[3] }, domain); - - fr work_root; - work_root = fr::one(); - fr expected; + // compute ifft of values, which should recover coeffs + polynomial_arithmetic::ifft(values.data(), recovered.data(), domain); - for (size_t i = 0; i < n; ++i) { - expected = polynomial_arithmetic::evaluate(poly, work_root, n); - EXPECT_EQ((fft_transform[i] == expected), true); - EXPECT_EQ(fft_transform_[i / n_poly][i % n_poly], fft_transform[i]); - work_root *= domain.root; + for (size_t k = 0; k < n; ++k) { + EXPECT_EQ(recovered[k], coeffs[k]); // check that ifft recovers coeffs + EXPECT_EQ(values[k], values_copy[k]); // check that ifft does not modify input values } } TEST(polynomials, split_polynomial_evaluate) { constexpr size_t n = 256; - fr fft_transform[n]; - fr poly[n]; + std::array fft_transform; + std::array poly; for (size_t i = 0; i < n; ++i) { poly[i] = fr::random_element(); @@ -111,213 +88,13 @@ TEST(polynomials, split_polynomial_evaluate) fr z = fr::random_element(); EXPECT_EQ(polynomial_arithmetic::evaluate( { fft_transform_[0], fft_transform_[1], fft_transform_[2], fft_transform_[3] }, z, n), - polynomial_arithmetic::evaluate(poly, z, n)); -} - -TEST(polynomials, basic_fft) -{ - constexpr size_t n = 1 << 14; - fr* data = (fr*)aligned_alloc(32, sizeof(fr) * n * 2); - fr* result = &data[0]; - fr* expected = &data[n]; - for (size_t i = 0; i < n; ++i) { - result[i] = fr::random_element(); - fr::__copy(result[i], expected[i]); - } - - auto domain = evaluation_domain(n); - domain.compute_lookup_table(); - polynomial_arithmetic::fft(result, domain); - polynomial_arithmetic::ifft(result, domain); - - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((result[i] == expected[i]), true); - } - aligned_free(data); -} - -TEST(polynomials, fft_ifft_consistency) -{ - constexpr size_t n = 256; - fr result[n]; - fr expected[n]; - for (size_t i = 0; i < n; ++i) { - result[i] = fr::random_element(); - fr::__copy(result[i], expected[i]); - } - - auto domain = evaluation_domain(n); - domain.compute_lookup_table(); - polynomial_arithmetic::fft(result, domain); - polynomial_arithmetic::ifft(result, domain); - - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((result[i] == expected[i]), true); - } -} - -TEST(polynomials, split_polynomial_fft_ifft_consistency) -{ - constexpr size_t n = 256; - constexpr size_t num_poly = 4; - fr result[num_poly][n]; - fr expected[num_poly][n]; - for (size_t j = 0; j < num_poly; j++) { - for (size_t i = 0; i < n; ++i) { - result[j][i] = fr::random_element(); - fr::__copy(result[j][i], expected[j][i]); - } - } - - auto domain = evaluation_domain(num_poly * n); - domain.compute_lookup_table(); - - std::vector coeffs_vec; - for (size_t j = 0; j < num_poly; j++) { - coeffs_vec.push_back(result[j]); - } - polynomial_arithmetic::fft(coeffs_vec, domain); - polynomial_arithmetic::ifft(coeffs_vec, domain); - - for (size_t j = 0; j < num_poly; j++) { - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((result[j][i] == expected[j][i]), true); - } - } -} - -TEST(polynomials, fft_coset_ifft_consistency) -{ - constexpr size_t n = 256; - fr result[n]; - fr expected[n]; - for (size_t i = 0; i < n; ++i) { - result[i] = fr::random_element(); - fr::__copy(result[i], expected[i]); - } - - auto domain = evaluation_domain(n); - domain.compute_lookup_table(); - fr T0; - T0 = domain.generator * domain.generator_inverse; - EXPECT_EQ((T0 == fr::one()), true); - - polynomial_arithmetic::coset_fft(result, domain); - polynomial_arithmetic::coset_ifft(result, domain); - - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((result[i] == expected[i]), true); - } -} - -TEST(polynomials, split_polynomial_fft_coset_ifft_consistency) -{ - constexpr size_t n = 256; - constexpr size_t num_poly = 4; - fr result[num_poly][n]; - fr expected[num_poly][n]; - for (size_t j = 0; j < num_poly; j++) { - for (size_t i = 0; i < n; ++i) { - result[j][i] = fr::random_element(); - fr::__copy(result[j][i], expected[j][i]); - } - } - - auto domain = evaluation_domain(num_poly * n); - domain.compute_lookup_table(); - - std::vector coeffs_vec; - for (size_t j = 0; j < num_poly; j++) { - coeffs_vec.push_back(result[j]); - } - polynomial_arithmetic::coset_fft(coeffs_vec, domain); - polynomial_arithmetic::coset_ifft(coeffs_vec, domain); - - for (size_t j = 0; j < num_poly; j++) { - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((result[j][i] == expected[j][i]), true); - } - } -} - -TEST(polynomials, fft_coset_ifft_cross_consistency) -{ - constexpr size_t n = 2; - fr expected[n]; - fr poly_a[4 * n]; - fr poly_b[4 * n]; - fr poly_c[4 * n]; - - for (size_t i = 0; i < n; ++i) { - poly_a[i] = fr::random_element(); - fr::__copy(poly_a[i], poly_b[i]); - fr::__copy(poly_a[i], poly_c[i]); - expected[i] = poly_a[i] + poly_c[i]; - expected[i] += poly_b[i]; - } - - for (size_t i = n; i < 4 * n; ++i) { - poly_a[i] = fr::zero(); - poly_b[i] = fr::zero(); - poly_c[i] = fr::zero(); - } - auto small_domain = evaluation_domain(n); - auto mid_domain = evaluation_domain(2 * n); - auto large_domain = evaluation_domain(4 * n); - small_domain.compute_lookup_table(); - mid_domain.compute_lookup_table(); - large_domain.compute_lookup_table(); - polynomial_arithmetic::coset_fft(poly_a, small_domain); - polynomial_arithmetic::coset_fft(poly_b, mid_domain); - polynomial_arithmetic::coset_fft(poly_c, large_domain); - - for (size_t i = 0; i < n; ++i) { - poly_a[i] = poly_a[i] + poly_c[4 * i]; - poly_a[i] = poly_a[i] + poly_b[2 * i]; - } - - polynomial_arithmetic::coset_ifft(poly_a, small_domain); - - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ((poly_a[i] == expected[i]), true); - } -} - -TEST(polynomials, barycentric_weight_evaluations) -{ - constexpr size_t n = 16; - - evaluation_domain domain(n); - - std::vector poly(n); - std::vector barycentric_poly(n); - - for (size_t i = 0; i < n / 2; ++i) { - poly[i] = fr::random_element(); - barycentric_poly[i] = poly[i]; - } - for (size_t i = n / 2; i < n; ++i) { - poly[i] = fr::zero(); - barycentric_poly[i] = poly[i]; - } - fr evaluation_point = fr{ 2, 0, 0, 0 }.to_montgomery_form(); - - fr result = - polynomial_arithmetic::compute_barycentric_evaluation(&barycentric_poly[0], n / 2, evaluation_point, domain); - - domain.compute_lookup_table(); - - polynomial_arithmetic::ifft(&poly[0], domain); - - fr expected = polynomial_arithmetic::evaluate(&poly[0], evaluation_point, n); - - EXPECT_EQ((result == expected), true); + polynomial_arithmetic::evaluate(poly.data(), z, n)); } TEST(polynomials, linear_poly_product) { constexpr size_t n = 64; - fr roots[n]; + std::array roots; fr z = fr::random_element(); fr expected = 1; @@ -327,7 +104,7 @@ TEST(polynomials, linear_poly_product) } fr dest[n + 1]; - polynomial_arithmetic::compute_linear_polynomial_product(roots, dest, n); + polynomial_arithmetic::compute_linear_polynomial_product(roots.data(), dest, n); fr result = polynomial_arithmetic::evaluate(dest, z, n + 1); EXPECT_EQ(result, expected); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp b/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp index 0a5d6fea4394..357cc323bcca 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp @@ -114,7 +114,7 @@ template struct SharedShiftedVirtualZeroesArray { * * Represents the first index after `start_` that is not backed by actual memory. Note however that * the backed memory might extend beyond end_ index but will not be accessed anymore. Namely, any - * access after after end_ returns zero. (Happens after Polynomial::shrink_end_index() call). + * access after end_ returns zero. (Happens after Polynomial::shrink_end_index() call). */ size_t end_ = 0; diff --git a/barretenberg/cpp/src/barretenberg/polynomials/univariate.hpp b/barretenberg/cpp/src/barretenberg/polynomials/univariate.hpp index 44f9e2abeccd..fd453f475d4c 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/univariate.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/univariate.hpp @@ -38,7 +38,6 @@ template class Univariate { using value_type = Fr; // used to get the type of the elements consistently with std::array - // TODO(https://github.com/AztecProtocol/barretenberg/issues/714) Try out std::valarray? std::array evaluations; Univariate() = default; @@ -359,7 +358,6 @@ template class Univariate { std::copy(evaluations.begin(), evaluations.end(), result.evaluations.begin()); - static constexpr Fr inverse_two = Fr(2).invert(); if constexpr (LENGTH == 2) { // f = b x + c // f(0) = c @@ -372,8 +370,7 @@ template class Univariate { result.value_at(idx + 1) = result.value_at(idx) + delta; } } else if constexpr (LENGTH == 3) { - // Based off https://hackmd.io/@aztec-network/SyR45cmOq?type=view - // The technique used here is the same as the length == 3 case below. + static constexpr Fr inverse_two = Fr(2).invert(); // f = a x^2 + b x + c // f(0) = c // f(1) = a + b + c @@ -382,6 +379,8 @@ template class Univariate { // Hence, a = (f(2) + f(0) - 2f(1)) / 2 // b = f(1) - a - f(0) // f(i+1) = f(i) + 2a * i + b + a + // Cost note: after computing a,b, extending several points costs a few adds per point (no + // inversions), vs the generic barycentric path. Fr a = (value_at(2) + value_at(0)) * inverse_two - value_at(1); Fr b = value_at(1) - a - value_at(0); Fr a2 = a + a; diff --git a/barretenberg/cpp/src/barretenberg/polynomials/univariate.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/univariate.test.cpp index c4c082b8081e..2337e567fd5c 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/univariate.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/univariate.test.cpp @@ -137,17 +137,3 @@ TYPED_TEST(UnivariateTest, Serialization) EXPECT_EQ(univariate.value_at(i), deserialized_univariate.value_at(i)); } } - -// DISABLED: domain_start parameter removed -// TYPED_TEST(UnivariateTest, EvaluationCustomDomain) -// { -// []() { -// auto poly = Univariate(std::array{ 1, 2 }); -// EXPECT_EQ(poly.evaluate(fr(5)), fr(5)); -// }(); -// -// []() { -// auto poly = Univariate(std::array{ 1, 11, 111, 1111, 11111 }); -// EXPECT_EQ(poly.evaluate(fr(2)), fr(294330751)); -// }(); -// } diff --git a/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.hpp b/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.hpp index 90e891cfc1e4..53b8396b17d2 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.hpp @@ -47,7 +47,7 @@ template class UnivariateCoef * @details This class represents a polynomial P(X) = a0 + a1.X + a2.X^2 * We define `coefficients[0] = a0` and `coefficients[1] = a1` * If LENGTH == 2 AND `has_a0_plus_a1 = true` then `coefficients[2] = a0 + a1` - * If LENGTH == 3 then `coefficients[3] = a2` + * If LENGTH == 3 then `coefficients[2] = a2` */ std::array coefficients; @@ -80,55 +80,6 @@ template class UnivariateCoef } }; - size_t size() { return coefficients.size(); }; - - // Check if the UnivariateCoefficientBasis is identically zero - bool is_zero() const - requires(LENGTH == 2) - { - return coefficients[0].is_zero() || coefficients[1].is_zero(); - } - - // Check if the UnivariateCoefficientBasis is identically zero - bool is_zero() const - requires(LENGTH == 3) - { - return coefficients[2].is_zero() || coefficients[0].is_zero() || coefficients[1].is_zero(); - } - - // Write the Univariate coefficients to a buffer - [[nodiscard]] std::vector to_buffer() const { return ::to_buffer(coefficients); } - - // Static method for creating a Univariate from a buffer - // IMPROVEMENT: Could be made to identically match equivalent methods in e.g. field.hpp. Currently bypasses - // unnecessary ::from_buffer call - static UnivariateCoefficientBasis serialize_from_buffer(uint8_t const* buffer) - { - UnivariateCoefficientBasis result; - std::read(buffer, result.coefficients); - return result; - } - - static UnivariateCoefficientBasis get_random() - { - auto output = UnivariateCoefficientBasis(); - for (size_t i = 0; i < LENGTH; ++i) { - output.value_at(i) = Fr::random_element(); - } - return output; - }; - - static UnivariateCoefficientBasis zero() - { - auto output = UnivariateCoefficientBasis(); - for (size_t i = 0; i != LENGTH; ++i) { - output.coefficients[i] = Fr::zero(); - } - return output; - } - - static UnivariateCoefficientBasis random_element() { return get_random(); }; - // Operations between UnivariateCoefficientBasis and other UnivariateCoefficientBasis bool operator==(const UnivariateCoefficientBasis& other) const = default; @@ -326,13 +277,6 @@ template class UnivariateCoef } return os; } - - // Begin iterators - auto begin() { return coefficients.begin(); } - auto begin() const { return coefficients.begin(); } - // End iterators - auto end() { return coefficients.end(); } - auto end() const { return coefficients.end(); } }; template diff --git a/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.test.cpp index 5cd61eda8c2a..6239c92e1792 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/univariate_coefficient_basis.test.cpp @@ -48,27 +48,3 @@ TYPED_TEST(UnivariateCoefficientBasisTest, Multiplication) Univariate expected = (f1.template extend_to<3>()) * (f2.template extend_to<3>()); EXPECT_EQ(result, expected); } - -TYPED_TEST(UnivariateCoefficientBasisTest, Serialization) -{ - const size_t LENGTH = 2; - std::array evaluations; - - for (size_t i = 0; i < LENGTH; ++i) { - evaluations[i] = fr::random_element(); - } - - // Instantiate a Univariate from the evaluations - auto univariate = Univariate(evaluations); - UnivariateCoefficientBasis univariate_m(univariate); - // Serialize univariate to buffer - std::vector buffer = univariate_m.to_buffer(); - - // Deserialize - auto deserialized_univariate = - Univariate(UnivariateCoefficientBasis::serialize_from_buffer(&buffer[0])); - - for (size_t i = 0; i < LENGTH; ++i) { - EXPECT_EQ(univariate.value_at(i), deserialized_univariate.value_at(i)); - } -}