fft2.sage 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # sage: w.multiplicative_order()
  2. # 1330
  3. # sage: 11^3 - 1
  4. # 1330
  5. # sage: K.<w> = GF(11^3, repr="int")
  6. # sage: w
  7. # 11
  8. p = 199
  9. # sage: factor(199^3 - 1)
  10. # 2 * 3^3 * 11 * 13267
  11. n = 3^3
  12. n = 10
  13. n = 5
  14. assert p.is_prime()
  15. def find_ext_order(p, n):
  16. N = 1
  17. while True:
  18. pNx_order = p^N - 1
  19. # Does n divide the group order 𝔽_{p^N}^×?
  20. if pNx_order % n == 0:
  21. return N
  22. N += 1
  23. # Alternative to the above fn. Technically we still need to loop since
  24. # discrete_log() is a bruteforce algo.
  25. def find_ext_order_alt(p, n):
  26. # We have that n | p^N - 1 for some n. This is the same as wrtiting:
  27. # p^N - 1 = ns for some s
  28. # => p^N - 1 ≡ 0 (mod n)
  29. # p · p^(N - 1) ≡ 1 (mod n)
  30. # But recall that p^(N - 1) ≡ p^-1
  31. # So we just take p (mod n), find its inverse then compute N - 1
  32. R = Integers(n)
  33. p = R(p)
  34. N_minus_1 = discrete_log(p^-1, p)
  35. return N_minus_1 + 1
  36. def find_nth_root_unity(K, n):
  37. # It cannot be a quadratic residue if n is odd
  38. #assert n % 2 == 1
  39. # So there is an nth root of unity in p^N. Now we have to find it.
  40. pNx_order = p^N - 1
  41. ω = K.multiplicative_generator()
  42. ω = ω^(pNx_order/n)
  43. assert ω^n == 1
  44. assert ω^(n - 1) != 1
  45. return ω
  46. N = find_ext_order(p, n)
  47. print(f"N = {N}")
  48. print()
  49. K.<a> = GF(p^N, repr="int")
  50. ω = find_nth_root_unity(K, n)
  51. L.<X> = K[]
  52. f = X^2 + 2*X + 4
  53. g = 2*X^2 + 110
  54. assert f.degree() < n/2
  55. assert g.degree() < n/2
  56. assert f.degree() + g.degree() < n
  57. print(f"f = {f}")
  58. print(f"g = {g}")
  59. print(f"fg = {f*g}")
  60. print()
  61. def vectorify(f):
  62. assert f.degree() < n
  63. fT = vector([f[i] for i in range(f.degree() + 1)] +
  64. # Zero padding
  65. [0 for _ in range(n - f.degree() - 1)])
  66. assert len(fT) == n
  67. # Just check decomposed polynomial is in the correct order
  68. assert sum([fT[i]*X^i for i in range(n)]) == f
  69. return fT
  70. fT = vectorify(f)
  71. gT = vectorify(g)
  72. def nXn_vandermonde(n, ω):
  73. # We hardcode this one so you know what is looks like
  74. if n == 5:
  75. Vω = matrix([
  76. [1, 1, 1, 1, 1],
  77. [1, ω^1, ω^2, ω^3, ω^4],
  78. [1, ω^2, ω^4, ω^1, ω^3],
  79. [1, ω^3, ω^1, ω^4, ω^2],
  80. [1, ω^4, ω^3, ω^2, ω^1],
  81. ])
  82. return Vω
  83. # This is the code to generate it
  84. Vω = matrix([[ω^(i * j) for j in range(n)] for i in range(n)])
  85. return Vω
  86. Vω = nXn_vandermonde(n, ω)
  87. Vω_inv = nXn_vandermonde(n, ω^-1)/n
  88. # Lemma: V_ω^{-1} = 1/n V_{ω^-1}
  89. assert Vω^-1 == Vω_inv
  90. DFT_ω_f = Vω * fT
  91. f_evals = [f(X=ω^i) for i in range(n)]
  92. print(f"DFT_ω(f) = {DFT_ω_f}")
  93. print(f"f(ω^i) = {f_evals}")
  94. print()
  95. DFT_ω_g = Vω * gT
  96. g_evals = [g(X=ω^i) for i in range(n)]
  97. print(f"DFT_ω(g) = {DFT_ω_g}")
  98. print(f"g(ω^i) = {g_evals}")
  99. print()
  100. def convolution(f, g):
  101. return f*g % (X^n - 1)
  102. def pointwise_prod(fT, gT):
  103. return [a_i*b_i for a_i, b_i in zip(fT, gT)]
  104. print(f"deg(f) + deg(g) = {f.degree() + g.degree()}")
  105. fжg = convolution(f, g)
  106. print(f"f☼g = {fжg}")
  107. assert fжg == f*g
  108. fжgT = vectorify(fжg)
  109. DFT_ω_fжg = Vω * fжgT
  110. for i in range(n):
  111. assert fжg(X=ω^i) == f(ω^i)*g(ω^i)
  112. print(f"DFT_ω(f☼g) = {DFT_ω_fжg}")
  113. DFT_fg_prod = vector(pointwise_prod(DFT_ω_f, DFT_ω_g))
  114. print(f"DFT_ω(f)·DFT_ω(g) = {DFT_fg_prod}")
  115. assert DFT_ω_fжg == DFT_fg_prod
  116. inv_DFT_fg = Vω_inv * DFT_fg_prod
  117. fgT = vectorify(f*g)
  118. assert inv_DFT_fg == fgT
  119. print(f"DFT^-1(DFT_ω(f)·DFT_ω(g)) = {inv_DFT_fg}")