Example #1
0
    def are_equivalent(self, x, y, trans = False):
        r""" 
        Test whether or not cusps x and y are equivalent modulo self.  If self
        has a reduce_cusp() method, use that; otherwise do a slow explicit
        test. 

        If trans = False, returns True or False. If trans = True, then return
        either False or an element of self mapping x onto y.

        EXAMPLE::

            sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(0), trans=True)
            [  3  -1]
            [-14   5]
            sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(1/7))
            False
        """
        x = Cusp(x)
        y = Cusp(y)
        if not trans:
            try:
                xr = self.reduce_cusp(x)
                yr = self.reduce_cusp(y)
                if xr != yr:
                    return False
                if xr == yr:
                    return True
            except NotImplementedError:
                pass

        from all import SL2Z 
    
        vx = lift_to_sl2z(x.numerator(),x.denominator(), 0)
        dx = SL2Z([vx[2], -vx[0], vx[3], -vx[1]])
        vy = lift_to_sl2z(y.numerator(),y.denominator(), 0)
        dy = SL2Z([vy[2], -vy[0], vy[3], -vy[1]])
    
        for i in xrange(self.index()):
            # Note that the width of any cusp is bounded above by the index of self.
            # If self is congruence, then the level of self is a much better bound, but
            # this method is written to work with non-congruence subgroups as well,
            if dy * SL2Z([1,i,0,1])*(~dx) in self:
                if trans:
                    return dy * SL2Z([1,i,0,1]) * ~dx
                else:
                    return True
            elif (self.is_odd() and dy * SL2Z([-1,-i,0,-1]) * ~dx in self):
                if trans:
                    return dy * SL2Z([-1,-i,0,-1]) * ~dx
                else:
                    return True
        return False
Example #2
0
    def are_equivalent(self, x, y, trans=False):
        r"""
        Test whether or not cusps x and y are equivalent modulo self.  If self
        has a reduce_cusp() method, use that; otherwise do a slow explicit
        test.

        If trans = False, returns True or False. If trans = True, then return
        either False or an element of self mapping x onto y.

        EXAMPLE::

            sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(0), trans=True)
            [  3  -1]
            [-14   5]
            sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(1/7))
            False
        """
        x = Cusp(x)
        y = Cusp(y)
        if not trans:
            try:
                xr = self.reduce_cusp(x)
                yr = self.reduce_cusp(y)
                if xr != yr:
                    return False
                if xr == yr:
                    return True
            except NotImplementedError:
                pass

        from all import SL2Z

        vx = lift_to_sl2z(x.numerator(), x.denominator(), 0)
        dx = SL2Z([vx[2], -vx[0], vx[3], -vx[1]])
        vy = lift_to_sl2z(y.numerator(), y.denominator(), 0)
        dy = SL2Z([vy[2], -vy[0], vy[3], -vy[1]])

        for i in xrange(self.index()):
            # Note that the width of any cusp is bounded above by the index of self.
            # If self is congruence, then the level of self is a much better bound, but
            # this method is written to work with non-congruence subgroups as well,
            if dy * SL2Z([1, i, 0, 1]) * (~dx) in self:
                if trans:
                    return dy * SL2Z([1, i, 0, 1]) * ~dx
                else:
                    return True
            elif (self.is_odd() and dy * SL2Z([-1, -i, 0, -1]) * ~dx in self):
                if trans:
                    return dy * SL2Z([-1, -i, 0, -1]) * ~dx
                else:
                    return True
        return False
Example #3
0
    def coset_reps(self):
        r"""
        Return representatives for the right cosets of this congruence
        subgroup in `{\rm SL}_2(\ZZ)` as a generator object.

        Use ``list(self.coset_reps())`` to obtain coset reps as a
        list.

        EXAMPLES::

            sage: list(Gamma0(5).coset_reps())
            [
            [1 0]  [ 0 -1]  [1 0]  [ 0 -1]  [ 0 -1]  [ 0 -1]
            [0 1], [ 1  0], [1 1], [ 1  2], [ 1  3], [ 1  4]
            ]
            sage: list(Gamma0(4).coset_reps())
            [
            [1 0]  [ 0 -1]  [1 0]  [ 0 -1]  [ 0 -1]  [1 0]
            [0 1], [ 1  0], [1 1], [ 1  2], [ 1  3], [2 1]
            ]
            sage: list(Gamma0(1).coset_reps())
            [
            [1 0]
            [0 1]
            ]
        """
        from .all import SL2Z
        N = self.level()
        if N == 1:  # P1List isn't very happy working modulo 1
            yield SL2Z([1, 0, 0, 1])
        else:
            for z in sage.modular.modsym.p1list.P1List(N):
                yield SL2Z(lift_to_sl2z(z[0], z[1], N))
Example #4
0
    def random_element(self, bound=100, *args, **kwds):
        r"""
        Return a random element of `{\rm SL}_2(\ZZ)` with entries whose
        absolute value is strictly less than bound (default 100).
        Additional arguments and keywords are passed to the random_element
        method of ZZ.

        (Algorithm: Generate a random pair of integers at most bound. If they
        are not coprime, throw them away and start again. If they are, find an
        element of `{\rm SL}_2(\ZZ)` whose bottom row is that, and
        left-multiply it by `\begin{pmatrix} 1 & w \\ 0 & 1\end{pmatrix}` for
        an integer `w` randomly chosen from a small enough range that the
        answer still has entries at most bound.)

        It is, unfortunately, not true that all elements of SL2Z with entries <
        bound appear with equal probability; those with larger bottom rows are
        favoured, because there are fewer valid possibilities for w.

        EXAMPLES::

            sage: SL2Z.random_element()
            [60 13]
            [83 18]
            sage: SL2Z.random_element(5)
            [-1  3]
            [ 1 -4]

        Passes extra positional or keyword arguments through::

            sage: SL2Z.random_element(5, distribution='1/n')
            [ 1 -4]
            [ 0  1]
        """
        if bound <= 1: raise ValueError("bound must be greater than 1")
        c = ZZ.random_element(1-bound, bound, *args, **kwds)
        d = ZZ.random_element(1-bound, bound, *args, **kwds)
        if gcd(c,d) != 1: # try again
            return self.random_element(bound, *args, **kwds)
        else:
            a,b,c,d = lift_to_sl2z(c,d,0)
            whi = bound
            wlo = bound
            if c > 0:
                whi = min(whi, ((bound - a)/ZZ(c)).ceil())
                wlo = min(wlo, ((bound + a)/ZZ(c)).ceil())
            elif c < 0:
                whi = min(whi, ((bound + a)/ZZ(-c)).ceil())
                wlo = min(wlo, ((bound - a)/ZZ(-c)).ceil())

            if d > 0:
                whi = min(whi, ((bound - b)/ZZ(d)).ceil())
                wlo = min(wlo, ((bound + b)/ZZ(d)).ceil())
            elif d < 0:
                whi = min(whi, ((bound + b)/ZZ(-d)).ceil())
                wlo = min(wlo, ((bound - b)/ZZ(-d)).ceil())

            w = ZZ.random_element(1-wlo, whi, *args, **kwds)
            a += c*w
            b += d*w
            return self([a,b,c,d])
Example #5
0
    def coset_reps(self):
        r"""
        Return representatives for the right cosets of this congruence
        subgroup in `{\rm SL}_2(\ZZ)` as a generator object.

        Use ``list(self.coset_reps())`` to obtain coset reps as a
        list.

        EXAMPLES::

            sage: list(Gamma0(5).coset_reps())
            [
            [1 0]  [ 0 -1]  [1 0]  [ 0 -1]  [ 0 -1]  [ 0 -1]
            [0 1], [ 1  0], [1 1], [ 1  2], [ 1  3], [ 1  4]
            ]
            sage: list(Gamma0(4).coset_reps())
            [
            [1 0]  [ 0 -1]  [1 0]  [ 0 -1]  [ 0 -1]  [1 0]
            [0 1], [ 1  0], [1 1], [ 1  2], [ 1  3], [2 1]
            ]
            sage: list(Gamma0(1).coset_reps())
            [
            [1 0]
            [0 1]
            ]
        """
        from all import SL2Z
        N = self.level()
        if N == 1: # P1List isn't very happy working modulo 1
            yield SL2Z([1,0,0,1])
        else:
            for z in sage.modular.modsym.p1list.P1List(N):
                yield SL2Z(lift_to_sl2z(z[0], z[1], N))
Example #6
0
    def gamma0_coset_reps(self):
        r"""
        Return a set of coset representatives for self \\ Gamma0(N), where N is
        the level of self.

        EXAMPLE::

            sage: GammaH(108, [1,-1]).gamma0_coset_reps()
            [
            [1 0]  [-43 -45]  [ 31  33]  [-49 -54]  [ 25  28]  [-19 -22]
            [0 1], [108 113], [108 115], [108 119], [108 121], [108 125],
            <BLANKLINE>
            [-17 -20]  [ 47  57]  [ 13  16]  [ 41  52]  [  7   9]  [-37 -49]
            [108 127], [108 131], [108 133], [108 137], [108 139], [108 143],
            <BLANKLINE>
            [-35 -47]  [ 29  40]  [ -5  -7]  [ 23  33]  [-11 -16]  [ 53  79]
            [108 145], [108 149], [108 151], [108 155], [108 157], [108 161]
            ]
        """
        from all import Gamma0
        N = self.level()
        G = Gamma0(N)
        s = []
        return [
            G(lift_to_sl2z(0, d.lift(), N))
            for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())
        ]
    def cusp_data(self, c):
        r"""
        Return a triple (g, w, t) where g is an element of self generating the
        stabiliser of the given cusp, w is the width of the cusp, and t is 1 if
        the cusp is regular and -1 if not.

        EXAMPLES::

            sage: Gamma1(4).cusp_data(Cusps(1/2))
            (
            [ 1 -1]
            [ 4 -3], 1, -1
            )
        """
        c = Cusp(c)

        # first find an element of SL2Z sending infinity to the given cusp
        w = lift_to_sl2z(c.denominator(), c.numerator(), 0)
        g = SL2Z([w[3], w[1], w[2], w[0]])

        for d in xrange(1, 1 + self.index()):
            if g * SL2Z([1, d, 0, 1]) * (~g) in self:
                return (g * SL2Z([1, d, 0, 1]) * (~g), d, 1)
            elif g * SL2Z([-1, -d, 0, -1]) * (~g) in self:
                return (g * SL2Z([-1, -d, 0, -1]) * (~g), d, -1)
        raise ArithmeticError("Can't get here!")
Example #8
0
    def random_element(self, bound=100, *args, **kwds):
        r"""
        Return a random element of `{\rm SL}_2(\ZZ)` with entries whose
        absolute value is strictly less than bound (default 100).
        Additional arguments and keywords are passed to the random_element
        method of ZZ.

        (Algorithm: Generate a random pair of integers at most bound. If they
        are not coprime, throw them away and start again. If they are, find an
        element of `{\rm SL}_2(\ZZ)` whose bottom row is that, and
        left-multiply it by `\begin{pmatrix} 1 & w \\ 0 & 1\end{pmatrix}` for
        an integer `w` randomly chosen from a small enough range that the
        answer still has entries at most bound.)

        It is, unfortunately, not true that all elements of SL2Z with entries <
        bound appear with equal probability; those with larger bottom rows are
        favoured, because there are fewer valid possibilities for w.

        EXAMPLES::

            sage: SL2Z.random_element()
            [60 13]
            [83 18]
            sage: SL2Z.random_element(5)
            [-1  3]
            [ 1 -4]

        Passes extra positional or keyword arguments through::

            sage: SL2Z.random_element(5, distribution='1/n')
            [ 1 -4]
            [ 0  1]
        """
        if bound <= 1: raise ValueError("bound must be greater than 1")
        c = ZZ.random_element(1 - bound, bound, *args, **kwds)
        d = ZZ.random_element(1 - bound, bound, *args, **kwds)
        if gcd(c, d) != 1:  # try again
            return self.random_element(bound, *args, **kwds)
        else:
            a, b, c, d = lift_to_sl2z(c, d, 0)
            whi = bound
            wlo = bound
            if c > 0:
                whi = min(whi, ((bound - a) / ZZ(c)).ceil())
                wlo = min(wlo, ((bound + a) / ZZ(c)).ceil())
            elif c < 0:
                whi = min(whi, ((bound + a) / ZZ(-c)).ceil())
                wlo = min(wlo, ((bound - a) / ZZ(-c)).ceil())

            if d > 0:
                whi = min(whi, ((bound - b) / ZZ(d)).ceil())
                wlo = min(wlo, ((bound + b) / ZZ(d)).ceil())
            elif d < 0:
                whi = min(whi, ((bound + b) / ZZ(-d)).ceil())
                wlo = min(wlo, ((bound - b) / ZZ(-d)).ceil())

            w = ZZ.random_element(1 - wlo, whi, *args, **kwds)
            a += c * w
            b += d * w
            return self([a, b, c, d])
Example #9
0
    def cusp_data(self, c):
        r"""
        Return a triple (g, w, t) where g is an element of self generating the
        stabiliser of the given cusp, w is the width of the cusp, and t is 1 if
        the cusp is regular and -1 if not.

        EXAMPLES::

            sage: Gamma1(4).cusp_data(Cusps(1/2))
            (
            [ 1 -1]
            [ 4 -3], 1, -1
            )
        """
        c = Cusp(c)

        # first find an element of SL2Z sending infinity to the given cusp
        w = lift_to_sl2z(c.denominator(), c.numerator(), 0)
        g = SL2Z([w[3], w[1], w[2],w[0]])

        for d in xrange(1,1+self.index()):
            if g * SL2Z([1,d,0,1]) * (~g) in self:
                return (g * SL2Z([1,d,0,1]) * (~g), d, 1)
            elif g * SL2Z([-1,-d,0,-1]) * (~g) in self:
                return (g * SL2Z([-1,-d,0,-1]) * (~g), d, -1)
        raise ArithmeticError("Can't get here!")
    def gamma0_coset_reps(self):
        r"""
        Return a set of coset representatives for self \\ Gamma0(N), where N is
        the level of self.

        EXAMPLES::

            sage: GammaH(108, [1,-1]).gamma0_coset_reps()
            [
            [1 0]  [-43  -2]  [ 31   2]  [-49  -5]  [ 25   3]  [-19  -3]
            [0 1], [108   5], [108   7], [108  11], [108  13], [108  17],
            <BLANKLINE>
            [-17  -3]  [ 47  10]  [ 13   3]  [ 41  11]  [  7   2]  [-37 -12]
            [108  19], [108  23], [108  25], [108  29], [108  31], [108  35],
            <BLANKLINE>
            [-35 -12]  [ 29  11]  [ -5  -2]  [ 23  10]  [-11  -5]  [ 53  26]
            [108  37], [108  41], [108  43], [108  47], [108  49], [108  53]
            ]
        """
        from .all import SL2Z
        N = self.level()
        return [
            SL2Z(lift_to_sl2z(0, d.lift(), N))
            for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())
        ]
Example #11
0
def lift_to_gamma1(g, m, n):
    r"""
    If ``g = [a,b,c,d]`` is a list of integers defining a `2 \times 2` matrix
    whose determinant is `1 \pmod m`, return a list of integers giving the
    entries of a matrix which is congruent to `g \pmod m` and to
    `\begin{pmatrix} 1 & * \\ 0 & 1 \end{pmatrix} \pmod n`. Here `m` and `n`
    must be coprime.

    Here `m` and `n` should be coprime positive integers. Either of `m` and `n`
    can be `1`. If `n = 1`, this still makes perfect sense; this is what is
    called by the function :func:`~lift_matrix_to_sl2z`. If `m = 1` this is a
    rather silly question, so we adopt the convention of always returning the
    identity matrix.

    The result is always a list of Sage integers (unlike ``lift_to_sl2z``,
    which tends to return Python ints).

    EXAMPLE::

        sage: from sage.modular.local_comp.liftings import lift_to_gamma1
        sage: A = matrix(ZZ, 2, lift_to_gamma1([10, 11, 3, 11], 19, 5)); A
        [371  68]
        [ 60  11]
        sage: A.det() == 1
        True
        sage: A.change_ring(Zmod(19))
        [10 11]
        [ 3 11]
        sage: A.change_ring(Zmod(5))
        [1 3]
        [0 1]
        sage: m = list(SL2Z.random_element())
        sage: n = lift_to_gamma1(m, 11, 17)
        sage: assert matrix(Zmod(11), 2, n) == matrix(Zmod(11),2,m)
        sage: assert matrix(Zmod(17), 2, [n[0], 0, n[2], n[3]]) == 1
        sage: type(lift_to_gamma1([10,11,3,11],19,5)[0])
        <type 'sage.rings.integer.Integer'>

    Tests with `m = 1` and with `n = 1`::

        sage: lift_to_gamma1([1,1,0,1], 5, 1)
        [1, 1, 0, 1]
        sage: lift_to_gamma1([2,3,11,22], 1, 5)
        [1, 0, 0, 1]
    """
    if m == 1:
        return [ZZ(1), ZZ(0), ZZ(0), ZZ(1)]
    a, b, c, d = [ZZ(x) for x in g]
    if not (a * d - b * c) % m == 1:
        raise ValueError("Determinant is {0} mod {1}, should be 1".format(
            (a * d - b * c) % m, m))
    c2 = crt(c, 0, m, n)
    d2 = crt(d, 1, m, n)
    a3, b3, c3, d3 = [ZZ(_) for _ in lift_to_sl2z(c2, d2, m * n)]
    r = (a3 * b - b3 * a) % m
    return [a3 + r * c3, b3 + r * d3, c3, d3]
Example #12
0
def lift_to_gamma1(g, m, n):
    r"""
    If ``g = [a,b,c,d]`` is a list of integers defining a `2 \times 2` matrix
    whose determinant is `1 \pmod m`, return a list of integers giving the
    entries of a matrix which is congruent to `g \pmod m` and to
    `\begin{pmatrix} 1 & * \\ 0 & 1 \end{pmatrix} \pmod n`. Here `m` and `n`
    must be coprime.

    Here `m` and `n` should be coprime positive integers. Either of `m` and `n`
    can be `1`. If `n = 1`, this still makes perfect sense; this is what is
    called by the function :func:`~lift_matrix_to_sl2z`. If `m = 1` this is a
    rather silly question, so we adopt the convention of always returning the
    identity matrix.

    The result is always a list of Sage integers (unlike ``lift_to_sl2z``,
    which tends to return Python ints).

    EXAMPLE::

        sage: from sage.modular.local_comp.liftings import lift_to_gamma1
        sage: A = matrix(ZZ, 2, lift_to_gamma1([10, 11, 3, 11], 19, 5)); A
        [371  68]
        [ 60  11]
        sage: A.det() == 1
        True
        sage: A.change_ring(Zmod(19))
        [10 11]
        [ 3 11]
        sage: A.change_ring(Zmod(5))
        [1 3]
        [0 1]
        sage: m = list(SL2Z.random_element())
        sage: n = lift_to_gamma1(m, 11, 17)
        sage: assert matrix(Zmod(11), 2, n) == matrix(Zmod(11),2,m)
        sage: assert matrix(Zmod(17), 2, [n[0], 0, n[2], n[3]]) == 1
        sage: type(lift_to_gamma1([10,11,3,11],19,5)[0])
        <type 'sage.rings.integer.Integer'>

    Tests with `m = 1` and with `n = 1`::

        sage: lift_to_gamma1([1,1,0,1], 5, 1)
        [1, 1, 0, 1]
        sage: lift_to_gamma1([2,3,11,22], 1, 5)
        [1, 0, 0, 1]
    """
    if m == 1:
        return [ZZ(1),ZZ(0),ZZ(0),ZZ(1)]
    a,b,c,d = [ZZ(x) for x in g]
    if not (a*d - b*c) % m == 1:
        raise ValueError( "Determinant is {0} mod {1}, should be 1".format((a*d - b*c) % m, m) )
    c2 = crt(c, 0, m, n)
    d2 = crt(d, 1, m, n)
    a3,b3,c3,d3 = map(ZZ, lift_to_sl2z(c2,d2,m*n))
    r = (a3*b - b3*a) % m
    return [a3 + r*c3, b3 + r*d3, c3, d3]
Example #13
0
def galois_action(self, t, N):
    from sage.modular.modsym.p1list import lift_to_sl2z
    if self.is_infinity():
        return self
    if not isinstance(t, Integer): 
        t = Integer(t)

    a = self._Cusp__a
    b = self._Cusp__b * t.inverse_mod(N)
    if b.gcd(a) != ZZ(1):
        _,_,a,b = lift_to_sl2z(a,b,N)
        a = Integer(a); b = Integer(b)

    # Now that we've computed the Galois action, we efficiently
    # construct the corresponding cusp as a Cusp object.
    return Cusp(a,b,check=False)
Example #14
0
def galois_action(self, t, N):
    from sage.modular.modsym.p1list import lift_to_sl2z
    if self.is_infinity():
        return self
    if not isinstance(t, Integer): 
        t = Integer(t)

    a = self._Cusp__a
    b = self._Cusp__b * t.inverse_mod(N)
    if b.gcd(a) != ZZ(1):
        _,_,a,b = lift_to_sl2z(a,b,N)
        a = Integer(a); b = Integer(b)

    # Now that we've computed the Galois action, we efficiently
    # construct the corresponding cusp as a Cusp object.
    return Cusp(a,b,check=False)
Example #15
0
    def gamma0_coset_reps(self):
        r"""
        Return a set of coset representatives for self \\ Gamma0(N), where N is
        the level of self.

        EXAMPLE::

            sage: GammaH(108, [1,-1]).gamma0_coset_reps()
            [[1 0] [0 1], [-43 -45] [108 113], [ 31  33] [108 115], [-49 -54]
            [108 119], [ 25  28] [108 121], [-19 -22] [108 125], [-17 -20] [108
            127], [ 47  57] [108 131], [ 13  16] [108 133], [ 41  52] [108
            137], [  7   9] [108 139], [-37 -49] [108 143], [-35 -47] [108
            145], [ 29  40] [108 149], [ -5  -7] [108 151], [ 23  33] [108
            155], [-11 -16] [108 157], [ 53  79] [108 161]]
        """
        from all import Gamma0
        N = self.level()
        G = Gamma0(N)
        s = []
        return [G(lift_to_sl2z(0, d.lift(), N)) for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())]
Example #16
0
    def cusp_reduction_table(self):
        r'''
        Returns a dictionary and the set of cusps.

        Assumes we have a finite set surjecting to the cusps (namely, P^1(O_F/N)). Runs through
        and computes a subset which represents the cusps, and shows how to go from any element 
        of P^1(O_F/N) to the chosen equivalent cusp.

        Takes as input the object representing P^1(O_F/N), where F is a number field
        (that is possibly Q), and N is some ideal in the field.  Runs the following algorithm:
                - take a remaining element C = (c:d) of P^1(O_F/N);
                - add this to the set of cusps, declaring it to be our chosen rep;
                - run through every translate C' = (c':d') of C under the stabiliser of infinity, and
                        remove this translate from the set of remaining elements;
                - store the matrix T in the stabiliser such that C' * T = C (as elements in P^1)
                        in the dictionary, with key C'.
        '''
        P = self.get_P1List()
        if hasattr(P.N(),'number_field'):
            K = P.N().number_field()
        else:
            K = QQ

        # from sage.modular.modsym.p1list_nf import lift_to_sl2_Ok
        from .my_p1list_nf import lift_to_sl2_Ok
        from sage.modular.modsym.p1list import lift_to_sl2z
        ## Define new function on the fly to pick which of Q/more general field we work in
        ## lift_to_matrix takes parameters c,d, then lifts (c:d) to a 2X2 matrix over the NF representing it
        lift_to_matrix = lambda c, d: lift_to_sl2z(c,d,P.N()) if K.degree() == 1 else lift_to_sl2_Ok(P.N(), c, d)

        ## Put all the points of P^1(O_F/N) into a list; these will corr. to our dictionary keys
        remaining_points = set(list(P)) if K == QQ else set([c.tuple() for c in P])
        reduction_table = {}
        cusp_set = []

        initial_points = len(remaining_points)

        ## Loop over all points of P^1(O_F/N)
        while len(remaining_points) > 0:
            ## Pick a new cusp representative
            c = remaining_points.pop()
            update_progress(1 - float(len(remaining_points)) / float(initial_points), "Finding cusps...")
            ## c is an MSymbol so not hashable. Create tuple that is
            ## Represent the cusp as a matrix, add to list of cusps, and add to dictionary
            new_cusp = Matrix(2,2,lift_to_matrix(c[0], c[1])) 
            new_cusp.set_immutable()
            cusp_set.append(new_cusp)
            reduction_table[c]=(new_cusp,matrix(2,2,1)) ## Set the value to I_2
            ## Now run over the whole orbit of this point under the stabiliser at infinity.
            ## For each elt of the orbit, explain how to reduce to the chosen cusp.

            ## Run over lifts of elements of O_F/N:
            if K == QQ:
                residues = Zmod(P.N())
                units = [1, -1]
            else:
                residues = P.N().residues()
                units = K.roots_of_unity()

            for hh in residues:
                h = K(hh) ## put into the number field
                ## Run over all finite order units in the number field
                for u in units:
                    ## Now have the matrix (u,h; 0,u^-1).
                    ## Compute the action of this matrix on c
                    new_c = P.normalize(u * c[0], u**-1 * c[1] + h * c[0])
                    if K != QQ: 
                        new_c = new_c.tuple()
                    if new_c not in reduction_table:
                        ## We've not seen this point before! But it's equivalent to c, so kill it!
                        ## (and also store the matrix we used to get to it)
                        remaining_points.remove(new_c)
                        T = matrix(2,2,[u,h,0,u**-1]) ## we used this matrix to get from c to new_c
                        reduction_table[new_c]=(new_cusp, T) ## update dictionary with the new_c + the matrix
                        if K != QQ:
                            assert P.normalize(*(vector(c) * T)).tuple() == new_c ## sanity check
                        else:
                            assert P.normalize(*(vector(c) * T)) == new_c ## sanity check

        return reduction_table, cusp_set