galoisgroups.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. """
  2. Compute Galois groups of polynomials.
  3. We use algorithms from [1], with some modifications to use lookup tables for
  4. resolvents.
  5. References
  6. ==========
  7. .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
  8. """
  9. from collections import defaultdict
  10. import random
  11. from sympy.core.symbol import Dummy, symbols
  12. from sympy.ntheory.primetest import is_square
  13. from sympy.polys.domains import ZZ
  14. from sympy.polys.densebasic import dup_random
  15. from sympy.polys.densetools import dup_eval
  16. from sympy.polys.euclidtools import dup_discriminant
  17. from sympy.polys.factortools import dup_factor_list, dup_irreducible_p
  18. from sympy.polys.numberfields.galois_resolvents import (
  19. GaloisGroupException, get_resolvent_by_lookup, define_resolvents,
  20. Resolvent,
  21. )
  22. from sympy.polys.numberfields.utilities import coeff_search
  23. from sympy.polys.polytools import (Poly, poly_from_expr,
  24. PolificationFailed, ComputationFailed)
  25. from sympy.polys.sqfreetools import dup_sqf_p
  26. from sympy.utilities import public
  27. class MaxTriesException(GaloisGroupException):
  28. ...
  29. def tschirnhausen_transformation(T, max_coeff=10, max_tries=30, history=None,
  30. fixed_order=True):
  31. r"""
  32. Given a univariate, monic, irreducible polynomial over the integers, find
  33. another such polynomial defining the same number field.
  34. Explanation
  35. ===========
  36. See Alg 6.3.4 of [1].
  37. Parameters
  38. ==========
  39. T : Poly
  40. The given polynomial
  41. max_coeff : int
  42. When choosing a transformation as part of the process,
  43. keep the coeffs between plus and minus this.
  44. max_tries : int
  45. Consider at most this many transformations.
  46. history : set, None, optional (default=None)
  47. Pass a set of ``Poly.rep``'s in order to prevent any of these
  48. polynomials from being returned as the polynomial ``U`` i.e. the
  49. transformation of the given polynomial *T*. The given poly *T* will
  50. automatically be added to this set, before we try to find a new one.
  51. fixed_order : bool, default True
  52. If ``True``, work through candidate transformations A(x) in a fixed
  53. order, from small coeffs to large, resulting in deterministic behavior.
  54. If ``False``, the A(x) are chosen randomly, while still working our way
  55. up from small coefficients to larger ones.
  56. Returns
  57. =======
  58. Pair ``(A, U)``
  59. ``A`` and ``U`` are ``Poly``, ``A`` is the
  60. transformation, and ``U`` is the transformed polynomial that defines
  61. the same number field as *T*. The polynomial ``A`` maps the roots of
  62. *T* to the roots of ``U``.
  63. Raises
  64. ======
  65. MaxTriesException
  66. if could not find a polynomial before exceeding *max_tries*.
  67. """
  68. X = Dummy('X')
  69. n = T.degree()
  70. if history is None:
  71. history = set()
  72. history.add(T.rep)
  73. if fixed_order:
  74. coeff_generators = {}
  75. deg_coeff_sum = 3
  76. current_degree = 2
  77. def get_coeff_generator(degree):
  78. gen = coeff_generators.get(degree, coeff_search(degree, 1))
  79. coeff_generators[degree] = gen
  80. return gen
  81. for i in range(max_tries):
  82. # We never use linear A(x), since applying a fixed linear transformation
  83. # to all roots will only multiply the discriminant of T by a square
  84. # integer. This will change nothing important. In particular, if disc(T)
  85. # was zero before, it will still be zero now, and typically we apply
  86. # the transformation in hopes of replacing T by a squarefree poly.
  87. if fixed_order:
  88. # If d is degree and c max coeff, we move through the dc-space
  89. # along lines of constant sum. First d + c = 3 with (d, c) = (2, 1).
  90. # Then d + c = 4 with (d, c) = (3, 1), (2, 2). Then d + c = 5 with
  91. # (d, c) = (4, 1), (3, 2), (2, 3), and so forth. For a given (d, c)
  92. # we go though all sets of coeffs where max = c, before moving on.
  93. gen = get_coeff_generator(current_degree)
  94. coeffs = next(gen)
  95. m = max(abs(c) for c in coeffs)
  96. if current_degree + m > deg_coeff_sum:
  97. if current_degree == 2:
  98. deg_coeff_sum += 1
  99. current_degree = deg_coeff_sum - 1
  100. else:
  101. current_degree -= 1
  102. gen = get_coeff_generator(current_degree)
  103. coeffs = next(gen)
  104. a = [ZZ(1)] + [ZZ(c) for c in coeffs]
  105. else:
  106. # We use a progressive coeff bound, up to the max specified, since it
  107. # is preferable to succeed with smaller coeffs.
  108. # Give each coeff bound five tries, before incrementing.
  109. C = min(i//5 + 1, max_coeff)
  110. d = random.randint(2, n - 1)
  111. a = dup_random(d, -C, C, ZZ)
  112. A = Poly(a, T.gen)
  113. U = Poly(T.resultant(X - A), X)
  114. if U.rep not in history and dup_sqf_p(U.rep.rep, ZZ):
  115. return A, U
  116. raise MaxTriesException
  117. def has_square_disc(T):
  118. """Convenience to check if a Poly or dup has square discriminant. """
  119. d = T.discriminant() if isinstance(T, Poly) else dup_discriminant(T, ZZ)
  120. return is_square(d)
  121. def _galois_group_degree_3(T, max_tries=30, randomize=False):
  122. r"""
  123. Compute the Galois group of a polynomial of degree 3.
  124. Explanation
  125. ===========
  126. Uses Prop 6.3.5 of [1].
  127. """
  128. from sympy.combinatorics.galois import S3TransitiveSubgroups
  129. return ((S3TransitiveSubgroups.A3, True) if has_square_disc(T)
  130. else (S3TransitiveSubgroups.S3, False))
  131. def _galois_group_degree_4_root_approx(T, max_tries=30, randomize=False):
  132. r"""
  133. Compute the Galois group of a polynomial of degree 4.
  134. Explanation
  135. ===========
  136. Follows Alg 6.3.7 of [1], using a pure root approximation approach.
  137. """
  138. from sympy.combinatorics.permutations import Permutation
  139. from sympy.combinatorics.galois import S4TransitiveSubgroups
  140. X = symbols('X0 X1 X2 X3')
  141. # We start by considering the resolvent for the form
  142. # F = X0*X2 + X1*X3
  143. # and the group G = S4. In this case, the stabilizer H is D4 = < (0123), (02) >,
  144. # and a set of representatives of G/H is {I, (01), (03)}
  145. F1 = X[0]*X[2] + X[1]*X[3]
  146. s1 = [
  147. Permutation(3),
  148. Permutation(3)(0, 1),
  149. Permutation(3)(0, 3)
  150. ]
  151. R1 = Resolvent(F1, X, s1)
  152. # In the second half of the algorithm (if we reach it), we use another
  153. # form and set of coset representatives. However, we may need to permute
  154. # them first, so cannot form their resolvent now.
  155. F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
  156. s2_pre = [
  157. Permutation(3),
  158. Permutation(3)(0, 2)
  159. ]
  160. history = set()
  161. for i in range(max_tries):
  162. if i > 0:
  163. # If we're retrying, need a new polynomial T.
  164. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  165. history=history,
  166. fixed_order=not randomize)
  167. R_dup, _, i0 = R1.eval_for_poly(T, find_integer_root=True)
  168. # If R is not squarefree, must retry.
  169. if not dup_sqf_p(R_dup, ZZ):
  170. continue
  171. # By Prop 6.3.1 of [1], Gal(T) is contained in A4 iff disc(T) is square.
  172. sq_disc = has_square_disc(T)
  173. if i0 is None:
  174. # By Thm 6.3.3 of [1], Gal(T) is not conjugate to any subgroup of the
  175. # stabilizer H = D4 that we chose. This means Gal(T) is either A4 or S4.
  176. return ((S4TransitiveSubgroups.A4, True) if sq_disc
  177. else (S4TransitiveSubgroups.S4, False))
  178. # Gal(T) is conjugate to a subgroup of H = D4, so it is either V, C4
  179. # or D4 itself.
  180. if sq_disc:
  181. # Neither C4 nor D4 is contained in A4, so Gal(T) must be V.
  182. return (S4TransitiveSubgroups.V, True)
  183. # Gal(T) can only be D4 or C4.
  184. # We will now use our second resolvent, with G being that conjugate of D4 that
  185. # Gal(T) is contained in. To determine the right conjugate, we will need
  186. # the permutation corresponding to the integer root we found.
  187. sigma = s1[i0]
  188. # Applying sigma means permuting the args of F, and
  189. # conjugating the set of coset representatives.
  190. F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
  191. s2 = [sigma*tau*sigma for tau in s2_pre]
  192. R2 = Resolvent(F2, X, s2)
  193. R_dup, _, _ = R2.eval_for_poly(T)
  194. d = dup_discriminant(R_dup, ZZ)
  195. # If d is zero (R has a repeated root), must retry.
  196. if d == 0:
  197. continue
  198. if is_square(d):
  199. return (S4TransitiveSubgroups.C4, False)
  200. else:
  201. return (S4TransitiveSubgroups.D4, False)
  202. raise MaxTriesException
  203. def _galois_group_degree_4_lookup(T, max_tries=30, randomize=False):
  204. r"""
  205. Compute the Galois group of a polynomial of degree 4.
  206. Explanation
  207. ===========
  208. Based on Alg 6.3.6 of [1], but uses resolvent coeff lookup.
  209. """
  210. from sympy.combinatorics.galois import S4TransitiveSubgroups
  211. history = set()
  212. for i in range(max_tries):
  213. R_dup = get_resolvent_by_lookup(T, 0)
  214. if dup_sqf_p(R_dup, ZZ):
  215. break
  216. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  217. history=history,
  218. fixed_order=not randomize)
  219. else:
  220. raise MaxTriesException
  221. # Compute list L of degrees of irreducible factors of R, in increasing order:
  222. fl = dup_factor_list(R_dup, ZZ)
  223. L = sorted(sum([
  224. [len(r) - 1] * e for r, e in fl[1]
  225. ], []))
  226. if L == [6]:
  227. return ((S4TransitiveSubgroups.A4, True) if has_square_disc(T)
  228. else (S4TransitiveSubgroups.S4, False))
  229. if L == [1, 1, 4]:
  230. return (S4TransitiveSubgroups.C4, False)
  231. if L == [2, 2, 2]:
  232. return (S4TransitiveSubgroups.V, True)
  233. assert L == [2, 4]
  234. return (S4TransitiveSubgroups.D4, False)
  235. def _galois_group_degree_5_hybrid(T, max_tries=30, randomize=False):
  236. r"""
  237. Compute the Galois group of a polynomial of degree 5.
  238. Explanation
  239. ===========
  240. Based on Alg 6.3.9 of [1], but uses a hybrid approach, combining resolvent
  241. coeff lookup, with root approximation.
  242. """
  243. from sympy.combinatorics.galois import S5TransitiveSubgroups
  244. from sympy.combinatorics.permutations import Permutation
  245. X5 = symbols("X0,X1,X2,X3,X4")
  246. res = define_resolvents()
  247. F51, _, s51 = res[(5, 1)]
  248. F51 = F51.as_expr(*X5)
  249. R51 = Resolvent(F51, X5, s51)
  250. history = set()
  251. reached_second_stage = False
  252. for i in range(max_tries):
  253. if i > 0:
  254. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  255. history=history,
  256. fixed_order=not randomize)
  257. R51_dup = get_resolvent_by_lookup(T, 1)
  258. if not dup_sqf_p(R51_dup, ZZ):
  259. continue
  260. # First stage
  261. # If we have not yet reached the second stage, then the group still
  262. # might be S5, A5, or M20, so must test for that.
  263. if not reached_second_stage:
  264. sq_disc = has_square_disc(T)
  265. if dup_irreducible_p(R51_dup, ZZ):
  266. return ((S5TransitiveSubgroups.A5, True) if sq_disc
  267. else (S5TransitiveSubgroups.S5, False))
  268. if not sq_disc:
  269. return (S5TransitiveSubgroups.M20, False)
  270. # Second stage
  271. reached_second_stage = True
  272. # R51 must have an integer root for T.
  273. # To choose our second resolvent, we need to know which conjugate of
  274. # F51 is a root.
  275. rounded_roots = R51.round_roots_to_integers_for_poly(T)
  276. # These are integers, and candidates to be roots of R51.
  277. # We find the first one that actually is a root.
  278. for permutation_index, candidate_root in rounded_roots.items():
  279. if not dup_eval(R51_dup, candidate_root, ZZ):
  280. break
  281. X = X5
  282. F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[4]**2 + X[4]*X[0]**2
  283. s2_pre = [
  284. Permutation(4),
  285. Permutation(4)(0, 1)(2, 4)
  286. ]
  287. i0 = permutation_index
  288. sigma = s51[i0]
  289. F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
  290. s2 = [sigma*tau*sigma for tau in s2_pre]
  291. R2 = Resolvent(F2, X, s2)
  292. R_dup, _, _ = R2.eval_for_poly(T)
  293. d = dup_discriminant(R_dup, ZZ)
  294. if d == 0:
  295. continue
  296. if is_square(d):
  297. return (S5TransitiveSubgroups.C5, True)
  298. else:
  299. return (S5TransitiveSubgroups.D5, True)
  300. raise MaxTriesException
  301. def _galois_group_degree_5_lookup_ext_factor(T, max_tries=30, randomize=False):
  302. r"""
  303. Compute the Galois group of a polynomial of degree 5.
  304. Explanation
  305. ===========
  306. Based on Alg 6.3.9 of [1], but uses resolvent coeff lookup, plus
  307. factorization over an algebraic extension.
  308. """
  309. from sympy.combinatorics.galois import S5TransitiveSubgroups
  310. _T = T
  311. history = set()
  312. for i in range(max_tries):
  313. R_dup = get_resolvent_by_lookup(T, 1)
  314. if dup_sqf_p(R_dup, ZZ):
  315. break
  316. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  317. history=history,
  318. fixed_order=not randomize)
  319. else:
  320. raise MaxTriesException
  321. sq_disc = has_square_disc(T)
  322. if dup_irreducible_p(R_dup, ZZ):
  323. return ((S5TransitiveSubgroups.A5, True) if sq_disc
  324. else (S5TransitiveSubgroups.S5, False))
  325. if not sq_disc:
  326. return (S5TransitiveSubgroups.M20, False)
  327. # If we get this far, Gal(T) can only be D5 or C5.
  328. # But for Gal(T) to have order 5, T must already split completely in
  329. # the extension field obtained by adjoining a single one of its roots.
  330. fl = Poly(_T, domain=ZZ.alg_field_from_poly(_T)).factor_list()[1]
  331. if len(fl) == 5:
  332. return (S5TransitiveSubgroups.C5, True)
  333. else:
  334. return (S5TransitiveSubgroups.D5, True)
  335. def _galois_group_degree_6_lookup(T, max_tries=30, randomize=False):
  336. r"""
  337. Compute the Galois group of a polynomial of degree 6.
  338. Explanation
  339. ===========
  340. Based on Alg 6.3.10 of [1], but uses resolvent coeff lookup.
  341. """
  342. from sympy.combinatorics.galois import S6TransitiveSubgroups
  343. # First resolvent:
  344. history = set()
  345. for i in range(max_tries):
  346. R_dup = get_resolvent_by_lookup(T, 1)
  347. if dup_sqf_p(R_dup, ZZ):
  348. break
  349. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  350. history=history,
  351. fixed_order=not randomize)
  352. else:
  353. raise MaxTriesException
  354. fl = dup_factor_list(R_dup, ZZ)
  355. # Group the factors by degree.
  356. factors_by_deg = defaultdict(list)
  357. for r, _ in fl[1]:
  358. factors_by_deg[len(r) - 1].append(r)
  359. L = sorted(sum([
  360. [d] * len(ff) for d, ff in factors_by_deg.items()
  361. ], []))
  362. T_has_sq_disc = has_square_disc(T)
  363. if L == [1, 2, 3]:
  364. f1 = factors_by_deg[3][0]
  365. return ((S6TransitiveSubgroups.C6, False) if has_square_disc(f1)
  366. else (S6TransitiveSubgroups.D6, False))
  367. elif L == [3, 3]:
  368. f1, f2 = factors_by_deg[3]
  369. any_square = has_square_disc(f1) or has_square_disc(f2)
  370. return ((S6TransitiveSubgroups.G18, False) if any_square
  371. else (S6TransitiveSubgroups.G36m, False))
  372. elif L == [2, 4]:
  373. if T_has_sq_disc:
  374. return (S6TransitiveSubgroups.S4p, True)
  375. else:
  376. f1 = factors_by_deg[4][0]
  377. return ((S6TransitiveSubgroups.A4xC2, False) if has_square_disc(f1)
  378. else (S6TransitiveSubgroups.S4xC2, False))
  379. elif L == [1, 1, 4]:
  380. return ((S6TransitiveSubgroups.A4, True) if T_has_sq_disc
  381. else (S6TransitiveSubgroups.S4m, False))
  382. elif L == [1, 5]:
  383. return ((S6TransitiveSubgroups.PSL2F5, True) if T_has_sq_disc
  384. else (S6TransitiveSubgroups.PGL2F5, False))
  385. elif L == [1, 1, 1, 3]:
  386. return (S6TransitiveSubgroups.S3, False)
  387. assert L == [6]
  388. # Second resolvent:
  389. history = set()
  390. for i in range(max_tries):
  391. R_dup = get_resolvent_by_lookup(T, 2)
  392. if dup_sqf_p(R_dup, ZZ):
  393. break
  394. _, T = tschirnhausen_transformation(T, max_tries=max_tries,
  395. history=history,
  396. fixed_order=not randomize)
  397. else:
  398. raise MaxTriesException
  399. T_has_sq_disc = has_square_disc(T)
  400. if dup_irreducible_p(R_dup, ZZ):
  401. return ((S6TransitiveSubgroups.A6, True) if T_has_sq_disc
  402. else (S6TransitiveSubgroups.S6, False))
  403. else:
  404. return ((S6TransitiveSubgroups.G36p, True) if T_has_sq_disc
  405. else (S6TransitiveSubgroups.G72, False))
  406. @public
  407. def galois_group(f, *gens, by_name=False, max_tries=30, randomize=False, **args):
  408. r"""
  409. Compute the Galois group for polynomials *f* up to degree 6.
  410. Examples
  411. ========
  412. >>> from sympy import galois_group
  413. >>> from sympy.abc import x
  414. >>> f = x**4 + 1
  415. >>> G, alt = galois_group(f)
  416. >>> print(G)
  417. PermutationGroup([
  418. (0 1)(2 3),
  419. (0 2)(1 3)])
  420. The group is returned along with a boolean, indicating whether it is
  421. contained in the alternating group $A_n$, where $n$ is the degree of *T*.
  422. Along with other group properties, this can help determine which group it
  423. is:
  424. >>> alt
  425. True
  426. >>> G.order()
  427. 4
  428. Alternatively, the group can be returned by name:
  429. >>> G_name, _ = galois_group(f, by_name=True)
  430. >>> print(G_name)
  431. S4TransitiveSubgroups.V
  432. The group itself can then be obtained by calling the name's
  433. ``get_perm_group()`` method:
  434. >>> G_name.get_perm_group()
  435. PermutationGroup([
  436. (0 1)(2 3),
  437. (0 2)(1 3)])
  438. Group names are values of the enum classes
  439. :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`,
  440. :py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`,
  441. etc.
  442. Parameters
  443. ==========
  444. f : Expr
  445. Irreducible polynomial over :ref:`ZZ` or :ref:`QQ`, whose Galois group
  446. is to be determined.
  447. gens : optional list of symbols
  448. For converting *f* to Poly, and will be passed on to the
  449. :py:func:`~.poly_from_expr` function.
  450. by_name : bool, default False
  451. If ``True``, the Galois group will be returned by name.
  452. Otherwise it will be returned as a :py:class:`~.PermutationGroup`.
  453. max_tries : int, default 30
  454. Make at most this many attempts in those steps that involve
  455. generating Tschirnhausen transformations.
  456. randomize : bool, default False
  457. If ``True``, then use random coefficients when generating Tschirnhausen
  458. transformations. Otherwise try transformations in a fixed order. Both
  459. approaches start with small coefficients and degrees and work upward.
  460. args : optional
  461. For converting *f* to Poly, and will be passed on to the
  462. :py:func:`~.poly_from_expr` function.
  463. Returns
  464. =======
  465. Pair ``(G, alt)``
  466. The first element ``G`` indicates the Galois group. It is an instance
  467. of one of the :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`
  468. :py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`, etc. enum
  469. classes if *by_name* was ``True``, and a :py:class:`~.PermutationGroup`
  470. if ``False``.
  471. The second element is a boolean, saying whether the group is contained
  472. in the alternating group $A_n$ ($n$ the degree of *T*).
  473. Raises
  474. ======
  475. ValueError
  476. if *f* is of an unsupported degree.
  477. MaxTriesException
  478. if could not complete before exceeding *max_tries* in those steps
  479. that involve generating Tschirnhausen transformations.
  480. See Also
  481. ========
  482. .Poly.galois_group
  483. """
  484. gens = gens or []
  485. args = args or {}
  486. try:
  487. F, opt = poly_from_expr(f, *gens, **args)
  488. except PolificationFailed as exc:
  489. raise ComputationFailed('galois_group', 1, exc)
  490. return F.galois_group(by_name=by_name, max_tries=max_tries,
  491. randomize=randomize)