624 lines
20 KiB
Python
624 lines
20 KiB
Python
|
"""
|
||
|
Compute Galois groups of polynomials.
|
||
|
|
||
|
We use algorithms from [1], with some modifications to use lookup tables for
|
||
|
resolvents.
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
|
||
|
|
||
|
"""
|
||
|
|
||
|
from collections import defaultdict
|
||
|
import random
|
||
|
|
||
|
from sympy.core.symbol import Dummy, symbols
|
||
|
from sympy.ntheory.primetest import is_square
|
||
|
from sympy.polys.domains import ZZ
|
||
|
from sympy.polys.densebasic import dup_random
|
||
|
from sympy.polys.densetools import dup_eval
|
||
|
from sympy.polys.euclidtools import dup_discriminant
|
||
|
from sympy.polys.factortools import dup_factor_list, dup_irreducible_p
|
||
|
from sympy.polys.numberfields.galois_resolvents import (
|
||
|
GaloisGroupException, get_resolvent_by_lookup, define_resolvents,
|
||
|
Resolvent,
|
||
|
)
|
||
|
from sympy.polys.numberfields.utilities import coeff_search
|
||
|
from sympy.polys.polytools import (Poly, poly_from_expr,
|
||
|
PolificationFailed, ComputationFailed)
|
||
|
from sympy.polys.sqfreetools import dup_sqf_p
|
||
|
from sympy.utilities import public
|
||
|
|
||
|
|
||
|
class MaxTriesException(GaloisGroupException):
|
||
|
...
|
||
|
|
||
|
|
||
|
def tschirnhausen_transformation(T, max_coeff=10, max_tries=30, history=None,
|
||
|
fixed_order=True):
|
||
|
r"""
|
||
|
Given a univariate, monic, irreducible polynomial over the integers, find
|
||
|
another such polynomial defining the same number field.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
See Alg 6.3.4 of [1].
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
T : Poly
|
||
|
The given polynomial
|
||
|
max_coeff : int
|
||
|
When choosing a transformation as part of the process,
|
||
|
keep the coeffs between plus and minus this.
|
||
|
max_tries : int
|
||
|
Consider at most this many transformations.
|
||
|
history : set, None, optional (default=None)
|
||
|
Pass a set of ``Poly.rep``'s in order to prevent any of these
|
||
|
polynomials from being returned as the polynomial ``U`` i.e. the
|
||
|
transformation of the given polynomial *T*. The given poly *T* will
|
||
|
automatically be added to this set, before we try to find a new one.
|
||
|
fixed_order : bool, default True
|
||
|
If ``True``, work through candidate transformations A(x) in a fixed
|
||
|
order, from small coeffs to large, resulting in deterministic behavior.
|
||
|
If ``False``, the A(x) are chosen randomly, while still working our way
|
||
|
up from small coefficients to larger ones.
|
||
|
|
||
|
Returns
|
||
|
=======
|
||
|
|
||
|
Pair ``(A, U)``
|
||
|
|
||
|
``A`` and ``U`` are ``Poly``, ``A`` is the
|
||
|
transformation, and ``U`` is the transformed polynomial that defines
|
||
|
the same number field as *T*. The polynomial ``A`` maps the roots of
|
||
|
*T* to the roots of ``U``.
|
||
|
|
||
|
Raises
|
||
|
======
|
||
|
|
||
|
MaxTriesException
|
||
|
if could not find a polynomial before exceeding *max_tries*.
|
||
|
|
||
|
"""
|
||
|
X = Dummy('X')
|
||
|
n = T.degree()
|
||
|
if history is None:
|
||
|
history = set()
|
||
|
history.add(T.rep)
|
||
|
|
||
|
if fixed_order:
|
||
|
coeff_generators = {}
|
||
|
deg_coeff_sum = 3
|
||
|
current_degree = 2
|
||
|
|
||
|
def get_coeff_generator(degree):
|
||
|
gen = coeff_generators.get(degree, coeff_search(degree, 1))
|
||
|
coeff_generators[degree] = gen
|
||
|
return gen
|
||
|
|
||
|
for i in range(max_tries):
|
||
|
|
||
|
# We never use linear A(x), since applying a fixed linear transformation
|
||
|
# to all roots will only multiply the discriminant of T by a square
|
||
|
# integer. This will change nothing important. In particular, if disc(T)
|
||
|
# was zero before, it will still be zero now, and typically we apply
|
||
|
# the transformation in hopes of replacing T by a squarefree poly.
|
||
|
|
||
|
if fixed_order:
|
||
|
# If d is degree and c max coeff, we move through the dc-space
|
||
|
# along lines of constant sum. First d + c = 3 with (d, c) = (2, 1).
|
||
|
# Then d + c = 4 with (d, c) = (3, 1), (2, 2). Then d + c = 5 with
|
||
|
# (d, c) = (4, 1), (3, 2), (2, 3), and so forth. For a given (d, c)
|
||
|
# we go though all sets of coeffs where max = c, before moving on.
|
||
|
gen = get_coeff_generator(current_degree)
|
||
|
coeffs = next(gen)
|
||
|
m = max(abs(c) for c in coeffs)
|
||
|
if current_degree + m > deg_coeff_sum:
|
||
|
if current_degree == 2:
|
||
|
deg_coeff_sum += 1
|
||
|
current_degree = deg_coeff_sum - 1
|
||
|
else:
|
||
|
current_degree -= 1
|
||
|
gen = get_coeff_generator(current_degree)
|
||
|
coeffs = next(gen)
|
||
|
a = [ZZ(1)] + [ZZ(c) for c in coeffs]
|
||
|
|
||
|
else:
|
||
|
# We use a progressive coeff bound, up to the max specified, since it
|
||
|
# is preferable to succeed with smaller coeffs.
|
||
|
# Give each coeff bound five tries, before incrementing.
|
||
|
C = min(i//5 + 1, max_coeff)
|
||
|
d = random.randint(2, n - 1)
|
||
|
a = dup_random(d, -C, C, ZZ)
|
||
|
|
||
|
A = Poly(a, T.gen)
|
||
|
U = Poly(T.resultant(X - A), X)
|
||
|
if U.rep not in history and dup_sqf_p(U.rep.rep, ZZ):
|
||
|
return A, U
|
||
|
raise MaxTriesException
|
||
|
|
||
|
|
||
|
def has_square_disc(T):
|
||
|
"""Convenience to check if a Poly or dup has square discriminant. """
|
||
|
d = T.discriminant() if isinstance(T, Poly) else dup_discriminant(T, ZZ)
|
||
|
return is_square(d)
|
||
|
|
||
|
|
||
|
def _galois_group_degree_3(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 3.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Uses Prop 6.3.5 of [1].
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.galois import S3TransitiveSubgroups
|
||
|
return ((S3TransitiveSubgroups.A3, True) if has_square_disc(T)
|
||
|
else (S3TransitiveSubgroups.S3, False))
|
||
|
|
||
|
|
||
|
def _galois_group_degree_4_root_approx(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 4.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Follows Alg 6.3.7 of [1], using a pure root approximation approach.
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.permutations import Permutation
|
||
|
from sympy.combinatorics.galois import S4TransitiveSubgroups
|
||
|
|
||
|
X = symbols('X0 X1 X2 X3')
|
||
|
# We start by considering the resolvent for the form
|
||
|
# F = X0*X2 + X1*X3
|
||
|
# and the group G = S4. In this case, the stabilizer H is D4 = < (0123), (02) >,
|
||
|
# and a set of representatives of G/H is {I, (01), (03)}
|
||
|
F1 = X[0]*X[2] + X[1]*X[3]
|
||
|
s1 = [
|
||
|
Permutation(3),
|
||
|
Permutation(3)(0, 1),
|
||
|
Permutation(3)(0, 3)
|
||
|
]
|
||
|
R1 = Resolvent(F1, X, s1)
|
||
|
|
||
|
# In the second half of the algorithm (if we reach it), we use another
|
||
|
# form and set of coset representatives. However, we may need to permute
|
||
|
# them first, so cannot form their resolvent now.
|
||
|
F2_pre = X[0]*X[1]**2 + X[1]*X[2]**2 + X[2]*X[3]**2 + X[3]*X[0]**2
|
||
|
s2_pre = [
|
||
|
Permutation(3),
|
||
|
Permutation(3)(0, 2)
|
||
|
]
|
||
|
|
||
|
history = set()
|
||
|
for i in range(max_tries):
|
||
|
if i > 0:
|
||
|
# If we're retrying, need a new polynomial T.
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
|
||
|
R_dup, _, i0 = R1.eval_for_poly(T, find_integer_root=True)
|
||
|
# If R is not squarefree, must retry.
|
||
|
if not dup_sqf_p(R_dup, ZZ):
|
||
|
continue
|
||
|
|
||
|
# By Prop 6.3.1 of [1], Gal(T) is contained in A4 iff disc(T) is square.
|
||
|
sq_disc = has_square_disc(T)
|
||
|
|
||
|
if i0 is None:
|
||
|
# By Thm 6.3.3 of [1], Gal(T) is not conjugate to any subgroup of the
|
||
|
# stabilizer H = D4 that we chose. This means Gal(T) is either A4 or S4.
|
||
|
return ((S4TransitiveSubgroups.A4, True) if sq_disc
|
||
|
else (S4TransitiveSubgroups.S4, False))
|
||
|
|
||
|
# Gal(T) is conjugate to a subgroup of H = D4, so it is either V, C4
|
||
|
# or D4 itself.
|
||
|
|
||
|
if sq_disc:
|
||
|
# Neither C4 nor D4 is contained in A4, so Gal(T) must be V.
|
||
|
return (S4TransitiveSubgroups.V, True)
|
||
|
|
||
|
# Gal(T) can only be D4 or C4.
|
||
|
# We will now use our second resolvent, with G being that conjugate of D4 that
|
||
|
# Gal(T) is contained in. To determine the right conjugate, we will need
|
||
|
# the permutation corresponding to the integer root we found.
|
||
|
sigma = s1[i0]
|
||
|
# Applying sigma means permuting the args of F, and
|
||
|
# conjugating the set of coset representatives.
|
||
|
F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
|
||
|
s2 = [sigma*tau*sigma for tau in s2_pre]
|
||
|
R2 = Resolvent(F2, X, s2)
|
||
|
R_dup, _, _ = R2.eval_for_poly(T)
|
||
|
d = dup_discriminant(R_dup, ZZ)
|
||
|
# If d is zero (R has a repeated root), must retry.
|
||
|
if d == 0:
|
||
|
continue
|
||
|
if is_square(d):
|
||
|
return (S4TransitiveSubgroups.C4, False)
|
||
|
else:
|
||
|
return (S4TransitiveSubgroups.D4, False)
|
||
|
|
||
|
raise MaxTriesException
|
||
|
|
||
|
|
||
|
def _galois_group_degree_4_lookup(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 4.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Based on Alg 6.3.6 of [1], but uses resolvent coeff lookup.
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.galois import S4TransitiveSubgroups
|
||
|
|
||
|
history = set()
|
||
|
for i in range(max_tries):
|
||
|
R_dup = get_resolvent_by_lookup(T, 0)
|
||
|
if dup_sqf_p(R_dup, ZZ):
|
||
|
break
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
else:
|
||
|
raise MaxTriesException
|
||
|
|
||
|
# Compute list L of degrees of irreducible factors of R, in increasing order:
|
||
|
fl = dup_factor_list(R_dup, ZZ)
|
||
|
L = sorted(sum([
|
||
|
[len(r) - 1] * e for r, e in fl[1]
|
||
|
], []))
|
||
|
|
||
|
if L == [6]:
|
||
|
return ((S4TransitiveSubgroups.A4, True) if has_square_disc(T)
|
||
|
else (S4TransitiveSubgroups.S4, False))
|
||
|
|
||
|
if L == [1, 1, 4]:
|
||
|
return (S4TransitiveSubgroups.C4, False)
|
||
|
|
||
|
if L == [2, 2, 2]:
|
||
|
return (S4TransitiveSubgroups.V, True)
|
||
|
|
||
|
assert L == [2, 4]
|
||
|
return (S4TransitiveSubgroups.D4, False)
|
||
|
|
||
|
|
||
|
def _galois_group_degree_5_hybrid(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 5.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Based on Alg 6.3.9 of [1], but uses a hybrid approach, combining resolvent
|
||
|
coeff lookup, with root approximation.
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.galois import S5TransitiveSubgroups
|
||
|
from sympy.combinatorics.permutations import Permutation
|
||
|
|
||
|
X5 = symbols("X0,X1,X2,X3,X4")
|
||
|
res = define_resolvents()
|
||
|
F51, _, s51 = res[(5, 1)]
|
||
|
F51 = F51.as_expr(*X5)
|
||
|
R51 = Resolvent(F51, X5, s51)
|
||
|
|
||
|
history = set()
|
||
|
reached_second_stage = False
|
||
|
for i in range(max_tries):
|
||
|
if i > 0:
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
R51_dup = get_resolvent_by_lookup(T, 1)
|
||
|
if not dup_sqf_p(R51_dup, ZZ):
|
||
|
continue
|
||
|
|
||
|
# First stage
|
||
|
# If we have not yet reached the second stage, then the group still
|
||
|
# might be S5, A5, or M20, so must test for that.
|
||
|
if not reached_second_stage:
|
||
|
sq_disc = has_square_disc(T)
|
||
|
|
||
|
if dup_irreducible_p(R51_dup, ZZ):
|
||
|
return ((S5TransitiveSubgroups.A5, True) if sq_disc
|
||
|
else (S5TransitiveSubgroups.S5, False))
|
||
|
|
||
|
if not sq_disc:
|
||
|
return (S5TransitiveSubgroups.M20, False)
|
||
|
|
||
|
# Second stage
|
||
|
reached_second_stage = True
|
||
|
# R51 must have an integer root for T.
|
||
|
# To choose our second resolvent, we need to know which conjugate of
|
||
|
# F51 is a root.
|
||
|
rounded_roots = R51.round_roots_to_integers_for_poly(T)
|
||
|
# These are integers, and candidates to be roots of R51.
|
||
|
# We find the first one that actually is a root.
|
||
|
for permutation_index, candidate_root in rounded_roots.items():
|
||
|
if not dup_eval(R51_dup, candidate_root, ZZ):
|
||
|
break
|
||
|
|
||
|
X = X5
|
||
|
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
|
||
|
s2_pre = [
|
||
|
Permutation(4),
|
||
|
Permutation(4)(0, 1)(2, 4)
|
||
|
]
|
||
|
|
||
|
i0 = permutation_index
|
||
|
sigma = s51[i0]
|
||
|
F2 = F2_pre.subs(zip(X, sigma(X)), simultaneous=True)
|
||
|
s2 = [sigma*tau*sigma for tau in s2_pre]
|
||
|
R2 = Resolvent(F2, X, s2)
|
||
|
R_dup, _, _ = R2.eval_for_poly(T)
|
||
|
d = dup_discriminant(R_dup, ZZ)
|
||
|
|
||
|
if d == 0:
|
||
|
continue
|
||
|
if is_square(d):
|
||
|
return (S5TransitiveSubgroups.C5, True)
|
||
|
else:
|
||
|
return (S5TransitiveSubgroups.D5, True)
|
||
|
|
||
|
raise MaxTriesException
|
||
|
|
||
|
|
||
|
def _galois_group_degree_5_lookup_ext_factor(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 5.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Based on Alg 6.3.9 of [1], but uses resolvent coeff lookup, plus
|
||
|
factorization over an algebraic extension.
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.galois import S5TransitiveSubgroups
|
||
|
|
||
|
_T = T
|
||
|
|
||
|
history = set()
|
||
|
for i in range(max_tries):
|
||
|
R_dup = get_resolvent_by_lookup(T, 1)
|
||
|
if dup_sqf_p(R_dup, ZZ):
|
||
|
break
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
else:
|
||
|
raise MaxTriesException
|
||
|
|
||
|
sq_disc = has_square_disc(T)
|
||
|
|
||
|
if dup_irreducible_p(R_dup, ZZ):
|
||
|
return ((S5TransitiveSubgroups.A5, True) if sq_disc
|
||
|
else (S5TransitiveSubgroups.S5, False))
|
||
|
|
||
|
if not sq_disc:
|
||
|
return (S5TransitiveSubgroups.M20, False)
|
||
|
|
||
|
# If we get this far, Gal(T) can only be D5 or C5.
|
||
|
# But for Gal(T) to have order 5, T must already split completely in
|
||
|
# the extension field obtained by adjoining a single one of its roots.
|
||
|
fl = Poly(_T, domain=ZZ.alg_field_from_poly(_T)).factor_list()[1]
|
||
|
if len(fl) == 5:
|
||
|
return (S5TransitiveSubgroups.C5, True)
|
||
|
else:
|
||
|
return (S5TransitiveSubgroups.D5, True)
|
||
|
|
||
|
|
||
|
def _galois_group_degree_6_lookup(T, max_tries=30, randomize=False):
|
||
|
r"""
|
||
|
Compute the Galois group of a polynomial of degree 6.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Based on Alg 6.3.10 of [1], but uses resolvent coeff lookup.
|
||
|
|
||
|
"""
|
||
|
from sympy.combinatorics.galois import S6TransitiveSubgroups
|
||
|
|
||
|
# First resolvent:
|
||
|
|
||
|
history = set()
|
||
|
for i in range(max_tries):
|
||
|
R_dup = get_resolvent_by_lookup(T, 1)
|
||
|
if dup_sqf_p(R_dup, ZZ):
|
||
|
break
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
else:
|
||
|
raise MaxTriesException
|
||
|
|
||
|
fl = dup_factor_list(R_dup, ZZ)
|
||
|
|
||
|
# Group the factors by degree.
|
||
|
factors_by_deg = defaultdict(list)
|
||
|
for r, _ in fl[1]:
|
||
|
factors_by_deg[len(r) - 1].append(r)
|
||
|
|
||
|
L = sorted(sum([
|
||
|
[d] * len(ff) for d, ff in factors_by_deg.items()
|
||
|
], []))
|
||
|
|
||
|
T_has_sq_disc = has_square_disc(T)
|
||
|
|
||
|
if L == [1, 2, 3]:
|
||
|
f1 = factors_by_deg[3][0]
|
||
|
return ((S6TransitiveSubgroups.C6, False) if has_square_disc(f1)
|
||
|
else (S6TransitiveSubgroups.D6, False))
|
||
|
|
||
|
elif L == [3, 3]:
|
||
|
f1, f2 = factors_by_deg[3]
|
||
|
any_square = has_square_disc(f1) or has_square_disc(f2)
|
||
|
return ((S6TransitiveSubgroups.G18, False) if any_square
|
||
|
else (S6TransitiveSubgroups.G36m, False))
|
||
|
|
||
|
elif L == [2, 4]:
|
||
|
if T_has_sq_disc:
|
||
|
return (S6TransitiveSubgroups.S4p, True)
|
||
|
else:
|
||
|
f1 = factors_by_deg[4][0]
|
||
|
return ((S6TransitiveSubgroups.A4xC2, False) if has_square_disc(f1)
|
||
|
else (S6TransitiveSubgroups.S4xC2, False))
|
||
|
|
||
|
elif L == [1, 1, 4]:
|
||
|
return ((S6TransitiveSubgroups.A4, True) if T_has_sq_disc
|
||
|
else (S6TransitiveSubgroups.S4m, False))
|
||
|
|
||
|
elif L == [1, 5]:
|
||
|
return ((S6TransitiveSubgroups.PSL2F5, True) if T_has_sq_disc
|
||
|
else (S6TransitiveSubgroups.PGL2F5, False))
|
||
|
|
||
|
elif L == [1, 1, 1, 3]:
|
||
|
return (S6TransitiveSubgroups.S3, False)
|
||
|
|
||
|
assert L == [6]
|
||
|
|
||
|
# Second resolvent:
|
||
|
|
||
|
history = set()
|
||
|
for i in range(max_tries):
|
||
|
R_dup = get_resolvent_by_lookup(T, 2)
|
||
|
if dup_sqf_p(R_dup, ZZ):
|
||
|
break
|
||
|
_, T = tschirnhausen_transformation(T, max_tries=max_tries,
|
||
|
history=history,
|
||
|
fixed_order=not randomize)
|
||
|
else:
|
||
|
raise MaxTriesException
|
||
|
|
||
|
T_has_sq_disc = has_square_disc(T)
|
||
|
|
||
|
if dup_irreducible_p(R_dup, ZZ):
|
||
|
return ((S6TransitiveSubgroups.A6, True) if T_has_sq_disc
|
||
|
else (S6TransitiveSubgroups.S6, False))
|
||
|
else:
|
||
|
return ((S6TransitiveSubgroups.G36p, True) if T_has_sq_disc
|
||
|
else (S6TransitiveSubgroups.G72, False))
|
||
|
|
||
|
|
||
|
@public
|
||
|
def galois_group(f, *gens, by_name=False, max_tries=30, randomize=False, **args):
|
||
|
r"""
|
||
|
Compute the Galois group for polynomials *f* up to degree 6.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import galois_group
|
||
|
>>> from sympy.abc import x
|
||
|
>>> f = x**4 + 1
|
||
|
>>> G, alt = galois_group(f)
|
||
|
>>> print(G)
|
||
|
PermutationGroup([
|
||
|
(0 1)(2 3),
|
||
|
(0 2)(1 3)])
|
||
|
|
||
|
The group is returned along with a boolean, indicating whether it is
|
||
|
contained in the alternating group $A_n$, where $n$ is the degree of *T*.
|
||
|
Along with other group properties, this can help determine which group it
|
||
|
is:
|
||
|
|
||
|
>>> alt
|
||
|
True
|
||
|
>>> G.order()
|
||
|
4
|
||
|
|
||
|
Alternatively, the group can be returned by name:
|
||
|
|
||
|
>>> G_name, _ = galois_group(f, by_name=True)
|
||
|
>>> print(G_name)
|
||
|
S4TransitiveSubgroups.V
|
||
|
|
||
|
The group itself can then be obtained by calling the name's
|
||
|
``get_perm_group()`` method:
|
||
|
|
||
|
>>> G_name.get_perm_group()
|
||
|
PermutationGroup([
|
||
|
(0 1)(2 3),
|
||
|
(0 2)(1 3)])
|
||
|
|
||
|
Group names are values of the enum classes
|
||
|
:py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`,
|
||
|
:py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`,
|
||
|
etc.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
f : Expr
|
||
|
Irreducible polynomial over :ref:`ZZ` or :ref:`QQ`, whose Galois group
|
||
|
is to be determined.
|
||
|
gens : optional list of symbols
|
||
|
For converting *f* to Poly, and will be passed on to the
|
||
|
:py:func:`~.poly_from_expr` function.
|
||
|
by_name : bool, default False
|
||
|
If ``True``, the Galois group will be returned by name.
|
||
|
Otherwise it will be returned as a :py:class:`~.PermutationGroup`.
|
||
|
max_tries : int, default 30
|
||
|
Make at most this many attempts in those steps that involve
|
||
|
generating Tschirnhausen transformations.
|
||
|
randomize : bool, default False
|
||
|
If ``True``, then use random coefficients when generating Tschirnhausen
|
||
|
transformations. Otherwise try transformations in a fixed order. Both
|
||
|
approaches start with small coefficients and degrees and work upward.
|
||
|
args : optional
|
||
|
For converting *f* to Poly, and will be passed on to the
|
||
|
:py:func:`~.poly_from_expr` function.
|
||
|
|
||
|
Returns
|
||
|
=======
|
||
|
|
||
|
Pair ``(G, alt)``
|
||
|
The first element ``G`` indicates the Galois group. It is an instance
|
||
|
of one of the :py:class:`sympy.combinatorics.galois.S1TransitiveSubgroups`
|
||
|
:py:class:`sympy.combinatorics.galois.S2TransitiveSubgroups`, etc. enum
|
||
|
classes if *by_name* was ``True``, and a :py:class:`~.PermutationGroup`
|
||
|
if ``False``.
|
||
|
|
||
|
The second element is a boolean, saying whether the group is contained
|
||
|
in the alternating group $A_n$ ($n$ the degree of *T*).
|
||
|
|
||
|
Raises
|
||
|
======
|
||
|
|
||
|
ValueError
|
||
|
if *f* is of an unsupported degree.
|
||
|
|
||
|
MaxTriesException
|
||
|
if could not complete before exceeding *max_tries* in those steps
|
||
|
that involve generating Tschirnhausen transformations.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
.Poly.galois_group
|
||
|
|
||
|
"""
|
||
|
gens = gens or []
|
||
|
args = args or {}
|
||
|
|
||
|
try:
|
||
|
F, opt = poly_from_expr(f, *gens, **args)
|
||
|
except PolificationFailed as exc:
|
||
|
raise ComputationFailed('galois_group', 1, exc)
|
||
|
|
||
|
return F.galois_group(by_name=by_name, max_tries=max_tries,
|
||
|
randomize=randomize)
|