Exemplo n.º 1
0
    def matrix(self, fill=True):
        """
        Returns the matrix whose entries give the minimal degrees of
        isogenies between curves in this class.

        INPUT:

        - ``fill`` -- boolean (default True).  If False then the
          matrix will contain only zeros and prime entries; if True it
          will fill in the other degrees.

        EXAMPLES::

            sage: isocls = EllipticCurve('15a3').isogeny_class(use_tuple=False)
            sage: isocls.matrix()
            [ 1  2  2  2  4  4  8  8]
            [ 2  1  4  4  8  8 16 16]
            [ 2  4  1  4  8  8 16 16]
            [ 2  4  4  1  2  2  4  4]
            [ 4  8  8  2  1  4  8  8]
            [ 4  8  8  2  4  1  2  2]
            [ 8 16 16  4  8  2  1  4]
            [ 8 16 16  4  8  2  4  1]
            sage: isocls.matrix(fill=False)
            [0 2 2 2 0 0 0 0]
            [2 0 0 0 0 0 0 0]
            [2 0 0 0 0 0 0 0]
            [2 0 0 0 2 2 0 0]
            [0 0 0 2 0 0 0 0]
            [0 0 0 2 0 0 2 2]
            [0 0 0 0 0 2 0 0]
            [0 0 0 0 0 2 0 0]
        """
        if self._mat is None:
            self._compute_matrix()
        mat = self._mat
        if fill and mat[0,0] == 0:
            from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix
            mat = fill_isogeny_matrix(mat)
        if not fill and mat[0,0] == 1:
            from sage.schemes.elliptic_curves.ell_curve_isogeny import unfill_isogeny_matrix
            mat = unfill_isogeny_matrix(mat)
        return mat
Exemplo n.º 2
0
    def matrix(self, fill=True):
        """
        Returns the matrix whose entries give the minimal degrees of
        isogenies between curves in this class.

        INPUT:

        - ``fill`` -- boolean (default True).  If False then the
          matrix will contain only zeros and prime entries; if True it
          will fill in the other degrees.

        EXAMPLES::

            sage: isocls = EllipticCurve('15a3').isogeny_class(use_tuple=False)
            sage: isocls.matrix()
            [ 1  2  2  2  4  4  8  8]
            [ 2  1  4  4  8  8 16 16]
            [ 2  4  1  4  8  8 16 16]
            [ 2  4  4  1  2  2  4  4]
            [ 4  8  8  2  1  4  8  8]
            [ 4  8  8  2  4  1  2  2]
            [ 8 16 16  4  8  2  1  4]
            [ 8 16 16  4  8  2  4  1]
            sage: isocls.matrix(fill=False)
            [0 2 2 2 0 0 0 0]
            [2 0 0 0 0 0 0 0]
            [2 0 0 0 0 0 0 0]
            [2 0 0 0 2 2 0 0]
            [0 0 0 2 0 0 0 0]
            [0 0 0 2 0 0 2 2]
            [0 0 0 0 0 2 0 0]
            [0 0 0 0 0 2 0 0]
        """
        if self._mat is None:
            self._compute_matrix()
        mat = self._mat
        if fill and mat[0, 0] == 0:
            from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix
            mat = fill_isogeny_matrix(mat)
        if not fill and mat[0, 0] == 1:
            from sage.schemes.elliptic_curves.ell_curve_isogeny import unfill_isogeny_matrix
            mat = unfill_isogeny_matrix(mat)
        return mat
Exemplo n.º 3
0
def make_graph(M):
    """
    Code extracted from Sage's elliptic curve isogeny class (reshaped
    in the case maxdegree==12)
    """
    from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
    from sage.graphs.graph import Graph
    n = M.nrows() # = M.ncols()
    G = Graph(unfill_isogeny_matrix(M), format='weighted_adjacency_matrix')
    MM = fill_isogeny_matrix(M)
    # The maximum degree classifies the shape of the isogeny
    # graph, though the number of vertices is often enough.
    # This only holds over Q, so this code will need to change
    # once other isogeny classes are implemented.
    if n == 1:
        # one vertex
        pass
    elif n == 2:
        # one edge, two vertices.  We align horizontally and put
        # the lower number on the left vertex.
        G.set_pos(pos={0:[-0.5,0],1:[0.5,0]})
    else:
        maxdegree = max(max(MM))
        if n == 3:
            # o--o--o
            centervert = [i for i in range(3) if max(MM.row(i)) < maxdegree][0]
            other = [i for i in range(3) if i != centervert]
            G.set_pos(pos={centervert:[0,0],other[0]:[-1,0],other[1]:[1,0]})
        elif maxdegree == 4:
            # o--o<8
            centervert = [i for i in range(4) if max(MM.row(i)) < maxdegree][0]
            other = [i for i in range(4) if i != centervert]
            G.set_pos(pos={centervert:[0,0],other[0]:[0,1],other[1]:[-0.8660254,-0.5],other[2]:[0.8660254,-0.5]})
        elif maxdegree == 27:
            # o--o--o--o
            centers = [i for i in range(4) if list(MM.row(i)).count(3) == 2]
            left = [j for j in range(4) if MM[centers[0],j] == 3 and j not in centers][0]
            right = [j for j in range(4) if MM[centers[1],j] == 3 and j not in centers][0]
            G.set_pos(pos={left:[-1.5,0],centers[0]:[-0.5,0],centers[1]:[0.5,0],right:[1.5,0]})
        elif n == 4:
            # square
            opp = [i for i in range(1,4) if not MM[0,i].is_prime()][0]
            other = [i for i in range(1,4) if i != opp]
            G.set_pos(pos={0:[1,1],other[0]:[-1,1],opp:[-1,-1],other[1]:[1,-1]})
        elif maxdegree == 8:
            # 8>o--o<8
            centers = [i for i in range(6) if list(MM.row(i)).count(2) == 3]
            left = [j for j in range(6) if MM[centers[0],j] == 2 and j not in centers]
            right = [j for j in range(6) if MM[centers[1],j] == 2 and j not in centers]
            G.set_pos(pos={centers[0]:[-0.5,0],left[0]:[-1,0.8660254],left[1]:[-1,-0.8660254],centers[1]:[0.5,0],right[0]:[1,0.8660254],right[1]:[1,-0.8660254]})
        elif maxdegree == 18:
            # two squares joined on an edge
            centers = [i for i in range(6) if list(MM.row(i)).count(3) == 2]
            top = [j for j in range(6) if MM[centers[0],j] == 3]
            bl = [j for j in range(6) if MM[top[0],j] == 2][0]
            br = [j for j in range(6) if MM[top[1],j] == 2][0]
            G.set_pos(pos={centers[0]:[0,0.5],centers[1]:[0,-0.5],top[0]:[-1,0.5],top[1]:[1,0.5],bl:[-1,-0.5],br:[1,-0.5]})
        elif maxdegree == 16:
            # tree from bottom, 3 regular except for the leaves.
            centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
            center = [i for i in centers if len([j for j in centers if MM[i,j] == 2]) == 2][0]
            centers.remove(center)
            bottom = [j for j in range(8) if MM[center,j] == 2 and j not in centers][0]
            left = [j for j in range(8) if MM[centers[0],j] == 2 and j != center]
            right = [j for j in range(8) if MM[centers[1],j] == 2 and j != center]
            G.set_pos(pos={center:[0,0],bottom:[0,-1],centers[0]:[-0.8660254,0.5],centers[1]:[0.8660254,0.5],left[0]:[-0.8660254,1.5],right[0]:[0.8660254,1.5],left[1]:[-1.7320508,0],right[1]:[1.7320508,0]})
        elif maxdegree == 12:
            # tent
            centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
            left = [j for j in range(8) if MM[centers[0],j] == 2]
            right = []
            for i in range(3):
                right.append([j for j in range(8) if MM[centers[1],j] == 2 and MM[left[i],j] == 3][0])
            G.set_pos(pos={centers[0]:[-0.3,0],centers[1]:[0.3,0],
                           left[0]:[-0.14,0.15], right[0]:[0.14,0.15],
                           left[1]:[-0.14,-0.15],right[1]:[0.14,-0.15],
                           left[2]:[-0.14,-0.3],right[2]:[0.14,-0.3]})

    G.relabel(range(1,n+1))
    return G
Exemplo n.º 4
0
def make_graph(M):
    """
    Code extracted from Sage's elliptic curve isogeny class (reshaped
    in the case maxdegree==12)
    """
    from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
    from sage.graphs.graph import Graph
    n = M.nrows() # = M.ncols()
    G = Graph(unfill_isogeny_matrix(M), format='weighted_adjacency_matrix')
    MM = fill_isogeny_matrix(M)
    # The maximum degree classifies the shape of the isogeny
    # graph, though the number of vertices is often enough.
    # This only holds over Q, so this code will need to change
    # once other isogeny classes are implemented.
    if n == 1:
        # one vertex
        pass
    elif n == 2:
        # one edge, two vertices.  We align horizontally and put
        # the lower number on the left vertex.
        G.set_pos(pos={0:[-0.5,0],1:[0.5,0]})
    else:
        maxdegree = max(max(MM))
        if n == 3:
            # o--o--o
            centervert = [i for i in range(3) if max(MM.row(i)) < maxdegree][0]
            other = [i for i in range(3) if i != centervert]
            G.set_pos(pos={centervert:[0,0],other[0]:[-1,0],other[1]:[1,0]})
        elif maxdegree == 4:
            # o--o<8
            centervert = [i for i in range(4) if max(MM.row(i)) < maxdegree][0]
            other = [i for i in range(4) if i != centervert]
            G.set_pos(pos={centervert:[0,0],other[0]:[0,1],other[1]:[-0.8660254,-0.5],other[2]:[0.8660254,-0.5]})
        elif maxdegree == 27:
            # o--o--o--o
            centers = [i for i in range(4) if list(MM.row(i)).count(3) == 2]
            left = [j for j in range(4) if MM[centers[0],j] == 3 and j not in centers][0]
            right = [j for j in range(4) if MM[centers[1],j] == 3 and j not in centers][0]
            G.set_pos(pos={left:[-1.5,0],centers[0]:[-0.5,0],centers[1]:[0.5,0],right:[1.5,0]})
        elif n == 4:
            # square
            opp = [i for i in range(1,4) if not MM[0,i].is_prime()][0]
            other = [i for i in range(1,4) if i != opp]
            G.set_pos(pos={0:[1,1],other[0]:[-1,1],opp:[-1,-1],other[1]:[1,-1]})
        elif maxdegree == 8:
            # 8>o--o<8
            centers = [i for i in range(6) if list(MM.row(i)).count(2) == 3]
            left = [j for j in range(6) if MM[centers[0],j] == 2 and j not in centers]
            right = [j for j in range(6) if MM[centers[1],j] == 2 and j not in centers]
            G.set_pos(pos={centers[0]:[-0.5,0],left[0]:[-1,0.8660254],left[1]:[-1,-0.8660254],centers[1]:[0.5,0],right[0]:[1,0.8660254],right[1]:[1,-0.8660254]})
        elif maxdegree == 18:
            # two squares joined on an edge
            centers = [i for i in range(6) if list(MM.row(i)).count(3) == 2]
            top = [j for j in range(6) if MM[centers[0],j] == 3]
            bl = [j for j in range(6) if MM[top[0],j] == 2][0]
            br = [j for j in range(6) if MM[top[1],j] == 2][0]
            G.set_pos(pos={centers[0]:[0,0.5],centers[1]:[0,-0.5],top[0]:[-1,0.5],top[1]:[1,0.5],bl:[-1,-0.5],br:[1,-0.5]})
        elif maxdegree == 16:
            # tree from bottom, 3 regular except for the leaves.
            centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
            center = [i for i in centers if len([j for j in centers if MM[i,j] == 2]) == 2][0]
            centers.remove(center)
            bottom = [j for j in range(8) if MM[center,j] == 2 and j not in centers][0]
            left = [j for j in range(8) if MM[centers[0],j] == 2 and j != center]
            right = [j for j in range(8) if MM[centers[1],j] == 2 and j != center]
            G.set_pos(pos={center:[0,0],bottom:[0,-1],centers[0]:[-0.8660254,0.5],centers[1]:[0.8660254,0.5],left[0]:[-0.8660254,1.5],right[0]:[0.8660254,1.5],left[1]:[-1.7320508,0],right[1]:[1.7320508,0]})
        elif maxdegree == 12:
            # tent
            centers = [i for i in range(8) if list(MM.row(i)).count(2) == 3]
            left = [j for j in range(8) if MM[centers[0],j] == 2]
            right = []
            for i in range(3):
                right.append([j for j in range(8) if MM[centers[1],j] == 2 and MM[left[i],j] == 3][0])
            G.set_pos(pos={centers[0]:[-0.3,0],centers[1]:[0.3,0],
                           left[0]:[-0.14,0.15], right[0]:[0.14,0.15],
                           left[1]:[-0.14,-0.15],right[1]:[0.14,-0.15],
                           left[2]:[-0.14,-0.3],right[2]:[0.14,-0.3]})

    G.relabel(range(1,n+1))
    return G
Exemplo n.º 5
0
    def _compute(self, verbose=False):
        """
        Computes the list of curves, the matrix and prime-degree
        isogenies.

        EXAMPLES::

            sage: K.<i> = QuadraticField(-1)
            sage: E = EllipticCurve(K, [0,0,0,0,1])
            sage: C = E.isogeny_class()
            sage: C2 = C.copy()
            sage: C2._mat
            sage: C2._compute()
            sage: C2._mat
            [1 3 6 2]
            [3 1 2 6]
            [6 2 1 3]
            [2 6 3 1]

            sage: C2._compute(verbose=True)
            possible isogeny degrees: [2, 3] -actual isogeny degrees: {2, 3} -added curve #1 (degree 2)... -added tuple [0, 1, 2]... -added tuple [1, 0, 2]... -added curve #2 (degree 3)... -added tuple [0, 2, 3]... -added tuple [2, 0, 3]...... relevant degrees: [2, 3]... -now completing the isogeny class... -processing curve #1... -added tuple [1, 0, 2]... -added tuple [0, 1, 2]... -added curve #3... -added tuple [1, 3, 3]... -added tuple [3, 1, 3]... -processing curve #2... -added tuple [2, 3, 2]... -added tuple [3, 2, 2]... -added tuple [2, 0, 3]... -added tuple [0, 2, 3]... -processing curve #3... -added tuple [3, 2, 2]... -added tuple [2, 3, 2]... -added tuple [3, 1, 3]... -added tuple [1, 3, 3]...... isogeny class has size 4
            Sorting permutation = {0: 1, 1: 2, 2: 0, 3: 3}
            Matrix = [1 3 6 2]
            [3 1 2 6]
            [6 2 1 3]
            [2 6 3 1]

        TESTS:

        Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong)::

            sage: K.<i> = NumberField(x^2+1)
            sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146])
            sage: C = E.isogeny_class()
            sage: curves = C.curves
            sage: isos = C.isogenies()
            sage: isos[0][3].codomain() == curves[3]
            True

        """
        from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
        from sage.matrix.all import MatrixSpace
        from sage.sets.set import Set
        self._maps = None

        E = self.E.global_minimal_model(semi_global=True)

        degs = possible_isogeny_degrees(E)
        if verbose:
            import sys
            sys.stdout.write(" possible isogeny degrees: %s" % degs)
            sys.stdout.flush()
        isogenies = E.isogenies_prime_degree(degs)
        if verbose:
            sys.stdout.write(" -actual isogeny degrees: %s" % Set([phi.degree() for phi in isogenies]))
            sys.stdout.flush()
        # Add all new codomains to the list and collect degrees:
        curves = [E]
        ncurves = 1
        degs = []
        # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi
        tuples = []

        def add_tup(t):
            for T in [t, [t[1],t[0],t[2],0]]:
                if not T in tuples:
                    tuples.append(T)
                    if verbose:
                        sys.stdout.write(" -added tuple %s..." % T[:3])
                        sys.stdout.flush()

        for phi in isogenies:
            E2 = phi.codomain()
            d = ZZ(phi.degree())
            if not any([E2.is_isomorphic(E3) for E3 in curves]):
                curves.append(E2)
                if verbose:
                    sys.stdout.write(" -added curve #%s (degree %s)..." % (ncurves,d))
                    sys.stdout.flush()
                add_tup([0,ncurves,d,phi])
                ncurves += 1
                if not d in degs:
                    degs.append(d)
        if verbose:
            sys.stdout.write("... relevant degrees: %s..." % degs)
            sys.stdout.write(" -now completing the isogeny class...")
            sys.stdout.flush()

        i = 1
        while i < ncurves:
            E1 = curves[i]
            if verbose:
                sys.stdout.write(" -processing curve #%s..." % i)
                sys.stdout.flush()

            isogenies = E1.isogenies_prime_degree(degs)

            for phi in isogenies:
                E2 = phi.codomain()
                d = phi.degree()
                js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)]
                if js: # seen codomain already -- up to isomorphism
                    j = js[0]
                    if phi.codomain()!=curves[j]:
                        iso = E2.isomorphism_to(curves[j])
                        phi.set_post_isomorphism(iso)
                    assert phi.domain()==curves[i] and phi.codomain()==curves[j]
                    add_tup([i,j,d,phi])
                else:
                    curves.append(E2)
                    if verbose:
                        sys.stdout.write(" -added curve #%s..." % ncurves)
                        sys.stdout.flush()
                    add_tup([i,ncurves,d,phi])
                    ncurves += 1
            i += 1

        if verbose:
            print("... isogeny class has size %s" % ncurves)

        # key function for sorting
        if E.has_rational_cm():
            key_function = lambda E: (-E.cm_discriminant(),flatten([list(ai) for ai in E.ainvs()]))
        else:
            key_function = lambda E: flatten([list(ai) for ai in E.ainvs()])

        self.curves = sorted(curves,key=key_function)
        perm = dict([(i,self.curves.index(E)) for i,E in enumerate(curves)])
        if verbose:
            print "Sorting permutation = %s" % perm

        mat = MatrixSpace(ZZ,ncurves)(0)
        self._maps = [[0]*ncurves for i in range(ncurves)]
        for i,j,l,phi in tuples:
            if phi!=0:
                mat[perm[i],perm[j]] = l
                self._maps[perm[i]][perm[j]] = phi
        self._mat = fill_isogeny_matrix(mat)
        if verbose:
            print "Matrix = %s" % self._mat

        if not E.has_rational_cm():
            self._qfmat = None
            return

        # In the CM case, we will have found some "horizontal"
        # isogenies of composite degree and would like to replace them
        # by isogenies of prime degree, mainly to make the isogeny
        # graph look better.  We also construct a matrix whose entries
        # are not degrees of cyclic isogenies, but rather quadratic
        # forms (in 1 or 2 variables) representing the isogeny
        # degrees.  For this we take a short cut: properly speaking,
        # when `\text{End}(E_1)=\text{End}(E_2)=O`, the set
        # `\text{Hom}(E_1,E_2)` is a rank `1` projective `O`-module,
        # hence has a well-defined ideal class associated to it, and
        # hence (using an identification between the ideal class group
        # and the group of classes of primitive quadratic forms of the
        # same discriminant) an equivalence class of quadratic forms.
        # But we currently only care about the numbers represented by
        # the form, i.e. which genus it is in rather than the exact
        # class.  So it suffices to find one form of the correct
        # discriminant which represents one isogeny degree from `E_1`
        # to `E_2` in order to obtain a form which represents all such
        # degrees.

        if verbose:
            print("Creating degree matrix (CM case)")

        allQs = {} # keys: discriminants d
                   # values: lists of equivalence classes of
                   # primitive forms of discriminant d
        def find_quadratic_form(d,n):
            if not d in allQs:
                from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives

                allQs[d] = BinaryQF_reduced_representatives(d, primitive_only=True)
            # now test which of the Qs represents n
            for Q in allQs[d]:
                if Q.solve_integer(n):
                    return Q
            raise ValueError("No form of discriminant %d represents %s" %(d,n))

        mat = self._mat
        qfmat = [[0 for i in range(ncurves)] for j in range(ncurves)]
        for i, E1 in enumerate(self.curves):
            for j, E2 in enumerate(self.curves):
                if j<i:
                    qfmat[i][j] = qfmat[j][i]
                    mat[i,j] = mat[j,i]
                elif i==j:
                    qfmat[i][j] = [1]
                    # mat[i,j] already 1
                else:
                    d = E1.cm_discriminant()
                    if d != E2.cm_discriminant():
                        qfmat[i][j] = [mat[i,j]]
                        # mat[i,j] already unique
                    else: # horizontal isogeny
                        q = find_quadratic_form(d,mat[i,j])
                        qfmat[i][j] = list(q)
                        mat[i,j] = q.small_prime_value()

        self._mat = mat
        self._qfmat = qfmat
        if verbose:
            print("new matrix = %s" % mat)
            print("matrix of forms = %s" % qfmat)
Exemplo n.º 6
0
    def _compute(self, verbose=False):
        """
        Computes the list of curves, the matrix and prime-degree
        isogenies.

        EXAMPLES::

            sage: K.<i> = QuadraticField(-1)
            sage: E = EllipticCurve(K, [0,0,0,0,1])
            sage: C = E.isogeny_class()
            sage: C2 = C.copy()
            sage: C2._mat
            sage: C2._compute()
            sage: C2._mat
            [1 3 6 2]
            [3 1 2 6]
            [6 2 1 3]
            [2 6 3 1]

            sage: C2._compute(verbose=True)
            possible isogeny degrees: [2, 3] -actual isogeny degrees: {2, 3} -added curve #1 (degree 2)... -added tuple [0, 1, 2]... -added tuple [1, 0, 2]... -added curve #2 (degree 3)... -added tuple [0, 2, 3]... -added tuple [2, 0, 3]...... relevant degrees: [2, 3]... -now completing the isogeny class... -processing curve #1... -added tuple [1, 0, 2]... -added tuple [0, 1, 2]... -added curve #3... -added tuple [1, 3, 3]... -added tuple [3, 1, 3]... -processing curve #2... -added tuple [2, 3, 2]... -added tuple [3, 2, 2]... -added tuple [2, 0, 3]... -added tuple [0, 2, 3]... -processing curve #3... -added tuple [3, 2, 2]... -added tuple [2, 3, 2]... -added tuple [3, 1, 3]... -added tuple [1, 3, 3]...... isogeny class has size 4
            Sorting permutation = {0: 1, 1: 2, 2: 0, 3: 3}
            Matrix = [1 3 6 2]
            [3 1 2 6]
            [6 2 1 3]
            [2 6 3 1]

        TESTS:

        Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong)::

            sage: K.<i> = NumberField(x^2+1)
            sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146])
            sage: C = E.isogeny_class()
            sage: curves = C.curves
            sage: isos = C.isogenies()
            sage: isos[0][3].codomain() == curves[3]
            True

        """
        from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix
        from sage.matrix.all import MatrixSpace
        from sage.sets.set import Set
        self._maps = None

        E = self.E.global_minimal_model(semi_global=True)

        degs = possible_isogeny_degrees(E)
        if verbose:
            import sys
            sys.stdout.write(" possible isogeny degrees: %s" % degs)
            sys.stdout.flush()
        isogenies = E.isogenies_prime_degree(degs)
        if verbose:
            sys.stdout.write(" -actual isogeny degrees: %s" % Set([phi.degree() for phi in isogenies]))
            sys.stdout.flush()
        # Add all new codomains to the list and collect degrees:
        curves = [E]
        ncurves = 1
        degs = []
        # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi
        tuples = []

        def add_tup(t):
            for T in [t, [t[1],t[0],t[2],0]]:
                if not T in tuples:
                    tuples.append(T)
                    if verbose:
                        sys.stdout.write(" -added tuple %s..." % T[:3])
                        sys.stdout.flush()

        for phi in isogenies:
            E2 = phi.codomain()
            d = ZZ(phi.degree())
            if not any([E2.is_isomorphic(E3) for E3 in curves]):
                curves.append(E2)
                if verbose:
                    sys.stdout.write(" -added curve #%s (degree %s)..." % (ncurves,d))
                    sys.stdout.flush()
                add_tup([0,ncurves,d,phi])
                ncurves += 1
                if not d in degs:
                    degs.append(d)
        if verbose:
            sys.stdout.write("... relevant degrees: %s..." % degs)
            sys.stdout.write(" -now completing the isogeny class...")
            sys.stdout.flush()

        i = 1
        while i < ncurves:
            E1 = curves[i]
            if verbose:
                sys.stdout.write(" -processing curve #%s..." % i)
                sys.stdout.flush()

            isogenies = E1.isogenies_prime_degree(degs)

            for phi in isogenies:
                E2 = phi.codomain()
                d = phi.degree()
                js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)]
                if js: # seen codomain already -- up to isomorphism
                    j = js[0]
                    if phi.codomain()!=curves[j]:
                        iso = E2.isomorphism_to(curves[j])
                        phi.set_post_isomorphism(iso)
                    assert phi.domain()==curves[i] and phi.codomain()==curves[j]
                    add_tup([i,j,d,phi])
                else:
                    curves.append(E2)
                    if verbose:
                        sys.stdout.write(" -added curve #%s..." % ncurves)
                        sys.stdout.flush()
                    add_tup([i,ncurves,d,phi])
                    ncurves += 1
            i += 1

        if verbose:
            print("... isogeny class has size %s" % ncurves)

        # key function for sorting
        if E.has_rational_cm():
            key_function = lambda E: (-E.cm_discriminant(),flatten([list(ai) for ai in E.ainvs()]))
        else:
            key_function = lambda E: flatten([list(ai) for ai in E.ainvs()])

        self.curves = sorted(curves,key=key_function)
        perm = dict([(i,self.curves.index(E)) for i,E in enumerate(curves)])
        if verbose:
            print("Sorting permutation = %s" % perm)

        mat = MatrixSpace(ZZ,ncurves)(0)
        self._maps = [[0]*ncurves for i in range(ncurves)]
        for i,j,l,phi in tuples:
            if phi!=0:
                mat[perm[i],perm[j]] = l
                self._maps[perm[i]][perm[j]] = phi
        self._mat = fill_isogeny_matrix(mat)
        if verbose:
            print("Matrix = %s" % self._mat)

        if not E.has_rational_cm():
            self._qfmat = None
            return

        # In the CM case, we will have found some "horizontal"
        # isogenies of composite degree and would like to replace them
        # by isogenies of prime degree, mainly to make the isogeny
        # graph look better.  We also construct a matrix whose entries
        # are not degrees of cyclic isogenies, but rather quadratic
        # forms (in 1 or 2 variables) representing the isogeny
        # degrees.  For this we take a short cut: properly speaking,
        # when `\text{End}(E_1)=\text{End}(E_2)=O`, the set
        # `\text{Hom}(E_1,E_2)` is a rank `1` projective `O`-module,
        # hence has a well-defined ideal class associated to it, and
        # hence (using an identification between the ideal class group
        # and the group of classes of primitive quadratic forms of the
        # same discriminant) an equivalence class of quadratic forms.
        # But we currently only care about the numbers represented by
        # the form, i.e. which genus it is in rather than the exact
        # class.  So it suffices to find one form of the correct
        # discriminant which represents one isogeny degree from `E_1`
        # to `E_2` in order to obtain a form which represents all such
        # degrees.

        if verbose:
            print("Creating degree matrix (CM case)")

        allQs = {} # keys: discriminants d
                   # values: lists of equivalence classes of
                   # primitive forms of discriminant d
        def find_quadratic_form(d,n):
            if not d in allQs:
                from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives

                allQs[d] = BinaryQF_reduced_representatives(d, primitive_only=True)
            # now test which of the Qs represents n
            for Q in allQs[d]:
                if Q.solve_integer(n):
                    return Q
            raise ValueError("No form of discriminant %d represents %s" %(d,n))

        mat = self._mat
        qfmat = [[0 for i in range(ncurves)] for j in range(ncurves)]
        for i, E1 in enumerate(self.curves):
            for j, E2 in enumerate(self.curves):
                if j<i:
                    qfmat[i][j] = qfmat[j][i]
                    mat[i,j] = mat[j,i]
                elif i==j:
                    qfmat[i][j] = [1]
                    # mat[i,j] already 1
                else:
                    d = E1.cm_discriminant()
                    if d != E2.cm_discriminant():
                        qfmat[i][j] = [mat[i,j]]
                        # mat[i,j] already unique
                    else: # horizontal isogeny
                        q = find_quadratic_form(d,mat[i,j])
                        qfmat[i][j] = list(q)
                        mat[i,j] = q.small_prime_value()

        self._mat = mat
        self._qfmat = qfmat
        if verbose:
            print("new matrix = %s" % mat)
            print("matrix of forms = %s" % qfmat)