diff --git a/tachyon/math/polynomial/domains/mixed_radix/BUILD.bazel b/tachyon/math/polynomial/domains/mixed_radix/BUILD.bazel new file mode 100644 index 0000000000..c389892cfb --- /dev/null +++ b/tachyon/math/polynomial/domains/mixed_radix/BUILD.bazel @@ -0,0 +1,28 @@ +load("//bazel:tachyon_cc.bzl", "tachyon_cc_library", "tachyon_cc_test") + +package(default_visibility = ["//visibility:public"]) + +tachyon_cc_library( + name = "mixed_radix_evaluation_domain", + hdrs = ["mixed_radix_evaluation_domain.h"], + deps = [ + "//tachyon/base:logging", + "//tachyon/base/strings:string_number_conversions", + "//tachyon/math/base/gmp:gmp_util", + "//tachyon/math/finite_fields:prime_field_base", + "//tachyon/math/polynomial/domains:evaluation_domain", + "//tachyon/math/polynomial/domains:evaluation_domain_util", + ], +) + +tachyon_cc_test( + name = "mixed_radix_evaluation_domain_unittest", + size = "small", + srcs = ["mixed_radix_evaluation_domain_unittest.cc"], + deps = [ + ":mixed_radix_evaluation_domain", + "//tachyon/math/elliptic_curves/bn/bn384_small_two_adicity:fq", + "//tachyon/math/polynomial/domains:evaluation_domain", + "//tachyon/math/polynomial/polynomials/univariate:univariate_polynomial", + ], +) diff --git a/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain.h b/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain.h new file mode 100644 index 0000000000..bf544b79b7 --- /dev/null +++ b/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain.h @@ -0,0 +1,356 @@ +// Copyright 2022 arkworks contributors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.arkworks and the LICENCE-APACHE.arkworks +// file. + +// This header contains a |MixedRadixEvaluationDomain| for performing various +// kinds of polynomial arithmetic on top of fields that are FFT-friendly but do +// not have high-enough two-adicity to perform the FFT efficiently, i.e. the +// multiplicative subgroup G generated by |F::Config::kTwoAdicRootOfUnity| is +// not large enough. |MixedRadixEvaluationDomain| resolves this issue by using a +// larger subgroup obtained by combining G with another subgroup of size +// |F::Config::kSmallSubgroupBase|^|F::Config::kSmallSubgroupAdicity|, to obtain +// a subgroup generated by |F::Config::kLargeSubgroupRootOfUnity|. + +#ifndef TACHYON_MATH_POLYNOMIAL_DOMAINS_MIXED_RADIX_MIXED_RADIX_EVALUATION_DOMAIN_H_ +#define TACHYON_MATH_POLYNOMIAL_DOMAINS_MIXED_RADIX_MIXED_RADIX_EVALUATION_DOMAIN_H_ + +#include +#include + +#include "tachyon/base/logging.h" +#include "tachyon/base/strings/string_number_conversions.h" +#include "tachyon/math/base/gmp/gmp_util.h" +#include "tachyon/math/finite_fields/prime_field_util.h" +#include "tachyon/math/polynomial/domains/evaluation_domain.h" +#include "tachyon/math/polynomial/domains/evaluation_domain_util.h" + +namespace tachyon::math { + +// Defines a domain over which finite field (I)FFTs can be performed. Works only +// for fields that have a multiplicative subgroup of size that is a power-of-2 +// and another small subgroup over a different base defined. +template +class MixedRadixEvaluationDomain + : public EvaluationDomain> { + public: + using Field = F; + + constexpr MixedRadixEvaluationDomain() = default; + // Construct a domain that is large enough for evaluations of a polynomial + // having |num_coeffs| coefficients. + constexpr explicit MixedRadixEvaluationDomain(size_t num_coeffs) { + CHECK(F::Config::kHasLargeSubgroupRootOfUnity); + + // Compute the best size of our evaluation domain. + size_ = BestMixedDomainSize(num_coeffs); + + // Compute the size of our evaluation domain + uint64_t q = static_cast(Field::Config::kSmallSubgroupBase); + uint32_t q_adicity = + ComputeAdicity(q, gmp::FromDecString(base::NumberToString(size_))); + uint64_t q_part = static_cast(std::pow(q, q_adicity)); + + uint32_t two_adicity = + ComputeAdicity(2, gmp::FromDecString(base::NumberToString(size_))); + log_size_of_group_ = two_adicity; + uint64_t two_part = static_cast(std::pow(2, two_adicity)); + CHECK(size_ == q_part * two_part); + size_as_field_element_ = Field::FromDecString(base::NumberToString(size_)); + size_inv_ = size_as_field_element_.Inverse(); + + // Compute the generator for the multiplicative subgroup. + // It should be the |num_coeffs| root of unity. + Field::GetRootOfUnity(size_, &group_gen_); + // Check that it is indeed the requested root of unity. + DCHECK(group_gen_.Pow(BigInt<1>(size_)) == Field::One()); + group_gen_inv_ = group_gen_.Inverse(); + offset_ = Field::One(); + offset_inv_ = Field::One(); + offset_pow_size = Field::One(); + } + + constexpr MixedRadixEvaluationDomain GetCoset(Field offset) { + MixedRadixEvaluationDomain coset; + coset.size_ = size_; + coset.log_size_of_group_ = log_size_of_group_; + coset.size_as_field_element_ = size_as_field_element_; + coset.size_inv_ = size_inv_; + coset.group_gen_ = group_gen_; + coset.group_gen_inv_ = group_gen_inv_; + coset.offset_ = offset; + coset.offset_inv_ = offset.Inverse(); + coset.offset_pow_size = offset.Pow(BigInt<1>(size_)); + return coset; + } + + constexpr static bool ComputeSizeOfDomain(uint64_t num_coeffs, + uint64_t* size) { + if (F::Config::kHasLargeSubgroupRootOfUnity) { + // Compute the best size of our evaluation domain. + *size = BestMixedDomainSize(num_coeffs); + + uint64_t q = static_cast(Field::Config::kSmallSubgroupBase); + uint32_t q_adicity = + ComputeAdicity(q, gmp::FromDecString(base::NumberToString(*size))); + uint64_t q_part = static_cast(std::pow(q, q_adicity)); + + uint32_t two_adicity = + ComputeAdicity(2, gmp::FromDecString(base::NumberToString(*size))); + uint64_t two_part = static_cast(std::pow(2, two_adicity)); + + if (*size != q_part * two_part) { + LOG(ERROR) << "Cannot compute a mixed radix domain of size " << *size + << " for a polynomial of degree " << num_coeffs + << " (best size is " << q_part * two_part << ")"; + return false; + } + return true; + } else { + LOG(ERROR) << "Small subgroup base must be defined for the field to use " + << "a mixed radix domain."; + return false; + } + } + + constexpr uint64_t GetSize() const { return size_; } + + constexpr uint32_t GetLogSizeOfGroup() const { return log_size_of_group_; } + + constexpr Field GetSizeAsFieldElement() const { + return size_as_field_element_; + } + + constexpr Field GetGroupGen() const { return group_gen_; } + + constexpr Field GetGroupGenInv() const { return group_gen_inv_; } + + constexpr Field GetCosetOffset() const { return offset_; } + + constexpr Field GetCosetOffsetInv() const { return offset_inv_; } + + constexpr Field GetCosetOffsetPowSize() const { return offset_pow_size; } + + template + constexpr void FFTInPlace(DensePoly& poly) { + if (!offset_.IsOne()) { + EvaluationDomain>::DistributePowers( + poly, offset_); + } + poly.coefficients_.coefficients_.resize(size_, Field::Zero()); + SerialMixedRadixFFT(poly, group_gen_, log_size_of_group_); + } + + template + constexpr void IFFTInPlace(DensePoly& eval) { + eval.coefficients_.coefficients_.resize(size_, Field::Zero()); + SerialMixedRadixFFT(eval, group_gen_inv_, log_size_of_group_); + if (offset_.IsOne()) { + for (Field& coeff : eval.coefficients_.coefficients_) { + coeff *= size_inv_; + } + } else { + EvaluationDomain>:: + DistributePowersAndMulByConst(eval, offset_inv_, size_inv_); + } + } + + private: + friend class EvaluationDomain>; + + constexpr size_t MixedRadixFFTPermute(uint32_t two_adicity, + uint32_t q_adicity, size_t q, size_t n, + size_t i) { + // This is the permutation obtained by splitting into 2 groups |two_adicity| + // times and then |q| groups |q_adicity| many times. It can be efficiently + // described as follows i = 2^0 b_0 + 2^1 b_1 + ... + 2^{|two_adicity| - 1} + // b_{|two_adicity| - 1} + 2^|two_adicity| ( x_0 + |q|^1 x_1 + .. + + // |q|^{|q_adicity|-1} x_{|q_adicity|-1}). We want to return + // j = b_0 (|n|/2) + b_1 (|n|/ 2^2) + ... + b_{|two_adicity|-1} + // (|n|/2^|two_adicity|) + x_0 (|n| / 2^|two_adicity| / |q|) + .. + + // x_{|q_adicity|-1} (|n| / 2^|two_adicity| / |q|^|q_adicity|). + size_t res = 0; + size_t shift = n; + for (size_t j = 0; j < two_adicity; ++j) { + shift /= 2; + res += (i % 2) * shift; + i /= 2; + } + for (size_t j = 0; j < q_adicity; ++j) { + shift /= q; + res += (i % q) * shift; + i /= q; + } + return res; + } + + constexpr static uint64_t BestMixedDomainSize(uint64_t min_size) { + uint64_t best = UINT64_MAX; + uint32_t small_subgroup_base = Field::Config::kSmallSubgroupBase; + uint32_t small_subgroup_adicity = Field::Config::kSmallSubgroupAdicity; + for (size_t i = 0; i <= small_subgroup_adicity; ++i) { + uint64_t r = static_cast(std::pow(small_subgroup_base, i)); + uint32_t two_adicity = 0; + while (r < min_size) { + r *= 2; + two_adicity += 1; + } + if (two_adicity <= Field::Config::kTwoAdicity) { + best = std::min(best, r); + } + } + return best; + } + + // TODO(TomTaehoonKim): Implement ParallelFFT and BestFFT. (See + // https://github.com/arkworks-rs/algebra/blob/master/poly/src/domain/utils.rs#L105) + template + void SerialMixedRadixFFT(DensePoly& a, Field omega, uint32_t two_adicity) { + // Conceptually, this FFT first splits into 2 sub-arrays |two_adicity| many + // times, and then splits into q sub-arrays |q_adicity| many times. + + size_t n = a.coefficients_.coefficients_.size(); + uint32_t q = Field::Config::kSmallSubgroupBase; + uint64_t q_u64 = static_cast(Field::Config::kSmallSubgroupBase); + uint64_t n_u64 = static_cast(n); + + uint32_t q_adicity = + ComputeAdicity(q_u64, gmp::FromDecString(base::NumberToString(n_u64))); + uint64_t q_part = static_cast(std::pow(q_u64, q_adicity)); + uint64_t two_part = + static_cast(std::pow(uint64_t(2), two_adicity)); + + CHECK(n_u64 == q_part * two_part); + + size_t m = 1; + if (q_adicity > 0) { + // If we're using the other radix, we have to do two things differently + // than in the radix 2 case. 1. Applying the index permutation is a bit + // more complicated. It isn't an involution (like it is in the radix 2 + // case) so we need to remember which elements we've moved as we go along + // and can't use the trick of just swapping when processing the first + // element of a 2-cycle. + // + // 2. We need to do |q_adicity| many merge passes, each of which is a bit + // more complicated than the specialized q=2 case. + + // Applying the permutation + std::vector seen(n, false); + for (size_t k = 0; k < n; ++k) { + size_t i = k; + Field& a_i = a.coefficients_.coefficients_[i]; + while (!seen[i]) { + size_t dest = MixedRadixFFTPermute(two_adicity, q_adicity, q, n, i); + + Field a_dest = a.coefficients_.coefficients_[dest]; + a.coefficients_.coefficients_[dest] = a_i; + + seen[i] = true; + + a_i = a_dest; + i = dest; + } + } + + Field omega_q = omega.Pow(BigInt<1>(n / q)); + std::vector qth_roots(q, Field::One()); + for (size_t i = 1; i < q; ++i) { + qth_roots[i] = qth_roots[i - 1] * omega_q; + } + + std::vector terms(q - 1, Field::Zero()); + + // Doing the q_adicity passes. + for (size_t _i = 0; _i < q_adicity; ++_i) { + Field w_m = omega.Pow(BigInt<1>(n / (q * m))); + size_t k = 0; + while (k < n) { + Field w_j = Field::One(); // w_j is omega_m ^ j + for (size_t j = 0; j < m; ++j) { + Field base_term = a.coefficients_.coefficients_[k + j]; + Field w_j_i = w_j; + for (size_t i = 1; i < q; ++i) { + terms[i - 1] = a.coefficients_.coefficients_[k + j + i * m]; + terms[i - 1] *= w_j_i; + w_j_i *= w_j; + } + + for (size_t i = 0; i < q; ++i) { + a.coefficients_.coefficients_[k + j + i * m] = base_term; + for (size_t l = 1; l < q; ++l) { + Field tmp = terms[l - 1]; + tmp *= qth_roots[(i * l) % q]; + a.coefficients_.coefficients_[k + j + i * m] += tmp; + } + } + + w_j *= w_m; + } + + k += q * m; + } + m *= q; + } + } else { + // Swapping in place (from Storer's book) + for (uint64_t k = 0; k < n; ++k) { + uint64_t rk = BitRev(k, two_adicity); + if (k < rk) { + std::swap(a.coefficients_.coefficients_[k], + a.coefficients_.coefficients_[rk]); + } + } + } + + for (size_t _i = 0; _i < two_adicity; ++_i) { + // w_m is 2^|two_adicity|-th root of unity now + Field w_m = omega.Pow(BigInt<1>(n / (2 * m))); + + size_t k = 0; + while (k < n) { + Field w = Field::One(); + for (size_t j = 0; j < m; ++j) { + Field t = a.coefficients_.coefficients_[(k + m) + j]; + t *= w; + a.coefficients_.coefficients_[(k + m) + j] = + a.coefficients_.coefficients_[k + j]; + a.coefficients_.coefficients_[(k + m) + j] -= t; + a.coefficients_.coefficients_[k + j] += t; + w *= w_m; + } + k += 2 * m; + } + m *= 2; + } + } + + // The size of the domain. + uint64_t size_; + // log2(|size_|). + uint32_t log_size_of_group_; + // Size of the domain as a field element. + Field size_as_field_element_; + // Inverse of the size in the field. + Field size_inv_; + // A generator of the subgroup. + Field group_gen_; + // Inverse of the generator of the subgroup. + Field group_gen_inv_; + // Offset that specifies the coset. + Field offset_; + // Inverse of the offset that specifies the coset. + Field offset_inv_; + // Constant coefficient for the vanishing polynomial. + // Equals |offset_|^|size_|. + Field offset_pow_size; +}; + +template +class EvaluationDomainTraits> { + public: + using Field = F; +}; + +} // namespace tachyon::math + +#endif // TACHYON_MATH_POLYNOMIAL_DOMAINS_MIXED_RADIX_MIXED_RADIX_EVALUATION_DOMAIN_H_ diff --git a/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain_unittest.cc b/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain_unittest.cc new file mode 100644 index 0000000000..7517cddb84 --- /dev/null +++ b/tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain_unittest.cc @@ -0,0 +1,212 @@ +// Copyright 2022 arkworks contributors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.arkworks and the LICENCE-APACHE.arkworks +// file. + +#include "tachyon/math/polynomial/domains/mixed_radix/mixed_radix_evaluation_domain.h" + +#include "gtest/gtest.h" + +#include "tachyon/math/elliptic_curves/bn/bn384_small_two_adicity/fq.h" +#include "tachyon/math/polynomial/polynomials/univariate/univariate_polynomial.h" + +namespace tachyon::math { + +namespace { + +template +class MixedRadixEvaluationDomainTest : public testing::Test { + public: + static void SetUpTestSuite() { PrimeFieldType::Init(); } +}; + +using PrimeFieldTypes = testing::Types; + +TYPED_TEST_SUITE(MixedRadixEvaluationDomainTest, PrimeFieldTypes); + +TYPED_TEST(MixedRadixEvaluationDomainTest, VanishingPolynomialEvaluation) { + using F = TypeParam; + + for (size_t coeffs = 1; coeffs < 12; ++coeffs) { + MixedRadixEvaluationDomain domain(coeffs); + MixedRadixEvaluationDomain coset_domain = + domain.GetCoset(F::FromMontgomery(F::Config::kSubgroupGenerator)); + auto z = domain.GetVanishingPolynomial(); + auto z_coset = coset_domain.GetVanishingPolynomial(); + for (size_t _i = 0; _i < 100; ++_i) { + F point = F::Random(); + EXPECT_EQ(z.Evaluate(point), domain.EvaluateVanishingPolynomial(point)); + EXPECT_EQ(z_coset.Evaluate(point), + coset_domain.EvaluateVanishingPolynomial(point)); + } + } +} + +TYPED_TEST(MixedRadixEvaluationDomainTest, + VanishingPolynomialVanishesOnDomain) { + using F = TypeParam; + + // NOTE(TomTaehoonKim): In arkworks, the test is done for 1..1000, but it + // takes ~300s with size = "enormous" in Tachyon, so we only test for 1..10. + for (size_t coeffs = 0; coeffs < 10; ++coeffs) { + MixedRadixEvaluationDomain domain(coeffs); + auto z = domain.GetVanishingPolynomial(); + for (F& element : domain.GetElements()) { + EXPECT_TRUE(z.Evaluate(element).IsZero()); + } + + MixedRadixEvaluationDomain coset_domain = + domain.GetCoset(F::FromMontgomery(F::Config::kSubgroupGenerator)); + auto z_coset = coset_domain.GetVanishingPolynomial(); + for (F& element : coset_domain.GetElements()) { + EXPECT_TRUE(z_coset.Evaluate(element).IsZero()); + } + } +} + +TYPED_TEST(MixedRadixEvaluationDomainTest, SizeOfElements) { + using F = TypeParam; + + for (size_t coeffs = 1; coeffs < 12; ++coeffs) { + size_t size = 1 << coeffs; + MixedRadixEvaluationDomain domain(size); + EXPECT_EQ(domain.GetSize(), domain.GetElements().size()); + } +} + +TYPED_TEST(MixedRadixEvaluationDomainTest, ContentsOfElements) { + using F = TypeParam; + + for (size_t coeffs = 1; coeffs < 12; ++coeffs) { + size_t size = 1 << coeffs; + MixedRadixEvaluationDomain domain(size); + F offset = F::FromMontgomery(F::Config::kSubgroupGenerator); + MixedRadixEvaluationDomain coset_domain = domain.GetCoset(offset); + std::vector elements = domain.GetElements(); + std::vector coset_elements = coset_domain.GetElements(); + for (size_t i = 0; i < size; ++i) { + EXPECT_EQ(elements[i], domain.GetGroupGen().Pow(BigInt<1>(i))); + EXPECT_EQ(elements[i], domain.GetElement(i)); + EXPECT_EQ(coset_elements[i], + offset * coset_domain.GetGroupGen().Pow(BigInt<1>(i))); + EXPECT_EQ(coset_elements[i], coset_domain.GetElement(i)); + } + } +} + +// Test that lagrange interpolation for a random polynomial at a random +// point works. +TYPED_TEST(MixedRadixEvaluationDomainTest, NonSystematicLagrangeCoefficients) { + using F = TypeParam; + + for (size_t domain_dim = 1; domain_dim < 10; ++domain_dim) { + size_t domain_size = 1 << domain_dim; + MixedRadixEvaluationDomain domain(domain_size); + MixedRadixEvaluationDomain coset_domain = + domain.GetCoset(F::FromMontgomery(F::Config::kSubgroupGenerator)); + + // Get random point + lagrange coefficients + F rand_pt = F::Random(); + std::vector lagrange_coeffs = + domain.EvaluateAllLagrangeCoefficients(rand_pt); + std::vector coset_lagrange_coeffs = + coset_domain.EvaluateAllLagrangeCoefficients(rand_pt); + + // Sample the random polynomial, evaluate it over the domain and the random + // point. + DenseUnivariatePolynomial rand_poly = + DenseUnivariatePolynomial::Random(domain_size - 1); + DenseUnivariatePolynomial poly_evals = domain.FFT(rand_poly); + DenseUnivariatePolynomial coset_poly_evals = + coset_domain.FFT(rand_poly); + F actual_eval = rand_poly.Evaluate(rand_pt); + + // Do lagrange interpolation, and compare against the actual evaluation + F interpolated_eval = F::Zero(); + F coset_interpolated_eval = F::Zero(); + for (size_t i = 0; i < domain_size; ++i) { + F* eval_coeff = poly_evals[i]; + F* coset_eval_coeff = coset_poly_evals[i]; + interpolated_eval += lagrange_coeffs[i] * (*eval_coeff); + coset_interpolated_eval += coset_lagrange_coeffs[i] * (*coset_eval_coeff); + } + EXPECT_EQ(actual_eval, interpolated_eval); + EXPECT_EQ(actual_eval, coset_interpolated_eval); + } +} + +// Test that lagrange coefficients for a point in the domain is correct +TYPED_TEST(MixedRadixEvaluationDomainTest, SystematicLagrangeCoefficients) { + // This runs in time O(N^2) in the domain size, so keep the domain dimension + // low. We generate lagrange coefficients for each element in the domain. + using F = TypeParam; + + for (size_t domain_dim = 1; domain_dim < 5; ++domain_dim) { + size_t domain_size = 1 << domain_dim; + MixedRadixEvaluationDomain domain(domain_size); + MixedRadixEvaluationDomain coset_domain = + domain.GetCoset(F::FromMontgomery(F::Config::kSubgroupGenerator)); + for (size_t i = 0; i < domain_size; ++i) { + F x = domain.GetElement(i); + F coset_x = coset_domain.GetElement(i); + std::vector lagrange_coeffs = + domain.EvaluateAllLagrangeCoefficients(x); + std::vector coset_lagrange_coeffs = + coset_domain.EvaluateAllLagrangeCoefficients(coset_x); + for (size_t j = 0; j < domain_size; ++j) { + // Lagrange coefficient for the evaluation point, which should be 1 if + // i == j + if (i == j) { + EXPECT_EQ(lagrange_coeffs[j], F::One()); + EXPECT_EQ(coset_lagrange_coeffs[j], F::One()); + } else { + EXPECT_EQ(lagrange_coeffs[j], F::Zero()); + EXPECT_EQ(coset_lagrange_coeffs[j], F::Zero()); + } + } + } + } +} + +// Tests that the ffts output the correct result. +// This assumes a correct polynomial evaluation at point procedure. +// It tests consistency of FFT/IFFT, and coset_fft/coset_ifft, +// along with testing that each individual evaluation is correct. +TYPED_TEST(MixedRadixEvaluationDomainTest, FFTCorrectness) { + using F = TypeParam; + + // Runs in time O(degree^2) + const size_t log_degree = 5; + const size_t degree = (1 << log_degree) - 1; + DenseUnivariatePolynomial rand_poly = + DenseUnivariatePolynomial::Random(degree); + for (size_t log_domain_size = log_degree; log_domain_size < log_degree + 2; + ++log_domain_size) { + size_t domain_size = 1 << log_domain_size; + MixedRadixEvaluationDomain domain(domain_size); + MixedRadixEvaluationDomain coset_domain = + domain.GetCoset(F::FromMontgomery(F::Config::kSubgroupGenerator)); + DenseUnivariatePolynomial poly_evals = domain.FFT(rand_poly); + DenseUnivariatePolynomial poly_coset_evals = + coset_domain.FFT(rand_poly); + + for (size_t i = 0; i < domain_size; ++i) { + EXPECT_EQ(*poly_evals[i], rand_poly.Evaluate(domain.GetElement(i))); + EXPECT_EQ(*poly_coset_evals[i], + rand_poly.Evaluate(coset_domain.GetElement(i))); + } + + DenseUnivariatePolynomial rand_poly_from_subgroup = + DenseUnivariatePolynomial(domain.IFFT(poly_evals)); + DenseUnivariatePolynomial rand_poly_from_coset = + DenseUnivariatePolynomial( + coset_domain.IFFT(poly_coset_evals)); + + EXPECT_EQ(rand_poly.ToString(), rand_poly_from_subgroup.ToString()); + EXPECT_EQ(rand_poly.ToString(), rand_poly_from_coset.ToString()); + } +} + +} // namespace + +} // namespace tachyon::math diff --git a/tachyon/math/polynomial/polynomials/univariate/dense_coefficients.h b/tachyon/math/polynomial/polynomials/univariate/dense_coefficients.h index e52ef75aa9..cefc1d79f9 100644 --- a/tachyon/math/polynomial/polynomials/univariate/dense_coefficients.h +++ b/tachyon/math/polynomial/polynomials/univariate/dense_coefficients.h @@ -24,6 +24,9 @@ class EvaluationDomain; template class Radix2EvaluationDomain; +template +class MixedRadixEvaluationDomain; + template class DenseCoefficients { public: @@ -130,7 +133,9 @@ class DenseCoefficients { friend class internal::UnivariatePolynomialOp< SparseCoefficients>; friend class EvaluationDomain>; + friend class EvaluationDomain>; friend class Radix2EvaluationDomain; + friend class MixedRadixEvaluationDomain; constexpr Field DoEvaluate(const Field& point) const { return HornerEvaluate(point); diff --git a/tachyon/math/polynomial/polynomials/univariate/univariate_polynomial.h b/tachyon/math/polynomial/polynomials/univariate/univariate_polynomial.h index 75018f89d7..191fb8f31a 100644 --- a/tachyon/math/polynomial/polynomials/univariate/univariate_polynomial.h +++ b/tachyon/math/polynomial/polynomials/univariate/univariate_polynomial.h @@ -157,7 +157,9 @@ class UnivariatePolynomial private: friend class internal::UnivariatePolynomialOp; friend class EvaluationDomain>; + friend class EvaluationDomain>; friend class Radix2EvaluationDomain; + friend class MixedRadixEvaluationDomain; Coefficients coefficients_; };