Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: Math 582b
Views: 2495

Defining Number Fields -- part 1

William Stein

Jan 11, 2016

(Note: Steven Sivek is listed as one of the authors, and he will interview for a tenure track job here later this month.)

Part 1: Absolute number fields defined by a single polynomial

The simplest number field: Q\QQ

RationalField()
Rational Field
QQ
Rational Field

Define "x":

# these are all equivalent, except the second two also define R x = polygen(QQ,'x') # polynomial generator over QQ R.<x> = PolynomialRing(QQ) # another way R.<x> = QQ[] # another way print x
x
preparse('R.<x> = PolynomialRing(QQ)')
"R = PolynomialRing(QQ, names=('x',)); (x,) = R._first_ngens(1)"
parent(x)
Univariate Polynomial Ring in x over Rational Field

In Sage you must specify the name of the generator of the field. There is no default and no way to change it once you specify it:

# This should fail NumberField(x^2 + 1)
Error in lines 2-2 Traceback (most recent call last): File "/projects/sage/sage-6.10/local/lib/python2.7/site-packages/smc_sagews/sage_server.py", line 905, in execute exec compile(block+'\n', '', 'single') in namespace, locals File "", line 1, in <module> File "/projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/rings/number_field/number_field.py", line 491, in NumberField return NumberField_version2(polynomial=polynomial, name=name, check=check, embedding=embedding, latex_name=latex_name, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structure=structure) File "sage/structure/factory.pyx", line 364, in sage.structure.factory.UniqueFactory.__call__ (/projects/sage/sage-6.10/src/build/cythonized/sage/structure/factory.c:1245) key, kwds = self.create_key_and_extra_args(*args, **kwds) File "/projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/rings/number_field/number_field.py", line 560, in create_key_and_extra_args raise TypeError("You must specify the name of the generator.") TypeError: You must specify the name of the generator.
%gp Mod(x^2 + 1, x^+7)
Mod(x^2 + 1, x^7)
QQ[sqrt(-2)]
Number Field in a with defining polynomial x^2 + 2
QuadraticField(3)
Number Field in a with defining polynomial x^2 - 3
K = NumberField(x^2 + 1, 'alpha') K
Number Field in alpha with defining polynomial x^2 + 1

You can get the field generator of the number field (that corresponds to the zero of the polynomial) in a few ways.

# works; is like Magma, but 0 based; is *NOT* valid python K.0
alpha
# The Sage preparser convert K.0 into K.gen(0); you can use that directly preparse("K.0")
'K.gen(0)'
K.gen(0)
alpha
K.gens() # all of the generators, as a 1-tuple
(alpha,)

You can also create the number field and assign the generator to a named variable all at once.

K.<alpha> = NumberField(x^2 + 1) print "K=", K print "alpha = ", alpha
K= Number Field in alpha with defining polynomial x^2 + 1 alpha = alpha
alpha^2 # confirm
-1
show( (2+3/2*alpha)^10 )
184623128α+96532871024\displaystyle \frac{184623}{128} \alpha + \frac{9653287}{1024}

Robert Bradshaw also made it possible to construct a number field using certain symbolic expressions. For example:

QQ[sqrt(-1)]
Number Field in I with defining polynomial x^2 + 1
QQ[2^(1/3)]
Number Field in a with defining polynomial x^3 - 2

Say something about the underlying subtle algorithm used for the above, which relies on:

a = 2^(1/3) a.minpoly() # why no a.minimal_polynomial()
x^3 - 2
a.minpoly??
File: /projects/sage/sage-6.10/src/sage/symbolic/expression.pyx Source: def minpoly(self, *args, **kwds): """ Return the minimal polynomial of this symbolic expression. EXAMPLES:: sage: golden_ratio.minpoly() x^2 - x - 1 """ try: obj = self.pyobject() return obj.minpoly() except AttributeError: pass except TypeError: pass from sage.calculus.calculus import minpoly return minpoly(self, *args, **kwds)
sage.calculus.calculus.minpoly??
File: /projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/calculus/calculus.py Source: def minpoly(ex, var='x', algorithm=None, bits=None, degree=None, epsilon=0): r""" Return the minimal polynomial of self, if possible. INPUT: - ``var`` - polynomial variable name (default 'x') - ``algorithm`` - 'algebraic' or 'numerical' (default both, but with numerical first) - ``bits`` - the number of bits to use in numerical approx - ``degree`` - the expected algebraic degree - ``epsilon`` - return without error as long as f(self) epsilon, in the case that the result cannot be proven. All of the above parameters are optional, with epsilon=0, bits and degree tested up to 1000 and 24 by default respectively. The numerical algorithm will be faster if bits and/or degree are given explicitly. The algebraic algorithm ignores the last three parameters. OUTPUT: The minimal polynomial of self. If the numerical algorithm is used then it is proved symbolically when epsilon=0 (default). If the minimal polynomial could not be found, two distinct kinds of errors are raised. If no reasonable candidate was found with the given bit/degree parameters, a ``ValueError`` will be raised. If a reasonable candidate was found but (perhaps due to limits in the underlying symbolic package) was unable to be proved correct, a ``NotImplementedError`` will be raised. ALGORITHM: Two distinct algorithms are used, depending on the algorithm parameter. By default, the numerical algorithm is attempted first, then the algebraic one. Algebraic: Attempt to evaluate this expression in QQbar, using cyclotomic fields to resolve exponential and trig functions at rational multiples of pi, field extensions to handle roots and rational exponents, and computing compositums to represent the full expression as an element of a number field where the minimal polynomial can be computed exactly. The bits, degree, and epsilon parameters are ignored. Numerical: Computes a numerical approximation of ``self`` and use PARI's algdep to get a candidate minpoly `f`. If `f(\mathtt{self})`, evaluated to a higher precision, is close enough to 0 then evaluate `f(\mathtt{self})` symbolically, attempting to prove vanishing. If this fails, and ``epsilon`` is non-zero, return `f` if and only if `f(\mathtt{self}) < \mathtt{epsilon}`. Otherwise raise a ``ValueError`` (if no suitable candidate was found) or a ``NotImplementedError`` (if a likely candidate was found but could not be proved correct). EXAMPLES: First some simple examples:: sage: sqrt(2).minpoly() x^2 - 2 sage: minpoly(2^(1/3)) x^3 - 2 sage: minpoly(sqrt(2) + sqrt(-1)) x^4 - 2*x^2 + 9 sage: minpoly(sqrt(2)-3^(1/3)) x^6 - 6*x^4 + 6*x^3 + 12*x^2 + 36*x + 1 Works with trig and exponential functions too. :: sage: sin(pi/3).minpoly() x^2 - 3/4 sage: sin(pi/7).minpoly() x^6 - 7/4*x^4 + 7/8*x^2 - 7/64 sage: minpoly(exp(I*pi/17)) x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 Here we verify it gives the same result as the abstract number field. :: sage: (sqrt(2) + sqrt(3) + sqrt(6)).minpoly() x^4 - 22*x^2 - 48*x - 23 sage: K.<a,b> = NumberField([x^2-2, x^2-3]) sage: (a+b+a*b).absolute_minpoly() x^4 - 22*x^2 - 48*x - 23 The minpoly function is used implicitly when creating number fields:: sage: x = var('x') sage: eqn = x^3 + sqrt(2)*x + 5 == 0 sage: a = solve(eqn, x)[0].rhs() sage: QQ[a] Number Field in a with defining polynomial x^6 + 10*x^3 - 2*x^2 + 25 Here we solve a cubic and then recover it from its complicated radical expansion. :: sage: f = x^3 - x + 1 sage: a = f.solve(x)[0].rhs(); a -1/2*(1/18*sqrt(23)*sqrt(3) - 1/2)^(1/3)*(I*sqrt(3) + 1) - 1/6*(-I*sqrt(3) + 1)/(1/18*sqrt(23)*sqrt(3) - 1/2)^(1/3) sage: a.minpoly() x^3 - x + 1 Note that simplification may be necessary to see that the minimal polynomial is correct. :: sage: a = sqrt(2)+sqrt(3)+sqrt(5) sage: f = a.minpoly(); f x^8 - 40*x^6 + 352*x^4 - 960*x^2 + 576 sage: f(a) (sqrt(5) + sqrt(3) + sqrt(2))^8 - 40*(sqrt(5) + sqrt(3) + sqrt(2))^6 + 352*(sqrt(5) + sqrt(3) + sqrt(2))^4 - 960*(sqrt(5) + sqrt(3) + sqrt(2))^2 + 576 sage: f(a).expand() 0 :: sage: a = sin(pi/7) sage: f = a.minpoly(algorithm='numerical'); f x^6 - 7/4*x^4 + 7/8*x^2 - 7/64 sage: f(a).horner(a).numerical_approx(100) 0.00000000000000000000000000000 The degree must be high enough (default tops out at 24). :: sage: a = sqrt(3) + sqrt(2) sage: a.minpoly(algorithm='numerical', bits=100, degree=3) Traceback (most recent call last): ... ValueError: Could not find minimal polynomial (100 bits, degree 3). sage: a.minpoly(algorithm='numerical', bits=100, degree=10) x^4 - 10*x^2 + 1 :: sage: cos(pi/33).minpoly(algorithm='algebraic') x^10 + 1/2*x^9 - 5/2*x^8 - 5/4*x^7 + 17/8*x^6 + 17/16*x^5 - 43/64*x^4 - 43/128*x^3 + 3/64*x^2 + 3/128*x + 1/1024 sage: cos(pi/33).minpoly(algorithm='numerical') x^10 + 1/2*x^9 - 5/2*x^8 - 5/4*x^7 + 17/8*x^6 + 17/16*x^5 - 43/64*x^4 - 43/128*x^3 + 3/64*x^2 + 3/128*x + 1/1024 Sometimes it fails, as it must given that some numbers aren't algebraic:: sage: sin(1).minpoly(algorithm='numerical') Traceback (most recent call last): ... ValueError: Could not find minimal polynomial (1000 bits, degree 24). .. note:: Of course, failure to produce a minimal polynomial does not necessarily indicate that this number is transcendental. """ if algorithm is None or algorithm.startswith('numeric'): bits_list = [bits] if bits else [100,200,500,1000] degree_list = [degree] if degree else [2,4,8,12,24] for bits in bits_list: a = ex.numerical_approx(bits) check_bits = int(1.25 * bits + 80) aa = ex.numerical_approx(check_bits) for degree in degree_list: f = QQ[var](algdep(a, degree)) # TODO: use the known_bits parameter? # If indeed we have found a minimal polynomial, # it should be accurate to a much higher precision. error = abs(f(aa)) dx = ~RR(Integer(1) << (check_bits - degree - 2)) expected_error = abs(f.derivative()(CC(aa))) * dx if error < expected_error: # Degree might have been an over-estimate, # factor because we want (irreducible) minpoly. ff = f.factor() for g, e in ff: lead = g.leading_coefficient() if lead != 1: g = g / lead expected_error = abs(g.derivative()(CC(aa))) * dx error = abs(g(aa)) if error < expected_error: # See if we can prove equality exactly if g(ex).simplify_trig().canonicalize_radical() == 0: return g # Otherwise fall back to numerical guess elif epsilon and error < epsilon: return g elif algorithm is not None: raise NotImplementedError("Could not prove minimal polynomial %s (epsilon %s)" % (g, RR(error).str(no_sci=False))) if algorithm is not None: raise ValueError("Could not find minimal polynomial (%s bits, degree %s)." % (bits, degree)) if algorithm is None or algorithm == 'algebraic': from sage.rings.all import QQbar return QQ[var](QQbar(ex).minpoly()) raise ValueError("Unknown algorithm: %s" % algorithm)

Quick in-class exercise:

P1. Create the following number fields:

  • Q(5)\QQ(\sqrt{\sqrt{5}})

  • Q(ζ11)\QQ(\zeta_{11})

  • Q[y]/(y3+y+7)\QQ[y]/(y^3 + y + 7)

  • Q[x]/(x100+x17+1)\QQ[x]/(x^{100} + x^17 + 1)

P2. What happens if the polynomial is reducible?

P3. What happens if the defining polynomial is not monic?

R.<x> = PolynomialRing(QQ)
S = R.quotient(x^100 + x^17 + 1)
S.cayley_graph??
File: /projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/categories/semigroups.py Source: def cayley_graph(self, side="right", simple=False, elements = None, generators = None, connecting_set = None): r""" Return the Cayley graph for this finite semigroup. INPUT: - ``side`` -- "left", "right", or "twosided": the side on which the generators act (default:"right") - ``simple`` -- boolean (default:False): if True, returns a simple graph (no loops, no labels, no multiple edges) - ``generators`` -- a list, tuple, or family of elements of ``self`` (default: ``self.semigroup_generators()``) - ``connecting_set`` -- alias for ``generators``; deprecated - ``elements`` -- a list (or iterable) of elements of ``self`` OUTPUT: - :class:`DiGraph` EXAMPLES: We start with the (right) Cayley graphs of some classical groups:: sage: D4 = DihedralGroup(4); D4 Dihedral group of order 8 as a permutation group sage: G = D4.cayley_graph() sage: show(G, color_by_label=True, edge_labels=True) sage: A5 = AlternatingGroup(5); A5 Alternating group of order 5!/2 as a permutation group sage: G = A5.cayley_graph() sage: G.show3d(color_by_label=True, edge_size=0.01, edge_size2=0.02, vertex_size=0.03) sage: G.show3d(vertex_size=0.03, edge_size=0.01, edge_size2=0.02, vertex_colors={(1,1,1):G.vertices()}, bgcolor=(0,0,0), color_by_label=True, xres=700, yres=700, iterations=200) # long time (less than a minute) sage: G.num_edges() 120 sage: w = WeylGroup(['A',3]) sage: d = w.cayley_graph(); d Digraph on 24 vertices sage: d.show3d(color_by_label=True, edge_size=0.01, vertex_size=0.03) Alternative generators may be specified:: sage: G = A5.cayley_graph(generators=[A5.gens()[0]]) sage: G.num_edges() 60 sage: g=PermutationGroup([(i+1,j+1) for i in range(5) for j in range(5) if j!=i]) sage: g.cayley_graph(generators=[(1,2),(2,3)]) Digraph on 120 vertices If ``elements`` is specified, then only the subgraph induced and those elements is returned. Here we use it to display the Cayley graph of the free monoid truncated on the elements of length at most 3:: sage: M = Monoids().example(); M An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') sage: elements = [ M.prod(w) for w in sum((list(Words(M.semigroup_generators(),k)) for k in range(4)),[]) ] sage: G = M.cayley_graph(elements = elements) sage: G.num_verts(), G.num_edges() (85, 84) sage: G.show3d(color_by_label=True, edge_size=0.001, vertex_size=0.01) We now illustrate the ``side`` and ``simple`` options on a semigroup:: sage: S = FiniteSemigroups().example(alphabet=('a','b')) sage: g = S.cayley_graph(simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('b', 'ba', None)] :: sage: g = S.cayley_graph(side="left", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided", simple=True) sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'ab', None), ('a', 'ba', None), ('ab', 'ba', None), ('b', 'ab', None), ('b', 'ba', None), ('ba', 'ab', None)] :: sage: g = S.cayley_graph(side="twosided") sage: g.vertices() ['a', 'ab', 'b', 'ba'] sage: g.edges() [('a', 'a', (0, 'left')), ('a', 'a', (0, 'right')), ('a', 'ab', (1, 'right')), ('a', 'ba', (1, 'left')), ('ab', 'ab', (0, 'left')), ('ab', 'ab', (0, 'right')), ('ab', 'ab', (1, 'right')), ('ab', 'ba', (1, 'left')), ('b', 'ab', (0, 'left')), ('b', 'b', (1, 'left')), ('b', 'b', (1, 'right')), ('b', 'ba', (0, 'right')), ('ba', 'ab', (0, 'left')), ('ba', 'ba', (0, 'right')), ('ba', 'ba', (1, 'left')), ('ba', 'ba', (1, 'right'))] :: sage: s1 = SymmetricGroup(1); s = s1.cayley_graph(); s.vertices() [()] TESTS:: sage: SymmetricGroup(2).cayley_graph(side="both") Traceback (most recent call last): ... ValueError: option 'side' must be 'left', 'right' or 'twosided' .. TODO:: - Add more options for constructing subgraphs of the Cayley graph, handling the standard use cases when exploring large/infinite semigroups (a predicate, generators of an ideal, a maximal length in term of the generators) - Specify good default layout/plot/latex options in the graph - Generalize to combinatorial modules with module generators / operators AUTHORS: - Bobby Moretti (2007-08-10) - Robert Miller (2008-05-01): editing - Nicolas M. Thiery (2008-12): extension to semigroups, ``side``, ``simple``, and ``elements`` options, ... """ from sage.graphs.digraph import DiGraph from monoids import Monoids from groups import Groups if not side in ["left", "right", "twosided"]: raise ValueError("option 'side' must be 'left', 'right' or 'twosided'") if elements is None: assert self.is_finite(), "elements should be specified for infinite semigroups" elements = self else: elements = set(elements) if simple or self in Groups(): result = DiGraph() else: result = DiGraph(multiedges = True, loops = True) result.add_vertices(elements) if connecting_set is not None: generators = connecting_set if generators is None: if self in Monoids and hasattr(self, "monoid_generators"): generators = self.monoid_generators() else: generators = self.semigroup_generators() if isinstance(generators, (list, tuple)): generators = dict((self(g), self(g)) for g in generators) left = (side == "left" or side == "twosided") right = (side == "right" or side == "twosided") def add_edge(source, target, label, side_label): """ Skips edges whose targets are not in elements Return an appropriate edge given the options """ if (elements is not self and target not in elements): return if simple: result.add_edge([source, target]) elif side == "twosided": result.add_edge([source, target, (label, side_label)]) else: result.add_edge([source, target, label]) for x in elements: for i in generators.keys(): if left: add_edge(x, generators[i]*x, i, "left" ) if right: add_edge(x, x*generators[i], i, "right") return result
S.cayley_graph()
Error in lines 1-1 Traceback (most recent call last): File "/projects/sage/sage-6.10/local/lib/python2.7/site-packages/smc_sagews/sage_server.py", line 905, in execute exec compile(block+'\n', '', 'single') in namespace, locals File "", line 1, in <module> File "/projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/categories/semigroups.py", line 307, in cayley_graph assert self.is_finite(), "elements should be specified for infinite semigroups" File "sage/rings/ring.pyx", line 886, in sage.rings.ring.Ring.is_finite (/projects/sage/sage-6.10/src/build/cythonized/sage/rings/ring.c:8412) raise NotImplementedError NotImplementedError
K.<alpha> = NumberField(x^2 + 1)
K.is_isomorphic??
File: /projects/sage/sage-6.10/local/lib/python2.7/site-packages/sage/rings/number_field/number_field.py Source: def is_isomorphic(self, other): """ Return True if self is isomorphic as a number field to other. EXAMPLES:: sage: k.<a> = NumberField(x^2 + 1) sage: m.<b> = NumberField(x^2 + 4) sage: k.is_isomorphic(m) True sage: m.<b> = NumberField(x^2 + 5) sage: k.is_isomorphic (m) False :: sage: k = NumberField(x^3 + 2, 'a') sage: k.is_isomorphic(NumberField((x+1/3)^3 + 2, 'b')) True sage: k.is_isomorphic(NumberField(x^3 + 4, 'b')) True sage: k.is_isomorphic(NumberField(x^3 + 5, 'b')) False """ if not isinstance(other, NumberField_generic): raise ValueError("other must be a generic number field.") t = self.pari_polynomial().nfisisom(other.pari_polynomial()) return t != 0