#!/usr/bin/env python # coding: utf-8 # # Solutions to some exercises # # ## Exercise I.7 # # > Let ω ∈ ℂ be a cube root of unity, the ring ℤ[ω] is also known as the Eisenstein integers. Determine all elliptic curves with complex multiplication by ℤ[ω]. # # Let's start with ω: we can use Sage's symbolic calculus engine to construct it as a complex number # In[1]: w = e^(2*i*pi / 3) w # Careful, Sage's symbolic elements do not evaluate powers automatically: # In[2]: w^3 # To verify ω³ = 1 we need to use the function `.expand()` # In[3]: (w^3).expand() # Now, we can construct the order, and get its discriminant # In[4]: O = ZZ[w] # In[5]: O.discriminant() # Of, course, we knew this already, because we knew the minimal polynomial of ω. # # By the way, we could have used Sage's number fields, instead of the symbolic engine. # In[6]: K. = CyclotomicField(3) K, w # In[7]: w^3 # In[8]: chi = w.minpoly() chi # In[9]: chi.discriminant() # Constructing the order also works # In[10]: O = ZZ[w] O # In[11]: O.discriminant() # Now, we can use the Hilbert class polynomial to get all the j-invariants with CM by -3 # In[12]: H = hilbert_class_polynomial(-3) H # Clearly, the only root is j=0, so we learn that -3 has class number 1 # In[13]: H.roots() # Hence, the only curve is defined over ℚ # In[14]: E = EllipticCurve(j=0) E # In[15]: E.has_cm() # In[16]: E.cm_discriminant() # By default, Sage does not use the short Weierstrass model (y² = x³ + ax + b) for CM elliptic curves. We can get one like this # In[17]: E.short_weierstrass_model() # But here is a more famous (non isomorphic) equation for j = 0 # In[18]: F = EllipticCurve([0,1]) F # In[19]: F.j_invariant() # In[20]: E.is_isomorphic(F) # Let's find a prime of supersingular reduction for our curve. First, we must exclude primes of singular reduction # In[21]: F.discriminant().factor() # It would seem that the curve has singular reduction at 2 and 3, but actually Sage's preferred equation shows that 2 is not such a prime # In[22]: E.discriminant().factor() # and it turns out that it is indeed the only supersingular curve over 𝔽₂ # In[23]: E.change_ring(GF(2)).is_supersingular() # Let's try to find a prime p > 3 such that the curve has supseringular reduction. We need -3 to not be a square modulo p # In[24]: GF(5)(-3).is_square() # In[25]: Ebar = E.change_ring(GF(5)) Ebar # In[26]: Ebar.is_supersingular() # In[27]: Ebar.trace_of_frobenius() # ## Exercise II.6 # # Find a prime power $q$ and an elliptic curve $E/𝔽_q$ such that the 3-isogeny volcano # of $E$ is the same as the one below. # In[28]: get_ipython().run_cell_magic('HTML', '', ' image/svg+xml \n') # We start from the crater: we look for some discriminants of class number 7, and we check whether (3) splits in the ring of integers # In[29]: for i in range(1,1000): D = fundamental_discriminant(-i) if hilbert_class_polynomial(D).degree() == 7: print(D, GF(3)(D).is_square()) # We may thus choose D = -71 # In[30]: D = -71 H = hilbert_class_polynomial(-71) H # We now need to find a prime of ordinary reduction, and for the fun we also ask that the Hilbert class polynomial splits in $𝔽_p$, so that the curves are defined over it. # In[31]: for p in primes(3, 1000): if GF(p)(D).is_square(): Hp = H.change_ring(GF(p)) if Hp.splitting_field('x').degree() == 1: print(p, Hp.is_squarefree()) # 71 is obviously an outlier, we may thus take 107. We get all the j-invariants by factoring the Hilbert class polynomial # In[32]: p = 107 k = GF(p) Hp = H.change_ring(k) Hp.roots() # In[33]: E = EllipticCurve(j=GF(p)(77)) E # In[34]: E.isogenies_prime_degree(3) # By looking at the list of rational isogenies, we see that only the crater is defined over $𝔽_p$. Let's investigate more. The factorization of the division polynomials gives us the generators of the kernels of the isogenies # In[35]: E.division_polynomial(3).factor() # However, by looking at the rational 3-torsion points, we see that only x = -15 defines a rational point, whereas x = -49 defines a point over a quadratic extension # In[36]: E(0).division_points(3) # In[37]: E.lift_x(-15) # In[38]: E.change_ring(GF(p^2)).lift_x(-49) # Said otherwise, the Frobenius π has two distinct eigenvalues ±1 modulo 3: x = -15 is associated to +1, while x = -49 is associated to -1. We can follow the crater along the direction +1 by taking at every step the isogeny defined by the unique $𝔽_p$-rational group of order $3$ # In[39]: EE = E print(EE.j_invariant()) while True: P = EE(0).division_points(3)[1] EE = EE.isogeny(P).codomain() print(EE.j_invariant()) if EE.j_invariant() == E.j_invariant(): break # Let's draw the graph via a depth first search, for confirmation # In[40]: def explore(G, E): jE = E.j_invariant() G.add_vertex(jE) for i in E.isogenies_prime_degree(3): F = i.codomain() jF = F.j_invariant() if jF not in G: explore(G, F) G.add_edge(jE, jF) return G explore(Graph(), E) # To get some vertical isogenies, we need to move to an algebraic extension. The factorization pattern of the division polynomial (or the matrix of π) suggests that an extension of degree 2 is enough. # In[41]: EE = E.change_ring(GF(p^2)) G = explore(Graph(), EE) G # In[42]: G.plot() # To get the last level, we need to take a new extension. By the general theory, we now know that the Frobenius endomorphism of any curve $E/𝔽_{p^2}$ on the bottom level must have matrix $\left(\begin{smallmatrix}1&1\\0&1\end{smallmatrix}\right) \mod 3$, and thus that $π^3=1\mod 3$. Thus, an extension of degree 3 will give the missing level. # In[43]: EE = E.change_ring(GF(p^6)) G = explore(Graph(), EE) G # In[44]: G.plot() # What if we want the full volcano to be defined over $𝔽_p$? Instead of focusing on the fundamental discriminant (associated to the crater), we may shoot directly for the class group of the lower level. Because the conductor of the lower level is 9, we look at $D = -71·9^2$ # In[45]: D*3^4 # In[46]: H = hilbert_class_polynomial(D*3^4) H # As expected, the lower level has class number 42. We proceed like before. # In[47]: for p in primes(3, 10000): if GF(p)(D*3^4).is_square(): Hp = H.change_ring(GF(p)) if Hp.splitting_field('x').degree() == 1: print(p, Hp.is_squarefree()) # In[48]: hilbert_class_polynomial(D*3^4).change_ring(GF(5851)).factor() # In[49]: E = EllipticCurve(j=GF(5851)(-106)) G = explore(Graph(), E) G # In[50]: G.plot()