test_primes.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. from math import prod
  2. from sympy import QQ, ZZ
  3. from sympy.abc import x, theta
  4. from sympy.ntheory import factorint
  5. from sympy.ntheory.residue_ntheory import n_order
  6. from sympy.polys import Poly, cyclotomic_poly
  7. from sympy.polys.matrices import DomainMatrix
  8. from sympy.polys.numberfields.basis import round_two
  9. from sympy.polys.numberfields.exceptions import StructureError
  10. from sympy.polys.numberfields.modules import PowerBasis, to_col
  11. from sympy.polys.numberfields.primes import (
  12. prime_decomp, _two_elt_rep,
  13. _check_formal_conditions_for_maximal_order,
  14. )
  15. from sympy.testing.pytest import raises
  16. def test_check_formal_conditions_for_maximal_order():
  17. T = Poly(cyclotomic_poly(5, x))
  18. A = PowerBasis(T)
  19. B = A.submodule_from_matrix(2 * DomainMatrix.eye(4, ZZ))
  20. C = B.submodule_from_matrix(3 * DomainMatrix.eye(4, ZZ))
  21. D = A.submodule_from_matrix(DomainMatrix.eye(4, ZZ)[:, :-1])
  22. # Is a direct submodule of a power basis, but lacks 1 as first generator:
  23. raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(B))
  24. # Is not a direct submodule of a power basis:
  25. raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(C))
  26. # Is direct submod of pow basis, and starts with 1, but not sq/max rank/HNF:
  27. raises(StructureError, lambda: _check_formal_conditions_for_maximal_order(D))
  28. def test_two_elt_rep():
  29. ell = 7
  30. T = Poly(cyclotomic_poly(ell))
  31. ZK, dK = round_two(T)
  32. for p in [29, 13, 11, 5]:
  33. P = prime_decomp(p, T)
  34. for Pi in P:
  35. # We have Pi in two-element representation, and, because we are
  36. # looking at a cyclotomic field, this was computed by the "easy"
  37. # method that just factors T mod p. We will now convert this to
  38. # a set of Z-generators, then convert that back into a two-element
  39. # rep. The latter need not be identical to the two-elt rep we
  40. # already have, but it must have the same HNF.
  41. H = p*ZK + Pi.alpha*ZK
  42. gens = H.basis_element_pullbacks()
  43. # Note: we could supply f = Pi.f, but prefer to test behavior without it.
  44. b = _two_elt_rep(gens, ZK, p)
  45. if b != Pi.alpha:
  46. H2 = p*ZK + b*ZK
  47. assert H2 == H
  48. def test_valuation_at_prime_ideal():
  49. p = 7
  50. T = Poly(cyclotomic_poly(p))
  51. ZK, dK = round_two(T)
  52. P = prime_decomp(p, T, dK=dK, ZK=ZK)
  53. assert len(P) == 1
  54. P0 = P[0]
  55. v = P0.valuation(p*ZK)
  56. assert v == P0.e
  57. # Test easy 0 case:
  58. assert P0.valuation(5*ZK) == 0
  59. def test_decomp_1():
  60. # All prime decompositions in cyclotomic fields are in the "easy case,"
  61. # since the index is unity.
  62. # Here we check the ramified prime.
  63. T = Poly(cyclotomic_poly(7))
  64. raises(ValueError, lambda: prime_decomp(7))
  65. P = prime_decomp(7, T)
  66. assert len(P) == 1
  67. P0 = P[0]
  68. assert P0.e == 6
  69. assert P0.f == 1
  70. # Test powers:
  71. assert P0**0 == P0.ZK
  72. assert P0**1 == P0
  73. assert P0**6 == 7 * P0.ZK
  74. def test_decomp_2():
  75. # More easy cyclotomic cases, but here we check unramified primes.
  76. ell = 7
  77. T = Poly(cyclotomic_poly(ell))
  78. for p in [29, 13, 11, 5]:
  79. f_exp = n_order(p, ell)
  80. g_exp = (ell - 1) // f_exp
  81. P = prime_decomp(p, T)
  82. assert len(P) == g_exp
  83. for Pi in P:
  84. assert Pi.e == 1
  85. assert Pi.f == f_exp
  86. def test_decomp_3():
  87. T = Poly(x ** 2 - 35)
  88. rad = {}
  89. ZK, dK = round_two(T, radicals=rad)
  90. # 35 is 3 mod 4, so field disc is 4*5*7, and theory says each of the
  91. # rational primes 2, 5, 7 should be the square of a prime ideal.
  92. for p in [2, 5, 7]:
  93. P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
  94. assert len(P) == 1
  95. assert P[0].e == 2
  96. assert P[0]**2 == p*ZK
  97. def test_decomp_4():
  98. T = Poly(x ** 2 - 21)
  99. rad = {}
  100. ZK, dK = round_two(T, radicals=rad)
  101. # 21 is 1 mod 4, so field disc is 3*7, and theory says the
  102. # rational primes 3, 7 should be the square of a prime ideal.
  103. for p in [3, 7]:
  104. P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
  105. assert len(P) == 1
  106. assert P[0].e == 2
  107. assert P[0]**2 == p*ZK
  108. def test_decomp_5():
  109. # Here is our first test of the "hard case" of prime decomposition.
  110. # We work in a quadratic extension Q(sqrt(d)) where d is 1 mod 4, and
  111. # we consider the factorization of the rational prime 2, which divides
  112. # the index.
  113. # Theory says the form of p's factorization depends on the residue of
  114. # d mod 8, so we consider both cases, d = 1 mod 8 and d = 5 mod 8.
  115. for d in [-7, -3]:
  116. T = Poly(x ** 2 - d)
  117. rad = {}
  118. ZK, dK = round_two(T, radicals=rad)
  119. p = 2
  120. P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
  121. if d % 8 == 1:
  122. assert len(P) == 2
  123. assert all(P[i].e == 1 and P[i].f == 1 for i in range(2))
  124. assert prod(Pi**Pi.e for Pi in P) == p * ZK
  125. else:
  126. assert d % 8 == 5
  127. assert len(P) == 1
  128. assert P[0].e == 1
  129. assert P[0].f == 2
  130. assert P[0].as_submodule() == p * ZK
  131. def test_decomp_6():
  132. # Another case where 2 divides the index. This is Dedekind's example of
  133. # an essential discriminant divisor. (See Cohen, Exercise 6.10.)
  134. T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
  135. rad = {}
  136. ZK, dK = round_two(T, radicals=rad)
  137. p = 2
  138. P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=rad.get(p))
  139. assert len(P) == 3
  140. assert all(Pi.e == Pi.f == 1 for Pi in P)
  141. assert prod(Pi**Pi.e for Pi in P) == p*ZK
  142. def test_decomp_7():
  143. # Try working through an AlgebraicField
  144. T = Poly(x ** 3 + x ** 2 - 2 * x + 8)
  145. K = QQ.alg_field_from_poly(T)
  146. p = 2
  147. P = K.primes_above(p)
  148. ZK = K.maximal_order()
  149. assert len(P) == 3
  150. assert all(Pi.e == Pi.f == 1 for Pi in P)
  151. assert prod(Pi**Pi.e for Pi in P) == p*ZK
  152. def test_decomp_8():
  153. # This time we consider various cubics, and try factoring all primes
  154. # dividing the index.
  155. cases = (
  156. x ** 3 + 3 * x ** 2 - 4 * x + 4,
  157. x ** 3 + 3 * x ** 2 + 3 * x - 3,
  158. x ** 3 + 5 * x ** 2 - x + 3,
  159. x ** 3 + 5 * x ** 2 - 5 * x - 5,
  160. x ** 3 + 3 * x ** 2 + 5,
  161. x ** 3 + 6 * x ** 2 + 3 * x - 1,
  162. x ** 3 + 6 * x ** 2 + 4,
  163. x ** 3 + 7 * x ** 2 + 7 * x - 7,
  164. x ** 3 + 7 * x ** 2 - x + 5,
  165. x ** 3 + 7 * x ** 2 - 5 * x + 5,
  166. x ** 3 + 4 * x ** 2 - 3 * x + 7,
  167. x ** 3 + 8 * x ** 2 + 5 * x - 1,
  168. x ** 3 + 8 * x ** 2 - 2 * x + 6,
  169. x ** 3 + 6 * x ** 2 - 3 * x + 8,
  170. x ** 3 + 9 * x ** 2 + 6 * x - 8,
  171. x ** 3 + 15 * x ** 2 - 9 * x + 13,
  172. )
  173. def display(T, p, radical, P, I, J):
  174. """Useful for inspection, when running test manually."""
  175. print('=' * 20)
  176. print(T, p, radical)
  177. for Pi in P:
  178. print(f' ({Pi!r})')
  179. print("I: ", I)
  180. print("J: ", J)
  181. print(f'Equal: {I == J}')
  182. inspect = False
  183. for g in cases:
  184. T = Poly(g)
  185. rad = {}
  186. ZK, dK = round_two(T, radicals=rad)
  187. dT = T.discriminant()
  188. f_squared = dT // dK
  189. F = factorint(f_squared)
  190. for p in F:
  191. radical = rad.get(p)
  192. P = prime_decomp(p, T, dK=dK, ZK=ZK, radical=radical)
  193. I = prod(Pi**Pi.e for Pi in P)
  194. J = p * ZK
  195. if inspect:
  196. display(T, p, radical, P, I, J)
  197. assert I == J
  198. def test_PrimeIdeal_eq():
  199. # `==` should fail on objects of different types, so even a completely
  200. # inert PrimeIdeal should test unequal to the rational prime it divides.
  201. T = Poly(cyclotomic_poly(7))
  202. P0 = prime_decomp(5, T)[0]
  203. assert P0.f == 6
  204. assert P0.as_submodule() == 5 * P0.ZK
  205. assert P0 != 5
  206. def test_PrimeIdeal_add():
  207. T = Poly(cyclotomic_poly(7))
  208. P0 = prime_decomp(7, T)[0]
  209. # Adding ideals computes their GCD, so adding the ramified prime dividing
  210. # 7 to 7 itself should reproduce this prime (as a submodule).
  211. assert P0 + 7 * P0.ZK == P0.as_submodule()
  212. def test_str():
  213. # Without alias:
  214. k = QQ.alg_field_from_poly(Poly(x**2 + 7))
  215. frp = k.primes_above(2)[0]
  216. assert str(frp) == '(2, 3*_x/2 + 1/2)'
  217. frp = k.primes_above(3)[0]
  218. assert str(frp) == '(3)'
  219. # With alias:
  220. k = QQ.alg_field_from_poly(Poly(x ** 2 + 7), alias='alpha')
  221. frp = k.primes_above(2)[0]
  222. assert str(frp) == '(2, 3*alpha/2 + 1/2)'
  223. frp = k.primes_above(3)[0]
  224. assert str(frp) == '(3)'
  225. def test_repr():
  226. T = Poly(x**2 + 7)
  227. ZK, dK = round_two(T)
  228. P = prime_decomp(2, T, dK=dK, ZK=ZK)
  229. assert repr(P[0]) == '[ (2, (3*x + 1)/2) e=1, f=1 ]'
  230. assert P[0].repr(field_gen=theta) == '[ (2, (3*theta + 1)/2) e=1, f=1 ]'
  231. assert P[0].repr(field_gen=theta, just_gens=True) == '(2, (3*theta + 1)/2)'
  232. def test_PrimeIdeal_reduce():
  233. k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8))
  234. Zk = k.maximal_order()
  235. P = k.primes_above(2)
  236. frp = P[2]
  237. # reduce_element
  238. a = Zk.parent(to_col([23, 20, 11]), denom=6)
  239. a_bar_expected = Zk.parent(to_col([11, 5, 2]), denom=6)
  240. a_bar = frp.reduce_element(a)
  241. assert a_bar == a_bar_expected
  242. # reduce_ANP
  243. a = k([QQ(11, 6), QQ(20, 6), QQ(23, 6)])
  244. a_bar_expected = k([QQ(2, 6), QQ(5, 6), QQ(11, 6)])
  245. a_bar = frp.reduce_ANP(a)
  246. assert a_bar == a_bar_expected
  247. # reduce_alg_num
  248. a = k.to_alg_num(a)
  249. a_bar_expected = k.to_alg_num(a_bar_expected)
  250. a_bar = frp.reduce_alg_num(a)
  251. assert a_bar == a_bar_expected
  252. def test_issue_23402():
  253. k = QQ.alg_field_from_poly(Poly(x ** 3 + x ** 2 - 2 * x + 8))
  254. P = k.primes_above(3)
  255. assert P[0].alpha.equiv(0)