# HG changeset patch
# User David Loeffler <d.loeffler.01@cantab.net>
# Date 1331057340 0
# Node ID 02300303e9243260c6e638effe87e2eb20a8c5d7
# Parent  58790db46f55ee4d8d78096f465bc7b76079ee3a
#11709: Farey symbol reviewer changes

diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -53,6 +53,7 @@
 sage/rings/complex_double_api.h
 sage/misc/allocator.h
 sage/symbolic/pynac.h
+sage/modular/arithgroup/farey_symbol.h
 doc/output/
 doc/en/reference/sage/
 doc/en/reference/sagenb/
diff --git a/doc/en/bordeaux_2008/modular_forms_and_hecke_operators.rst b/doc/en/bordeaux_2008/modular_forms_and_hecke_operators.rst
--- a/doc/en/bordeaux_2008/modular_forms_and_hecke_operators.rst
+++ b/doc/en/bordeaux_2008/modular_forms_and_hecke_operators.rst
@@ -55,10 +55,24 @@
 enumerate) to obtain a list of generators [[ref my modular forms
 book]].
 
+The list of generators Sage computes is unfortunately large. Improving this
+would be an excellent Sage development project, which would involve much
+beautiful mathematics.
+
+UPDATE (March 2012): The project referred to above has been carried out (by
+several people, notably Hartmut Monien, building on earlier work of Chris
+Kurth). Sage now uses a much more advanced algorithm based on Farey symbols
+which calculates a *minimal* set of generators.
+
 ::
 
     sage: Gamma0(2).gens()
     ([1 1]
+    [0 1],
+    [ 1 -1]
+    [ 2 -1])
+    sage: Gamma0(2).gens(algorithm="todd-coxeter") # the old implementation
+    ([1 1]
      [0 1], 
      [-1  0]
      [ 0 -1], 
@@ -69,12 +83,8 @@
      [-1  1]
      [-2  1])
     sage: len(Gamma1(13).gens())
-    260
+    15
 
-As you can see above, the list of generators Sage computes is
-unfortunately large. Improving this would be an excellent Sage
-development project, which would involve much beautiful
-mathematics.
 
 Modular Forms
 -------------
diff --git a/sage/modular/arithgroup/arithgroup_element.py b/sage/modular/arithgroup/arithgroup_element.py
--- a/sage/modular/arithgroup/arithgroup_element.py
+++ b/sage/modular/arithgroup/arithgroup_element.py
@@ -133,7 +133,7 @@
 
         This once caused a segfault (see trac #5443)::
 
-            sage: r,s,t,u,v = Gamma0(2).gens()
+            sage: r,s,t,u,v = map(SL2Z, [[1, 1, 0, 1], [-1, 0, 0, -1], [1, -1, 0, 1], [1, -1, 2, -1], [-1, 1, -2, 1]])
             sage: v == s*u
             True
             sage: s*u == v
diff --git a/sage/modular/arithgroup/arithgroup_generic.py b/sage/modular/arithgroup/arithgroup_generic.py
--- a/sage/modular/arithgroup/arithgroup_generic.py
+++ b/sage/modular/arithgroup/arithgroup_generic.py
@@ -853,20 +853,48 @@
 
         return ZZ(1 + (self.projective_index()) / ZZ(12)  - (self.nu2())/ZZ(4) - (self.nu3())/ZZ(3) - self.ncusps()/ZZ(2))
 
-    def generators(self):
+    def farey_symbol(self):
         r"""
-        Return generators for this congruence subgroup.
-        
-        This is carried out using an "inverse Todd-Coxeter" algorithm. 
-        A Cython version of this for the special case of `\Gamma_0` and `\Gamma_1` is
-        implemented in the function ``congroup_pyx.generators_helper``. See the documentation
-        there for further details. Here we are assuming far less about the
-        group, so the computation is exceptionally slow!
+        Return the Farey symbol associated to this subgroup. See the
+        :mod:`~sage.modular.arithgroup.farey_symbol` module for more
+        information.
 
         EXAMPLE::
 
+            sage: Gamma1(4).farey_symbol()
+            FareySymbol(Congruence Subgroup Gamma1(4))
+        """
+        from farey_symbol import Farey
+        return Farey(self)
+
+    @cached_method
+    def generators(self, algorithm="farey"):
+        r"""
+        Return a list of generators for this congruence subgroup. The result is cached.
+
+        INPUT:
+
+        - ``algorithm`` (string): either ``farey`` or ``todd-coxeter``.
+
+        If ``algorithm`` is set to ``"farey"``, then the generators will be
+        calculated using Farey symbols, which will always return a *minimal*
+        generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
+        more information.
+
+        If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
+        based on Todd-Coxeter enumeration will be used. This is *exceedingly*
+        slow for general subgroups, and the list of generators will be far from
+        minimal (indeed it may contain repetitions).
+        
+        EXAMPLE::
+
             sage: Gamma(2).generators()
             [[1 2]
+            [0 1], [ 3 -2]
+            [ 2 -1], [-1  0]
+            [ 0 -1]]
+            sage: Gamma(2).generators(algorithm="todd-coxeter")
+            [[1 2]
             [0 1], [-1  0]
             [ 0 -1], [ 1  0]
             [-2  1], [-1  0]
@@ -875,13 +903,18 @@
             [ 2 -1], [1 0]
             [2 1]]
         """
-        return self.todd_coxeter()[1]
+        if algorithm=="farey":
+            return self.farey_symbol().generators()
+        elif algorithm == "todd-coxeter":
+            return self.todd_coxeter()[1]
+        else:
+            raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
 
-    def gens(self):
+    def gens(self, *args, **kwds):
         r"""
         Return a tuple of generators for this congruence subgroup.
 
-        The generators need not be minimal.
+        The generators need not be minimal. For arguments, see :meth:`~generators`.
 
         EXAMPLES::
 
@@ -890,7 +923,7 @@
             [ 1  0], [1 1]
             [0 1])
         """
-        return tuple(self.generators())
+        return tuple(self.generators(*args, **kwds))
 
     def gen(self, i):
         r"""
@@ -907,18 +940,17 @@
 
     def ngens(self):
         r"""
-        Return the number of generators for this arithmetic subgroup.
-
-        This need not be the minimal number of generators of self.
+        Return the size of the minimal generating set of self returned by
+        :meth:`generators`.
 
         EXAMPLES::
 
             sage: Gamma0(22).ngens()
-            42
+            8
             sage: Gamma1(14).ngens()
-            219
+            13
             sage: GammaH(11, [3]).ngens()
-            31
+            3
             sage: SL2Z.ngens()
             2
         """
@@ -1129,9 +1161,9 @@
         Return self as an arithmetic subgroup defined in terms of the
         permutation action of `SL(2,\ZZ)` on its right cosets.
 
-        This method uses Todd-coxeter enumeration (via the method todd_coxeter) which
-        can be extremly slow for arithmetic subgroup with relatively large index in
-        `SL(2,\ZZ)`.
+        This method uses Todd-coxeter enumeration (via the method
+        :meth:`~todd_coxeter`) which can be extremely slow for arithmetic
+        subgroups with relatively large index in `SL(2,\ZZ)`.
 
         EXAMPLES::
 
diff --git a/sage/modular/arithgroup/congroup_gamma0.py b/sage/modular/arithgroup/congroup_gamma0.py
--- a/sage/modular/arithgroup/congroup_gamma0.py
+++ b/sage/modular/arithgroup/congroup_gamma0.py
@@ -375,36 +375,60 @@
                 yield SL2Z(lift_to_sl2z(z[0], z[1], N))
 
     @cached_method
-    def generators(self):
+    def generators(self, algorithm="farey"):
         r"""
         Return generators for this congruence subgroup.
 
-        The result is cached.
+        INPUT:
+
+        - ``algorithm`` (string): either ``farey`` (default) or
+          ``todd-coxeter``.
+
+        If ``algorithm`` is set to ``"farey"``, then the generators will be
+        calculated using Farey symbols, which will always return a *minimal*
+        generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
+        more information.
+
+        If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
+        based on Todd-Coxeter enumeration will be used. This tends to return
+        far larger sets of generators.
 
         EXAMPLE::
 
-            sage: for g in Gamma0(3).generators():
-            ...     print g
-            ...     print '---'
-            [1 1]
-            [0 1]
-            ---
-            [-1  0]
-            [ 0 -1]
-            ---
-            ...
-            ---
-            [ 1  0]
-            [-3  1]
-            ---
+            sage: [x.matrix() for x in Gamma0(3).generators()]
+            [
+            [1 1]  [-1  1]
+            [0 1], [-3  2]
+            ]
+            sage: [x.matrix() for x in Gamma0(3).generators(algorithm="todd-coxeter")]
+            [
+            [1 1]  [-1  0]  [ 1 -1]  [1 0]  [1 1]  [-1  0]  [ 1  0]
+            [0 1], [ 0 -1], [ 0  1], [3 1], [0 1], [ 3 -1], [-3  1]
+            ]
+            sage: SL2Z.gens()
+            ([ 0 -1]
+            [ 1  0], [1 1]
+            [0 1])
         """
-        from sage.modular.modsym.p1list import P1List
-        from congroup_pyx import generators_helper
-        level = self.level()
-        if level == 1: # P1List isn't very happy working mod 1
+        if self.level() == 1:
+            # we return a fixed set of generators for SL2Z, for historical
+            # reasons, which aren't the ones the Farey symbol code gives
             return [ self([0,-1,1,0]), self([1,1,0,1]) ]
-        gen_list = generators_helper(P1List(level), level, Mat2Z)
-        return [self(g, check=False) for g in gen_list]
+
+        elif algorithm=="farey":
+            return self.farey_symbol().generators()
+
+        elif algorithm=="todd-coxeter":
+            from sage.modular.modsym.p1list import P1List
+            from congroup_pyx import generators_helper
+            level = self.level()
+            if level == 1: # P1List isn't very happy working mod 1
+                return [ self([0,-1,1,0]), self([1,1,0,1]) ]
+            gen_list = generators_helper(P1List(level), level, Mat2Z)
+            return [self(g, check=False) for g in gen_list]
+
+        else:
+            raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
 
     def gamma_h_subgroups(self):
         r"""
diff --git a/sage/modular/arithgroup/congroup_gamma1.py b/sage/modular/arithgroup/congroup_gamma1.py
--- a/sage/modular/arithgroup/congroup_gamma1.py
+++ b/sage/modular/arithgroup/congroup_gamma1.py
@@ -237,41 +237,49 @@
             raise NotImplementedError
         
     @cached_method
-    def generators(self):
+    def generators(self, algorithm="farey"):
         r"""
-        Return generators for this congruence subgroup.
+        Return generators for this congruence subgroup. The result is cached.
+ 
+        INPUT:
 
-        The result is cached.
+        - ``algorithm`` (string): either ``farey`` (default) or
+          ``todd-coxeter``.
 
+        If ``algorithm`` is set to ``"farey"``, then the generators will be
+        calculated using Farey symbols, which will always return a *minimal*
+        generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
+        more information.
+
+        If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
+        based on Todd-Coxeter enumeration will be used. This tends to return
+        far larger sets of generators.
+     
         EXAMPLE::
 
-            sage: for g in Gamma1(3).generators():
-            ...     print g
-            ...     print '---'
-            [1 1]
-            [0 1]
-            ---
-            [-20   9]
-            [ 51 -23]
-            ---
-            [ 4  1]
-            [-9 -2]
-            ---
-            ...
-            ---
-            [ 4 -1]
-            [ 9 -2]
-            ---
-            [ -5   3]
-            [-12   7]
-            ---
-
+            sage: [x.matrix() for x in Gamma1(3).generators()]
+            [
+            [1 1]  [ 1 -1]
+            [0 1], [ 3 -2]
+            ]
+            sage: [x.matrix() for x in Gamma1(3).generators(algorithm="todd-coxeter")]
+            [
+            [1 1]  [-20   9]  [ 4  1]  [-5 -2]  [ 1 -1]  [1 0]  [1 1]  [-5  2]
+            [0 1], [ 51 -23], [-9 -2], [ 3  1], [ 0  1], [3 1], [0 1], [12 -5],
+            [ 1  0]  [ 4 -1]  [ -5   3]  [ 1 -1]  [ 7 -3]  [ 4 -1]  [ -5   3]
+            [-3  1], [ 9 -2], [-12   7], [ 3 -2], [12 -5], [ 9 -2], [-12   7]
+            ]
         """
-        from sage.modular.modsym.g1list import G1list
-        from congroup_pyx import generators_helper
-        level = self.level()
-        gen_list = generators_helper(G1list(level), level, Mat2Z)
-        return [self(g, check=False) for g in gen_list]
+        if algorithm=="farey":
+            return self.farey_symbol().generators()
+        elif algorithm=="todd-coxeter":
+            from sage.modular.modsym.g1list import G1list
+            from congroup_pyx import generators_helper
+            level = self.level()
+            gen_list = generators_helper(G1list(level), level, Mat2Z)
+            return [self(g, check=False) for g in gen_list]
+        else:
+            raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
 
     def __call__(self, x, check=True):
         r"""
diff --git a/sage/modular/arithgroup/congroup_gammaH.py b/sage/modular/arithgroup/congroup_gammaH.py
--- a/sage/modular/arithgroup/congroup_gammaH.py
+++ b/sage/modular/arithgroup/congroup_gammaH.py
@@ -408,44 +408,53 @@
         return int(self.level() - 1) in v
 
     @cached_method
-    def generators(self):
+    def generators(self, algorithm="farey"):
         r"""
-        Return generators for this congruence subgroup.
+        Return generators for this congruence subgroup. The result is cached.
+ 
+        INPUT:
 
-        The result is cached.
+        - ``algorithm`` (string): either ``farey`` (default) or
+          ``todd-coxeter``.
+
+        If ``algorithm`` is set to ``"farey"``, then the generators will be
+        calculated using Farey symbols, which will always return a *minimal*
+        generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
+        more information.
+
+        If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
+        based on Todd-Coxeter enumeration will be used. This tends to return
+        far larger sets of generators.
 
         EXAMPLE::
 
-            sage: for g in GammaH(3, [2]).generators():
-            ...     print g
-            ...     print '---'
-            [1 1]
-            [0 1]
-             ---
-            [-1  0]
-            [ 0 -1]
-            ---
-            [ 1 -1]
-            [ 0  1]
-            ---
-            [1 0]
-            [3 1]
-            ---
-            [1 1]
-            [0 1]
-            ---
-            [-1  0]
-            [ 3 -1]
-            ---
-            [ 1  0]
-            [-3  1]
-            ---
+            sage: [x.matrix() for x in GammaH(7, [2]).generators()]
+            [
+            [1 1]  [ 2 -1]  [ 4 -3]
+            [0 1], [ 7 -3], [ 7 -5]
+            ]
+            sage: [x.matrix() for x in GammaH(7, [2]).generators(algorithm="todd-coxeter")]
+            [
+            [1 1]  [-90  29]  [ 15   4]  [-10  -3]  [ 1 -1]  [1 0]  [1 1]  [-3 -1]
+            [0 1], [301 -97], [-49 -13], [  7   2], [ 0  1], [7 1], [0 1], [ 7  2],
+            [-13   4]  [-5 -1]  [-5 -2]  [-10   3]  [ 1  0]  [ 9 -1]  [-20   7]
+            [ 42 -13], [21  4], [28 11], [ 63 -19], [-7  1], [28 -3], [-63  22],
+            [1 0]  [-3 -1]  [ 15  -4]  [ 2 -1]  [ 22  -7]  [-5  1]  [  8  -3]
+            [7 1], [ 7  2], [ 49 -13], [ 7 -3], [ 63 -20], [14 -3], [-21   8],
+            [11  5]  [-13  -4]
+            [35 16], [-42 -13]
+            ]
         """
-        from sage.modular.modsym.ghlist import GHlist
-        from congroup_pyx import generators_helper
-        level = self.level()
-        gen_list = generators_helper(GHlist(self), level, Mat2Z)
-        return [self(g, check=False) for g in gen_list]
+        if algorithm=="farey":
+            return self.farey_symbol().generators()
+        elif algorithm=="todd-coxeter":
+            from sage.modular.modsym.ghlist import GHlist
+            from congroup_pyx import generators_helper
+            level = self.level()
+            gen_list = generators_helper(GHlist(self), level, Mat2Z)
+            return [self(g, check=False) for g in gen_list]
+        else:
+            raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
     
     def _coset_reduction_data_first_coord(G):
         """
diff --git a/sage/modular/arithgroup/congroup_pyx.pyx b/sage/modular/arithgroup/congroup_pyx.pyx
--- a/sage/modular/arithgroup/congroup_pyx.pyx
+++ b/sage/modular/arithgroup/congroup_pyx.pyx
@@ -283,7 +283,7 @@
 
     EXAMPLES::
 
-        sage: Gamma0(7).generators() # indirect doctest
+        sage: Gamma0(7).generators(algorithm="todd-coxeter") # indirect doctest
         [[1 1]
         [0 1], [-1  0]
         [ 0 -1], [ 1 -1]
diff --git a/sage/modular/arithgroup/farey.cpp b/sage/modular/arithgroup/farey.cpp
--- a/sage/modular/arithgroup/farey.cpp
+++ b/sage/modular/arithgroup/farey.cpp
@@ -248,9 +248,13 @@
       a.push_back(0);
       b.push_back(1);
       for(size_t i=0; i<a.size(); i++) x.push_back(a[i]/b[i]);
-      generators.push_back(SL2Z(1,  2,  0,  1));
-      generators.push_back(SL2Z(0, -1,  1, -1));
-      generators.push_back(SL2Z(1, -1,  1,  0));
+      if ( group->is_member(SL2Z(0, -1, 1, 1)) )
+        // index 2 even subgroup
+        generators.push_back(SL2Z(0, -1, 1, 1));
+      else
+        // index 4 odd subgroup
+        generators.push_back(SL2Z(0, 1, -1, -1));
+        generators.push_back(SL2Z(-1, 1, -1, 0));
       coset.push_back(SL2Z::E);
       coset.push_back(SL2Z::T);
       cusp_classes.push_back(1);
@@ -446,11 +450,12 @@
     if( find(p.begin(), p.end(), pairing[i]) == p.end() ) {
       SL2Z m = pairing_matrix(i);
       if( not group->is_member(m) ) m = I*m;
+      if( pairing[i] == ODD and group->is_member(I) ) m = I*m;
       gen.push_back(m);
       if( pairing[i] > NO ) p.push_back(pairing[i]);
     }
   }
-  if( nu2() == 0 and group->is_member(I) ) gen.push_back(I);
+  if( nu2() == 0 and nu3() == 0 and group->is_member(I) ) gen.push_back(I);
   return gen;
 }
 
diff --git a/sage/modular/arithgroup/farey_symbol.pyx b/sage/modular/arithgroup/farey_symbol.pyx
--- a/sage/modular/arithgroup/farey_symbol.pyx
+++ b/sage/modular/arithgroup/farey_symbol.pyx
@@ -104,11 +104,10 @@
     index 10 arithmetic subgroup given by Tim Hsu::
     
          sage: from sage.modular.arithgroup.arithgroup_perm import HsuExample10
-
          sage: [g.matrix() for g in FareySymbol(HsuExample10()).generators()]
          [
-         [1 2]  [ 2 -1]  [ 4 -3]  [-1  0]
-         [0 1], [ 7 -3], [ 3 -2], [ 0 -1]
+         [1 2]  [-2  1]  [ 4 -3]
+         [0 1], [-7  3], [ 3 -2]
          ]
          
     Calculate the generators of the group `\Gamma' =
@@ -166,21 +165,31 @@
         ## of the group is called
         cdef int p
         if hasattr(group, "level"): p=group.level()
-        sig_on()
         if group == SL2Z:
+            sig_on()
             self.this_ptr = new cpp_farey()
+            sig_off()
         elif is_Gamma0(group):
+            sig_on()
             self.this_ptr = new cpp_farey(group, new is_element_Gamma0(p))
+            sig_off()
         elif is_Gamma1(group):
+            sig_on()
             self.this_ptr = new cpp_farey(group, new is_element_Gamma1(p))
+            sig_off()
         elif is_Gamma(group):
+            sig_on()
             self.this_ptr = new cpp_farey(group, new is_element_Gamma(p))
+            sig_off()
         elif is_GammaH(group):
+            sig_on()
             l = group._GammaH_class__H
             self.this_ptr = new cpp_farey(group, new is_element_GammaH(p, l))
+            sig_off()
         else:
+            sig_on()
             self.this_ptr = new cpp_farey(group)
-        sig_off()
+            sig_off()
 
     def __deallocpp__(self):
         r"""
@@ -351,6 +360,18 @@
             [ 1  0], [ 1 -1]
             ]
 
+        The unique index 2 even subgroup and index 4 odd subgroup each get handled correctly::
+
+            sage: [g.matrix() for g in FareySymbol(ArithmeticSubgroup_Permutation(S2="(1,2)", S3="()")).generators()]
+            [
+            [ 0 -1]  [-1  1]
+            [ 1  1], [-1  0]
+            ]
+            sage: [g.matrix() for g in FareySymbol(ArithmeticSubgroup_Permutation(S2="(1,2, 3, 4)", S3="(1,3)(2,4)")).generators()]
+            [
+            [ 0  1]  [-1  1]
+            [-1 -1], [-1  0]
+            ]
         """
         return self.this_ptr.get_generators()
 
diff --git a/sage/schemes/elliptic_curves/ell_rational_field.py b/sage/schemes/elliptic_curves/ell_rational_field.py
--- a/sage/schemes/elliptic_curves/ell_rational_field.py
+++ b/sage/schemes/elliptic_curves/ell_rational_field.py
@@ -3215,9 +3215,6 @@
         This map is actually a map on `X_0(N)`, so equivalent representatives
         in the upper half plane map to the same point:: 
             
-            sage: Gamma0(15).gen(5)
-            [-7 -1]
-            [15  2]
             sage: phi((-7*z-1)/(15*z+2))
             (8.20822465478524 - 13.1562816054681*I : -8.79855099049... + 69.4006129342...*I : 1.00000000000000)
         
