# HG changeset patch
# User David Loeffler <d.loeffler.01@cantab.net>
# Date 1310828377 -3600
# Node ID f2839cfc46bf2c42a7675f28b439b61308230c34
# Parent  b2fc07dff0524f8f812da73ec5ac7b4df7473529
#11598: congruence testing for odd arithmetic subgroups; enumeration of lifts
diff -r b2fc07dff052 -r f2839cfc46bf sage/modular/arithgroup/arithgroup_perm.py
--- a/sage/modular/arithgroup/arithgroup_perm.py	Fri Jul 15 16:06:24 2011 +0100
+++ b/sage/modular/arithgroup/arithgroup_perm.py	Sat Jul 16 15:59:37 2011 +0100
@@ -1,12 +1,13 @@
 r"""
-Arithmetic subgroups defined by permutation action of generators on cosets.
+Arithmetic subgroups defined by permutations of cosets
 
 A subgroup of finite index `H` of a finitely generated group `G` is completely
-described by the action of the generators on the right cosets `H\\G = \{Hg\}`.
-After some arbitrary choice of numbering one can identify the action of
-generators as elements of a symmetric group acting transitively (and satisfying
-the relations of the relators in G). As `{\rm SL}_2(\ZZ)` is a central extension of a
-free product of cyclic group one can easily design algorithms from this point of
+described by the action of the generators of `G` on the right cosets `H
+\backslash G = \{Hg\}_{g \in G}`. After some arbitrary choice of numbering one
+can identify the action of generators as elements of a symmetric group acting
+transitively (and satisfying the relations of the relators in G). As `{\rm
+SL}_2(\ZZ)` has a very simple presentation as a central extension of a free
+product of cyclic groups, one can easily design algorithms from this point of
 view.
 
 The generators of `{\rm SL}_2(\ZZ)` used in this module are named as follows `s_2`,
@@ -23,17 +24,19 @@
 
 .. MATH::
 
-    s_2^2 = s_3^3 = -1 \quad r = s_2^{-1}\ l^{-1}\ s_2.
-
-In particular not all four are needed to generate the whole `{\rm SL}_2(\ZZ)`. Three
-couples which which generate `{\rm SL}_2(\ZZ)` are of particular interest:
+    s_2^2 = s_3^3 = -1, \quad r = s_2^{-1}\ l^{-1}\ s_2.
+
+In particular not all four are needed to generate the whole group `{\rm
+SL}_2(\ZZ)`. Three couples which which generate `{\rm SL}_2(\ZZ)` are of
+particular interest:
 
 - `(l,r)` as the pair is involved in the continued fraction algorithm,
 - `(l,s_2)` similar as the one above because of the relations,
-- `(s_2,s_3)` as the pair generates freely `{\rm PSL}_2(\ZZ)`.
-
-Part of these functions are based on Chris Kurth's *KFarey* package [Kur08]. For
-tests see the file sage.modular.arithgroup.tests
+- `(s_2,s_3)` as the group `{\rm PSL}_2(\ZZ)` is the free product of the finite
+  cyclic groups generated by these two elements.
+
+Part of these functions are based on Chris Kurth's *KFarey* package [Kur08]_.
+For tests see the file :mod:`sage.modular.arithgroup.tests`.
 
 REFERENCES: 
 
@@ -51,7 +54,12 @@
 
 .. [Go09] Alexey G. Gorinov, "Combinatorics of double cosets and fundamental
    domains for the subgroups of the modular group", preprint
-   http://fr.arxiv.org/abs/0901.1340
+   http://arxiv.org/abs/0901.1340
+
+.. [KSV11] Ian Kiming, Matthias Schuett and Helena Verrill, "Lifts of
+   projective congruence groups", J. London Math. Soc. (2011) 83 (1): 96-120,
+   http://dx.doi.org/10.1112/jlms/jdq062. Arxiv version:
+   http://arxiv.org/abs/0905.4798.
 
 .. [Kul91] Ravi Kulkarni "An arithmetic geometric method in the study of the
    subgroups of the modular group", American Journal of Mathematics 113 (1991),
@@ -61,9 +69,9 @@
    http://www.public.iastate.edu/~kurthc/research/index.html
 
 .. [KuLo] Chris Kurth and Ling Long, "Computations with finite index subgroups
-   of `{\rm PSL}_2(ZZ)` using Farey symbols"
-
-.. [Ve] Helena Verrill, "Fundamental domain drawer", java program,
+   of `{\rm PSL}_2(\ZZ)` using Farey symbols"
+
+.. [Ve] Helena Verrill, "Fundamental domain drawer", Java program,
    http://www.math.lsu.edu/~verrill/
 
 TODO:
@@ -92,6 +100,9 @@
 
 - Vincent Delecroix (2010): implementation for odd groups, new design,
   improvements, documentation
+
+- David Loeffler (2011): congruence testing for odd subgroups, enumeration of
+  liftings of projective subgroups
 """
 
 ################################################################################
@@ -137,7 +148,7 @@
     The return format is a list of pairs ``(a,b)``, where ``a = 0`` or ``1``
     denoting ``L`` or ``R`` respectively, and ``b`` is an integer exponent.
 
-    See also the method eval_sl2z_word.
+    See also the function :func:`eval_sl2z_word`.
 
     EXAMPLES::
 
@@ -206,7 +217,7 @@
 
 def eval_sl2z_word(w):
     r"""
-    Given a word in the format output by sl2z_word_problem, convert it back
+    Given a word in the format output by :func:`sl2z_word_problem`, convert it back
     into an element of `{\rm SL}_2(\ZZ)`.
 
     EXAMPLES::
@@ -267,17 +278,18 @@
 
     return M
 
-def equalize_perms(l):
+def _equalize_perms(l):
     r"""
-    Transform a list of lists into a list of lists with identical length. Each list
-    ``p`` in the argument is completed with ``range(len(p),n)`` where ``n`` is
-    the maximal length of the lists in ``l``.
+    Transform a list of lists into a list of lists with identical length. Each
+    list ``p`` in the argument is completed with ``range(len(p),n)`` where
+    ``n`` is the maximal length of the lists in ``l``. Note that the lists are
+    modified in-place (rather than returning new lists).
 
     EXAMPLES::
 
-        sage: from sage.modular.arithgroup.arithgroup_perm import equalize_perms
+        sage: from sage.modular.arithgroup.arithgroup_perm import _equalize_perms
         sage: l = [[],[1,0],[3,0,1,2]]
-        sage: equalize_perms(l)
+        sage: _equalize_perms(l)
         sage: l
         [[0, 1, 2, 3], [1, 0, 2, 3], [3, 0, 1, 2]]
     """
@@ -287,18 +299,17 @@
     for p in l:
         p.extend(xrange(len(p),n))
 
-
 def ArithmeticSubgroup_Permutation(
         S2=None, S3=None, L=None, R=None,
         relabel=False,
         check=True):
     r"""
-    Generate a subgroup of `{\rm SL}_2(\ZZ)` from the action of generators on its
+    Construct a subgroup of `{\rm SL}_2(\ZZ)` from the action of generators on its
     right cosets.
 
     Return an arithmetic subgroup knowing the action, given by permutations, of
     at least two standard generators on the its cosets. The generators
-    considered are the following matrices
+    considered are the following matrices:
 
     .. math::
 
@@ -307,6 +318,9 @@
         l = \begin{pmatrix} 1 & 1 \\ 0 & 1\end{pmatrix},\quad 
         r = \begin{pmatrix} 1 & 0 \\ 1 & 1 \end{pmatrix}.
 
+    An error will be raised if only one permutation is given. If no arguments
+    are given at all, the full modular group `{\rm SL}(2, \ZZ)` is returned.
+
     INPUT:
 
     - ``S2``, ``S3``, ``L``, ``R`` - permutations - action of matrices on the
@@ -317,7 +331,7 @@
       canonical way.
 
     - ``check`` - boolean (default: True) - check that the input is valid (it
-      may be time efficient but less safer to set it to False)
+      may be time efficient but less safe to set it to False)
 
     EXAMPLES::
 
@@ -331,25 +345,52 @@
         sage: G.index()
         4
 
-        sage: ArithmeticSubgroup_Permutation()
+        sage: G = ArithmeticSubgroup_Permutation(); G
         Arithmetic subgroup with permutations of right cosets
          S2=()
          S3=()
          L=()
          R=()
+        sage: G == SL2Z
+        True
+
+    Some invalid inputs::
+
+        sage: ArithmeticSubgroup_Permutation(S2="(1,2)")
+        Traceback (most recent call last):
+        ...
+        ValueError: Need at least two generators
+        sage: ArithmeticSubgroup_Permutation(S2="(1,2)",S3="(3,4,5)")
+        Traceback (most recent call last):
+        ...
+        ValueError: Permutations do not generate a transitive group
+        sage: ArithmeticSubgroup_Permutation(L="(1,2)",R="(1,2,3)") 
+        Traceback (most recent call last):
+        ...
+        ValueError: Wrong relations between generators
+        sage: ArithmeticSubgroup_Permutation(S2="(1,2,3,4)",S3="()")
+        Traceback (most recent call last):
+        ...
+        ValueError: S2^2 does not equal to S3^3
+        sage: ArithmeticSubgroup_Permutation(S2="(1,4,2,5,3)", S3="(1,3,5,2,4)")
+        Traceback (most recent call last):
+        ...
+        ValueError: S2^2 = S3^3 must have order 1 or 2
+
+    The input checks can be disabled for speed::
+
+        sage: ArithmeticSubgroup_Permutation(S2="(1,2)",S3="(3,4,5)", check=False) # don't do this!
+        Arithmetic subgroup with permutations of right cosets
+         S2=(1,2)
+         S3=(3,4,5)
+         L=(1,2)(3,5,4)
+         R=(1,2)(3,4,5)
     """
     gens = filter(lambda x: x is not None, [S2,S3,L,R])
     if len(gens) == 0:
         S2 = S3 = L = R = ''
     elif len(gens) < 2:
-        raise ValueError, "need at least two generators"
-
-    if check:
-        from sage.groups.perm_gps.all import PermutationGroup
-
-        G = PermutationGroup(gens)
-        if not G.is_transitive():
-            raise ValueError, "the group is not transitive"
+        raise ValueError, "Need at least two generators"
 
     if S2 is not None:
         S2 = PermutationGroupElement(S2,check=check)
@@ -394,25 +435,34 @@
             R = S3 * S2
 
     if check and (L != ~S3 * ~S2 or R != S3 * S2):
-        raise ValueError, "wrong relations between generators"
+        raise ValueError, "Wrong relations between generators"
 
     inv = S2*S2
-    if check and (inv != S3*S3*S3):
-        raise ValueError, "S2^2 does not equal to S3^3"
+
+    if check:
+        if inv != S3*S3*S3:
+            raise ValueError, "S2^2 does not equal to S3^3"
+        elif not (inv*inv).is_one():
+            raise ValueError, "S2^2 = S3^3 must have order 1 or 2"
+
+        # Check transitivity. This is the most expensive check, so we do it
+        # last.
+        from sage.groups.perm_gps.all import PermutationGroup
+
+        G = PermutationGroup(gens)
+        if not G.is_transitive():
+            raise ValueError, "Permutations do not generate a transitive group"
 
     s2 = [i-1 for i in S2.list()]
     s3 = [i-1 for i in S3.list()]
     l = [i-1 for i in L.list()]
     r = [i-1 for i in R.list()]
-    equalize_perms((s2,s3,l,r))
+    _equalize_perms((s2,s3,l,r))
 
     if inv.is_one(): # the group is even
         G = EvenArithmeticSubgroup_Permutation(s2,s3,l,r)
-    else:
-        if (not check) or (inv.order() == 2 and inv in G.center()): # the group is odd
-            G = OddArithmeticSubgroup_Permutation(s2,s3,l,r)
-        else:
-             raise ValueError, "wrong relations between generators"
+    else: # the group is odd
+        G = OddArithmeticSubgroup_Permutation(s2,s3,l,r)
 
     if relabel:
         G.relabel()
@@ -490,6 +540,7 @@
             True
         """
         if isinstance(other, ArithmeticSubgroup_Permutation_class):
+
             return (self.is_odd() == other.is_odd() and
                     self.index() == other.index() and 
                     self.relabel(inplace=False)._S2 == other.relabel(inplace=False)._S2 and
@@ -498,8 +549,20 @@
         elif isinstance(other, ArithmeticSubgroup):
             return self == other.as_permutation_group()
 
-        else:
-            raise NotImplemented
+        raise NotImplemented
+
+    def __hash__(self):
+        r"""
+        Return a hash value.
+
+        TESTS::
+
+            sage: G1 = ArithmeticSubgroup_Permutation(S2='(1,2)(3,4)(5,6)',S3='(1,2,3)(4,5,6)')
+            sage: G2 = ArithmeticSubgroup_Permutation(S2='(1,2)(3,4)(5,6)',S3='(1,5,6)(4,2,3)')
+            sage: G1.__hash__() == G2.__hash__()
+            False
+        """
+        return hash((tuple(self.relabel(inplace=False)._S2),tuple(self.relabel(inplace=False)._S3)))
 
     def _repr_(self):
         r"""
@@ -1228,8 +1291,9 @@
 
         The *generalised level* of a subgroup of the modular group is the least
         common multiple of the widths of the cusps. It was proven by Wohlfart
-        that if the subgroup is a congruence subgroup then the (conventional)
-        level coincide with the generalised level. 
+        that for even congruence subgroups, the (conventional) level coincides
+        with the generalised level. For odd congruence subgroups the level is
+        either the generalised level, or twice the generalised level [KSV11]_.
 
         EXAMPLES::
 
@@ -1517,25 +1581,50 @@
 
     def is_congruence(self):
         r"""
-        Test whether self is a congruence group.
-
-        An odd group is *congruence* if, when we add to it the element `-Id` it
-        contains a congruence subgroup `\Gamma(n)` for a certain `n`.
+        Test whether self is a congruence group, i.e.~whether or not it
+        contains the subgroup `\Gamma(n)` for some `n`.
+
+        For odd groups, we first test whether the group generated by `G` and
+        `-1` is congruence, and then use a theorem of Kiming, Schuett and
+        Verrill [KSV11]_, which shows that an odd subgroup is congruence if and
+        only if it contains `\Gamma(N)` where `N` is twice its generalised
+        level (the least common multiple of its cusp widths). We can therefore
+        proceed by calculating the index of the subgroup of `{\rm SL}(2, \ZZ /
+        N\ZZ)` generated by the gens of self, and checking whether or not it
+        has the same index as self.
 
         EXAMPLES::
 
-            sage: S2 = '(1,6,4,3)(2,7,5,10)(8,9,11,12)'
-            sage: S3 = '(1,2,3,4,5,6)(7,8,9,10,11,12)'
-            sage: G = ArithmeticSubgroup_Permutation(S2=S2,S3=S3); G
-            Arithmetic subgroup with permutations of right cosets
-             S2=(1,6,4,3)(2,7,5,10)(8,9,11,12)
-             S3=(1,2,3,4,5,6)(7,8,9,10,11,12)
-             L=(2,3,10,8)(5,6,7,11)(9,12)
-             R=(1,7,9,2)(4,10,12,5)(8,11)
+            sage: GammaH(11,[4]).as_permutation_group().is_congruence()
+            True
+
+        The following example (taken from [KSV11]_) shows that it may be the
+        case that G is not congruence, even if its image in `{\rm PSL}(2,\ZZ)`
+        is congruence::
+
+            sage: S2 = "(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23)"
+            sage: S3 = "(1,14,15,13,2,3)(4,5,6,16,17,18)(7,8,9,19,20,21)(10,11,12,22,23,24)"
+            sage: G = ArithmeticSubgroup_Permutation(S2=S2,S3=S3)
             sage: G.is_congruence()
+            False
+            sage: G.to_even_subgroup().is_congruence()
+            True
+
+        In fact G is a lifting to `{\rm SL}(2,\ZZ)` of the group
+        `\bar{\Gamma}_0(6)`::
+
+            sage: G.to_even_subgroup() == Gamma0(6)
             True
         """
-        return self.to_even_subgroup().is_congruence()
+        Gev = self.to_even_subgroup()
+        if not Gev.is_congruence(): 
+            return False
+        else:
+            N = 2*self.generalised_level()
+            from sage.groups.matrix_gps.matrix_group import MatrixGroup
+            H = MatrixGroup([x.matrix().change_ring(Zmod(N)) for x in self.gens()])
+            from congroup_gamma import Gamma_constructor as Gamma
+            return Gamma(N).index() == self.index() * H.order()
 
 class EvenArithmeticSubgroup_Permutation(ArithmeticSubgroup_Permutation_class):
     r"""
@@ -1676,6 +1765,7 @@
         arithmetic subgroup.
 
         EXAMPLES::
+
             sage: G = ArithmeticSubgroup_Permutation(S2="(1,4)(2)(3)",S3="(1,2,3)(4)")
             sage: G.nu3()
             1
@@ -2114,14 +2204,32 @@
         if exp:
             return widths
         return sorted(widths)
- 
+
+    def to_even_subgroup(self, relabel=True):
+        r"""
+        Return the subgroup generated by self and ``-Id``. Since self is even,
+        this is just self. Provided for compatibility.
+
+        EXAMPLE::
+
+            sage: G = Gamma0(4).as_permutation_group()
+            sage: H = G.to_even_subgroup()
+            sage: H == G
+            True
+        """
+        if relabel:
+            return self.relabel(inplace=False)
+        else:
+            return self
+
     def is_congruence(self):
         r"""
         Return True if this is a congruence subgroup.
         
         ALGORITHM:
         
-        Uses Hsu's algorithm, as implemented by Chris Kurth in KFarey.
+        Uses Hsu's algorithm [Hsu96]_. Adapted from Chris Kurth's
+        implementation in KFarey [Kur08]_.
 
         EXAMPLES:
 
@@ -2235,7 +2343,191 @@
 
             return True
 
-
+    def one_odd_subgroup(self,random=False):
+        r"""
+        Return an odd subgroup of index 2 in `\Gamma`, where `\Gamma` is this
+        subgroup. If the optional argument ``random`` is False (the default),
+        this returns an arbitrary but consistent choice from the set of index 2
+        odd subgroups. If ``random`` is True, then it will choose one of these
+        at random. 
+        
+        For details of the algorithm used, see the docstring for the related
+        function :meth:`odd_subgroups`, which returns a list of all the
+        index 2 odd subgroups.
+
+        EXAMPLES:
+
+        Starting from `\Gamma(4)` we get back `\Gamma(4)`::
+
+            sage: G = Gamma(4).as_permutation_group()
+            sage: print G.is_odd(), G.index()
+            True 48
+            sage: Ge = G.to_even_subgroup()
+            sage: Go = Ge.one_odd_subgroup()
+            sage: print Go.is_odd(), Go.index()
+            True 48
+            sage: Go == G
+            True
+
+        Strating from `\Gamma(6)` we get a different group::
+
+            sage: G = Gamma(6).as_permutation_group()
+            sage: print G.is_odd(), G.index()
+            True 144
+            sage: Ge = G.to_even_subgroup()
+            sage: Go = Ge.one_odd_subgroup()
+            sage: print Go.is_odd(), Go.index()
+            True 144
+            sage: Go == G
+            False
+
+        An error will be raised if there are no such subgroups, which occurs if
+        and only if the group contains an element of order 4::
+
+            sage: Gamma0(10).as_permutation_group().one_odd_subgroup()
+            Traceback (most recent call last):
+            ...
+            ValueError: Group contains an element of order 4, hence no index 2 odd subgroups
+
+        Testing randomness::
+
+            sage: G = Gamma(6).as_permutation_group().to_even_subgroup()
+            sage: G1 = G.one_odd_subgroup(random=True) # random
+            sage: G1.is_subgroup(G)
+            True
+        """
+        if self.nu2() != 0:
+            raise ValueError, "Group contains an element of order 4, hence no index 2 odd subgroups"
+        n = self.index()
+        s2old, s3old = self.S2(), self.S3()
+        s2cycs = s2old.cycle_tuples() # no singletons can exist
+        s3cycs = s3old.cycle_tuples(singletons=True)
+        s2 = PermutationGroupElement([x + tuple(y + n for y in x) for x in s2cycs])
+        s3 = PermutationGroupElement([x + tuple(y + n for y in x) for x in s3cycs])
+
+        if random is False:
+            return ArithmeticSubgroup_Permutation(S2=s2,S3=s3,check=False)
+
+        from sage.misc.prandom import randint
+
+        t = []
+        for i in xrange(1,n+1):
+            if randint(0,1):
+                t.append((i,n+i))
+        t = PermutationGroupElement(t)
+        return ArithmeticSubgroup_Permutation(S2=s2,S3=t*s3*t,check=False)
+
+    def odd_subgroups(self):
+        r"""
+        Return a list of the odd subgroups of index 2 in `\Gamma`, where
+        `\Gamma` is this subgroup. (Equivalently, return the liftings of
+        `\bar{\Gamma} \le {\rm PSL}(2, \ZZ)` to `{\rm SL}(2, \ZZ)`.)
+
+        .. seealso:: :meth:`one_odd_subgroup`, which returns just one of the
+           odd subgroups (which is much quicker than enumerating them all).
+
+        ALGORITHM:
+
+        - If `\Gamma` has an element of order 4, then there are no index 2 odd
+          subgroups, so return the empty set.
+        
+        - If `\Gamma` has no elements of order 4, then the permutation `S_2` is
+          a combination of 2-cycles with no fixed points on `\{1, \dots, N\}`.
+          We construct the permutation `\tilde{S}_2` of `\{1, \dots, 2N\}`
+          which has a 4-cycle `(a, b, a+N, b+N)` for each 2-cycle `(a,b)` in
+          ``S2``. Similarly, we construct a permutation `\tilde{S}_3` which has
+          a 6-cycle `(a,b,c,a+N,b+N,c+N)` for each 3-cycle `(a,b,c)` in `S_3`,
+          and a 2-cycle `(a,a+N)` for each fixed point `a` of `S_3`.
+
+          Then the permutations `\tilde{S}_2` and `\tilde{S}_3` satisfy
+          `\tilde{S}_2^2 = \tilde{S}_3^3 = \iota` where `\iota` is the order 2
+          permutation interchanging `a` and `a+N` for each `a`. So the subgroup
+          corresponding to these permutations is an index 2 odd subgroup of
+          `\Gamma`.
+
+        - The other index 2 odd subgroups of `\Gamma` are obtained from the
+          pairs `\tilde{S}_2, \tilde{S}_3^\sigma` where `\sigma` is an element
+          of the group generated by the 2-cycles `(a, a+N)`.
+
+        Studying the permutations in the first example below gives a good
+        illustration of the algorithm.
+
+        EXAMPLES::
+
+            sage: G = sage.modular.arithgroup.arithgroup_perm.HsuExample10()
+            sage: [G.S2(), G.S3()]
+            [(1,2)(3,4)(5,6)(7,8)(9,10), (1,8,3)(2,4,6)(5,7,10)]
+            sage: X = G.odd_subgroups()
+            sage: for u in X: print [u.S2(), u.S3()]
+            [(1,2,11,12)(3,4,13,14)(5,6,15,16)(7,8,17,18)(9,10,19,20), (1,8,3,11,18,13)(2,4,6,12,14,16)(5,7,10,15,17,20)(9,19)] 
+            [(1,2,11,12)(3,4,13,14)(5,6,15,16)(7,8,17,18)(9,10,19,20), (1,18,13,11,8,3)(2,4,6,12,14,16)(5,7,10,15,17,20)(9,19)] 
+            [(1,2,11,12)(3,4,13,14)(5,6,15,16)(7,8,17,18)(9,10,19,20), (1,8,13,11,18,3)(2,4,6,12,14,16)(5,7,10,15,17,20)(9,19)] 
+            [(1,2,11,12)(3,4,13,14)(5,6,15,16)(7,8,17,18)(9,10,19,20), (1,18,3,11,8,13)(2,4,6,12,14,16)(5,7,10,15,17,20)(9,19)]
+            
+        A projective congruence subgroup may have noncongruence liftings, as the example of `\bar{\Gamma}_0(6)` illustrates (see [KSV11]_)::
+
+            sage: X = Gamma0(6).as_permutation_group().odd_subgroups(); Sequence([[u.S2(), u.S3()] for u in X],cr=True)
+            [
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,2,3,13,14,15)(4,5,6,16,17,18)(7,8,9,19,20,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,14,15,13,2,3)(4,5,6,16,17,18)(7,8,9,19,20,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,2,3,13,14,15)(4,17,6,16,5,18)(7,8,9,19,20,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,14,15,13,2,3)(4,17,6,16,5,18)(7,8,9,19,20,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,2,3,13,14,15)(4,5,6,16,17,18)(7,20,9,19,8,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,14,15,13,2,3)(4,5,6,16,17,18)(7,20,9,19,8,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,2,3,13,14,15)(4,17,6,16,5,18)(7,20,9,19,8,21)(10,11,12,22,23,24)],
+            [(1,3,13,15)(2,4,14,16)(5,7,17,19)(6,10,18,22)(8,12,20,24)(9,11,21,23), (1,14,15,13,2,3)(4,17,6,16,5,18)(7,20,9,19,8,21)(10,11,12,22,23,24)]
+            ]
+            sage: [u.is_congruence() for u in X]
+            [True, False, False, True, True, False, False, True]
+
+        Subgroups of large index may take a very long time::
+
+            sage: len(GammaH(11,[-1]).as_permutation_group().odd_subgroups()) # long time (15s)
+            2048
+        """
+        if self.nu2() != 0:
+            return []
+        n = self.index()
+        s2old, s3old = self.S2(), self.S3()
+        s2cycs = s2old.cycle_tuples() # no singletons can exist
+        s3cycs = s3old.cycle_tuples(singletons=True)
+        s2 = PermutationGroupElement([x + tuple(y + n for y in x) for x in s2cycs])
+        s3 = PermutationGroupElement([x + tuple(y + n for y in x) for x in s3cycs])
+        H = ArithmeticSubgroup_Permutation(S2=s2,S3=s3)
+
+        bucket = set([H])
+        res = [H]
+        # We use a set *and* a list since checking whether an element is in a
+        # set is very fast, but on the other hand we want the order the results
+        # are returned to be at least somewhat canonical.
+        ts = [PermutationGroupElement(range(1,1+2*n))]
+        
+        for i in xrange(1,n+1):
+            
+            t = PermutationGroupElement([(i, n+i)],check=False)
+            
+            s3c = t*s3*t
+            
+            if s3c == s3: 
+                # t commutes with s3; nothing to see here.
+                continue
+
+            HH = ArithmeticSubgroup_Permutation(S2=s2,S3=s3c,check=False)
+            
+            if HH not in bucket:
+                # Because the liftings are indexed by Hom(self, +-1) which is a
+                # vector space over F2, either HH is already familiar, or all
+                # the subgroups one gets by acting by t are new.
+                
+                bucket.add(HH)
+                res.append(HH)
+                ts.append(t)
+                for tt in ts[1:-1]: 
+                    ts.append(tt*t)
+                    res.append(ArithmeticSubgroup_Permutation(S2=s2,S3=tt*s3c*tt,check=False))
+                    bucket.add(res[-1])
+
+        return res
 
 def HsuExample10():
     r"""
diff -r b2fc07dff052 -r f2839cfc46bf sage/modular/arithgroup/tests.py
--- a/sage/modular/arithgroup/tests.py	Fri Jul 15 16:06:24 2011 +0100
+++ b/sage/modular/arithgroup/tests.py	Sat Jul 16 15:59:37 2011 +0100
@@ -27,8 +27,10 @@
     EXAMPLES::
 
         sage: import sage.modular.arithgroup.tests as tests
-        sage: tests.random_even_arithgroup(30) #random
+        sage: G = tests.random_even_arithgroup(30); G # random
         Arithmetic subgroup of index 30
+        sage: G.is_even()
+        True
     """
     from sage.groups.perm_gps.permgroup import PermutationGroup
 
@@ -36,8 +38,12 @@
 
     if nu2_max is None:
         nu2_max = index//5
+    elif nu2_max == 0:
+        assert index%2 == 0
     if nu3_max is None:
         nu3_max = index//7
+    elif nu3_max == 0:
+        assert index%3 == 0
 
     while not test:
         nu2 = prandom.randint(0,nu2_max)
@@ -63,11 +69,27 @@
 
     return ArithmeticSubgroup_Permutation(S2=S2,S3=S3)
 
+def random_odd_arithgroup(index,nu3_max=None):
+    r"""
+    Return a random odd arithmetic subgroup
+
+    EXAMPLES::
+
+        sage: from sage.modular.arithgroup.tests import random_odd_arithgroup
+        sage: G = random_odd_arithgroup(20); G #random
+        Arithmetic subgroup of index 20
+        sage: G.is_odd()
+        True
+    """
+    assert index%4 == 0
+    G = random_even_arithgroup(index//2,nu2_max=0,nu3_max=nu3_max)
+    return G.one_odd_subgroup(random=True)
+
 class Test:
     r"""
     Testing class for arithmetic subgroup implemented via permutations.
     """
-    def __init__(self, index=20, index_max=50):
+    def __init__(self, index=20, index_max=50, odd_probability=0.5):
         r"""
         Create an arithmetic subgroup testing object.
 
@@ -85,6 +107,9 @@
         """
         self.congroups = []
         i = 1
+        self.odd_probability = odd_probability
+        if index%2:
+            self.odd_probability=0
         while Gamma(i).index() < index_max:
             self.congroups.append(Gamma(i))
             i += 1
@@ -204,17 +229,24 @@
             sage: from sage.modular.arithgroup.tests import Test
             sage: Test().test_relabel() # random
         """
-        G = random_even_arithgroup(self.index)
+        if prandom.uniform(0,1) < self.odd_probability:
+            G = random_odd_arithgroup(self.index)
+        else:
+            G = random_even_arithgroup(self.index)
+
         G.relabel()
         s2 = G._S2
         s3 = G._S3
         l = G._L
         r = G._R
 
+        # 0 should be stabilized by the mapping
+        # used for renumbering so we start at 1
         p = range(1,self.index)
 
         for _ in xrange(10):
             prandom.shuffle(p)
+            # we add 0 to the mapping
             pp = [0] + p
             ss2 = [None]*self.index
             ss3 = [None]*self.index
@@ -291,7 +323,10 @@
             sage: from sage.modular.arithgroup.tests import Test
             sage: Test().test_contains() #random
         """
-        G = random_even_arithgroup(self.index)
+        if prandom.uniform(0,1) < self.odd_probability:
+            G = random_odd_arithgroup(self.index)
+        else:
+            G = random_even_arithgroup(self.index)
 
         for _ in xrange(20):
             g = G.random_element()
