Ejemplo n.º 1
0
def PBD_4_7(v, check=True, existence=False):
    r"""
    Return a `(v,\{4,7\})`-PBD

    For all `v` such that `n\equiv 1\pmod{3}` and `n\neq 10,19, 31` there exists
    a `(v,\{4,7\})`-PBD. This is proved in Proposition IX.4.5 from [BJL99]_,
    which this method implements.

    This construction of PBD is used by the construction of Kirkman Triple
    Systems.

    EXAMPLE::

        sage: from sage.combinat.designs.resolvable_bibd import PBD_4_7
        sage: PBD_4_7(22)
        Pairwise Balanced Design on 22 points with sets of sizes in set([4, 7])

    TESTS:

    All values `\leq 300`::

        sage: for i in range(1,300,3):
        ....:     if i not in [10,19,31]:
        ....:         assert PBD_4_7(i,existence=True)
        ....:         _ = PBD_4_7(i,check=True)
    """
    if v % 3 != 1 or v in [10, 19, 31]:
        if existence:
            return Unknown
        raise NotImplementedError
    if existence:
        return True

    from .group_divisible_designs import GroupDivisibleDesign
    from .group_divisible_designs import GDD_4_2
    from .bibd import PairwiseBalancedDesign
    from .bibd import balanced_incomplete_block_design

    if v == 22:
        # Beth/Jungnickel/Lenz: take KTS(15) and extend each of the 7 classes
        # with a new point. Make those new points a 7-set.
        KTS15 = kirkman_triple_system(15)
        blocks = [
            S + [i + 15] for i, classs in enumerate(KTS15._classes)
            for S in classs
        ] + [list(range(15, 22))]

    elif v == 34:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0, 0), (1, 1), (2, 0), (4, 1)]
        B = [(0, 0), (1, 0), (4, 2)]
        C = [(0, 0), (2, 2), (5, 0)]
        D = [(0, 0), (0, 1), (0, 2)]

        A = [[(x + i, y + j) for x, y in A] for i in range(9)
             for j in range(3)]
        B = [[(x + i, y + i + j) for x, y in B] + [27 + j] for i in range(9)
             for j in range(3)]
        C = [[(x + i + j, y + 2 * i + j) for x, y in C] + [30 + j]
             for i in range(9) for j in range(3)]
        D = [[(x + i, y + i) for x, y in D] + [33] for i in range(9)]

        blocks = [[
            int(x) if not isinstance(x, tuple) else (x[1] % 3) * 9 + (x[0] % 9)
            for x in S
        ] for S in A + B + C + D + [list(range(27, 34))]]
    elif v == 46:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(1, 0), (3, 0), (9, 0), (0, 1)]
        B = [(2, 0), (6, 0), (5, 0), (0, 1)]
        C = [(0, 0), (1, 1), (4, 2)]
        D = [(0, 0), (2, 1), (7, 2)]
        E = [(0, 0), (0, 1), (0, 2)]

        A = [[(x + i, y + j) for x, y in A] for i in range(13)
             for j in range(3)]
        B = [[(x + i, y + j) for x, y in B] for i in range(13)
             for j in range(3)]
        C = [[(x + i, y + j) for x, y in C] + [39 + j] for i in range(13)
             for j in range(3)]
        D = [[(x + i, y + j) for x, y in D] + [42 + j] for i in range(13)
             for j in range(3)]
        E = [[(x + i, y + i) for x, y in E] + [45] for i in range(13)]

        blocks = [[
            int(x) if not isinstance(x, tuple) else
            (x[1] % 3) * 13 + (x[0] % 13) for x in S
        ] for S in A + B + C + D + E + [list(range(39, 46))]]

    elif v == 58:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0, 0), (1, 0), (4, 0), (5, 1)]
        B = [(0, 0), (2, 0), (8, 0), (11, 1)]
        C = [(0, 0), (5, 0), (2, 1), (12, 1)]
        D = [(0, 0), (8, 1), (7, 2)]
        E = [(0, 0), (6, 1), (4, 2)]
        F = [(0, 0), (0, 1), (0, 2)]

        A = [[(x + i, y + j) for x, y in A] for i in range(17)
             for j in range(3)]
        B = [[(x + i, y + j) for x, y in B] for i in range(17)
             for j in range(3)]
        C = [[(x + i, y + j) for x, y in C] for i in range(17)
             for j in range(3)]
        D = [[(x + i, y + j) for x, y in D] + [51 + j] for i in range(17)
             for j in range(3)]
        E = [[(x + i, y + j) for x, y in E] + [54 + j] for i in range(17)
             for j in range(3)]
        F = [[(x + i, y + i) for x, y in F] + [57] for i in range(17)]

        blocks = [[
            int(x) if not isinstance(x, tuple) else
            (x[1] % 3) * 17 + (x[0] % 17) for x in S
        ] for S in A + B + C + D + E + F + [list(range(51, 58))]]

    elif v == 70:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0, 0), (1, 0), (5, 1), (13, 1)]
        B = [(0, 0), (4, 0), (20, 1), (10, 1)]
        C = [(0, 0), (16, 0), (17, 1), (19, 1)]
        D = [(0, 0), (2, 1), (8, 1), (11, 1)]
        E = [(0, 0), (3, 2), (9, 1)]
        F = [(0, 0), (7, 0), (14, 1)]
        H = [(0, 0), (0, 1), (0, 2)]

        A = [[(x + i, y + j) for x, y in A] for i in range(21)
             for j in range(3)]
        B = [[(x + i, y + j) for x, y in B] for i in range(21)
             for j in range(3)]
        C = [[(x + i, y + j) for x, y in C] for i in range(21)
             for j in range(3)]
        D = [[(x + i, y + j) for x, y in D] for i in range(21)
             for j in range(3)]
        E = [[(x + i, y + j) for x, y in E] + [63 + j] for i in range(21)
             for j in range(3)]
        F = [[(x + 3 * i + j, y + ii + j) for x, y in F] + [66 + j]
             for i in range(7) for j in range(3) for ii in range(3)]
        H = [[(x + i, y + i) for x, y in H] + [69] for i in range(21)]

        blocks = [[
            int(x) if not isinstance(x, tuple) else
            (x[1] % 3) * 21 + (x[0] % 21) for x in S
        ] for S in A + B + C + D + E + F + H + [list(range(63, 70))]]

    elif v == 82:
        # This construction is Theorem IX.3.16 from [BJL99] (p.627).
        #
        # A (15,{4},{3})-GDD from a (16,4)-BIBD
        from .group_divisible_designs import group_divisible_design
        from .orthogonal_arrays import transversal_design
        GDD = group_divisible_design(3 * 5, K=[4], G=[3], check=False)
        TD = transversal_design(5, 5)

        # A (75,{4},{15})-GDD
        GDD2 = [[3 * B[x // 3] + x % 3 for x in BB] for B in TD for BB in GDD]

        # We now complete the (75,{4},{15})-GDD into a (82,{4,7})-PBD. For this,
        # we add 7 new points that are added to all groups of size 15.
        #
        # On these groups a (15+7,{4,7})-PBD is pasted, in such a way that the 7
        # new points are a set of the final PBD
        PBD22 = PBD_4_7(15 + 7)
        S = next(SS for SS in PBD22 if len(SS) == 7)  # a set of size 7
        PBD22.relabel({
            v: i
            for i, v in enumerate([i for i in range(15 + 7) if i not in S] + S)
        })

        for B in PBD22:
            if B == S:
                continue
            for i in range(5):
                GDD2.append([x + i * 15 if x < 15 else x + 60 for x in B])

        GDD2.append(list(range(75, 82)))
        blocks = GDD2

    elif v == 94:
        # IX.4.5.l from [BJL99].
        #
        # take 4 parallel lines from an affine plane of order 7, and a 5th
        # one. This is a (31,{4,5,7})-BIBD. And 94=3*31+1.
        from sage.combinat.designs.block_design import AffineGeometryDesign
        AF = AffineGeometryDesign(2, 1, 7)
        parall = []
        plus_one = None
        for S in AF:
            if all(all(x not in SS for x in S) for SS in parall):
                parall.append(S)
            elif plus_one is None:
                plus_one = S
            if len(parall) == 4 and plus_one is not None:
                break
        X = set(sum(parall, plus_one))

        S_4_5_7 = [X.intersection(S) for S in AF]
        S_4_5_7 = [S for S in S_4_5_7 if len(S) > 1]
        S_4_5_7 = PairwiseBalancedDesign(X,
                                         blocks=S_4_5_7,
                                         K=[4, 5, 7],
                                         check=False)
        S_4_5_7.relabel()
        return PBD_4_7_from_Y(S_4_5_7, check=check)

    elif v == 127 or v == 142:
        # IX.4.5.o from [BJL99].
        #
        # Attach two or seven infinite points to a (40,4)-RBIBD to get a
        # (42,{4,5},{1,2,7})-GDD or a (47,{4,5},{1,2,7})-GDD
        points_to_add = 2 if v == 127 else 7
        rBIBD4 = v_4_1_rbibd(40)
        GDD = [
            S + [40 + i] if i < points_to_add else S
            for i, classs in enumerate(rBIBD4._classes) for S in classs
        ]
        if points_to_add == 7:
            GDD.append(list(range(40, 40 + points_to_add)))
            groups = [[x] for x in range(40 + points_to_add)]
        else:
            groups = [[x] for x in range(40)]
            groups.append(list(range(40, 40 + points_to_add)))
        GDD = GroupDivisibleDesign(40 + points_to_add,
                                   groups=groups,
                                   blocks=GDD,
                                   K=[2, 4, 5, 7],
                                   check=False,
                                   copy=False)

        return PBD_4_7_from_Y(GDD, check=check)

    elif v % 6 == 1 and GDD_4_2((v - 1) // 6, existence=True):
        # VII.5.17 from [BJL99]
        gdd = GDD_4_2((v - 1) // 6)
        return PBD_4_7_from_Y(gdd, check=check)

    elif v == 202:
        # IV.4.5.p from [BJL99]
        PBD = PBD_4_7(22, check=False)
        PBD = PBD_4_7_from_Y(PBD, check=False)
        return PBD_4_7_from_Y(PBD, check=check)

    elif balanced_incomplete_block_design(v, 4, existence=True):
        return balanced_incomplete_block_design(v, 4)
    elif balanced_incomplete_block_design(v, 7, existence=True):
        return balanced_incomplete_block_design(v, 7)
    else:
        from sage.combinat.designs.orthogonal_arrays import orthogonal_array
        # IX.4.5.m from [BJL99].
        #
        # This construction takes a TD(5,g) and truncates its last column to
        # size u: it yields a (4g+u,{4,5},{g,u})-GDD. If there exists a
        # (3g+1,{4,7})-PBD and a (3u+1,{4,7})-PBD, then we can apply the x->3x+1
        # construction on the truncated transversal design (which is a GDD).
        #
        # We write vv = 4g+u while satisfying the hypotheses.
        vv = (v - 1) // 3
        for g in range((vv + 5 - 1) // 5, vv // 4 + 1):
            u = vv - 4 * g
            if (orthogonal_array(5, g, existence=True)
                    and PBD_4_7(3 * g + 1, existence=True)
                    and PBD_4_7(3 * u + 1, existence=True)):
                from .orthogonal_arrays import transversal_design
                domain = set(range(vv))
                GDD = transversal_design(5, g)
                GDD = GroupDivisibleDesign(
                    vv,
                    groups=[[x for x in gr if x in domain]
                            for gr in GDD.groups()],
                    blocks=[[x for x in B if x in domain] for B in GDD],
                    G=set([g, u]),
                    K=[4, 5],
                    check=False)
                return PBD_4_7_from_Y(GDD, check=check)

    return PairwiseBalancedDesign(v,
                                  blocks=blocks,
                                  K=[4, 7],
                                  check=check,
                                  copy=False)
Ejemplo n.º 2
0
def PBD_4_7(v,check=True, existence=False):
    r"""
    Return a `(v,\{4,7\})`-PBD

    For all `v` such that `n\equiv 1\pmod{3}` and `n\neq 10,19, 31` there exists
    a `(v,\{4,7\})`-PBD. This is proved in Proposition IX.4.5 from [BJL99]_,
    which this method implements.

    This construction of PBD is used by the construction of Kirkman Triple
    Systems.

    EXAMPLE::

        sage: from sage.combinat.designs.resolvable_bibd import PBD_4_7
        sage: PBD_4_7(22)
        Pairwise Balanced Design on 22 points with sets of sizes in set([4, 7])

    TESTS:

    All values `\leq 300`::

        sage: for i in range(1,300,3):
        ....:     if i not in [10,19,31]:
        ....:         assert PBD_4_7(i,existence=True)
        ....:         _ = PBD_4_7(i,check=True)
    """
    if v%3 != 1 or v in [10,19,31]:
        if existence:
            return Unknown
        raise NotImplementedError
    if existence:
        return True

    from group_divisible_designs import GroupDivisibleDesign
    from group_divisible_designs import GDD_4_2
    from bibd import PairwiseBalancedDesign
    from bibd import balanced_incomplete_block_design

    if v == 22:
        # Beth/Jungnickel/Lenz: take KTS(15) and extend each of the 7 classes
        # with a new point. Make those new points a 7-set.
        KTS15 = kirkman_triple_system(15)
        blocks = [S+[i+15] for i,classs in enumerate(KTS15._classes) for S in classs]+[range(15,22)]

    elif v == 34:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0,0),(1,1),(2,0),(4,1)]
        B = [(0,0),(1,0),(4,2)]
        C = [(0,0),(2,2),(5,0)]
        D = [(0,0),(0,1),(0,2)]

        A = [[(x+i,  y+j)     for x,y in A]        for i in range(9) for j in range(3)]
        B = [[(x+i,  y+i+j)   for x,y in B]+[27+j] for i in range(9) for j in range(3)]
        C = [[(x+i+j,y+2*i+j) for x,y in C]+[30+j] for i in range(9) for j in range(3)]
        D = [[(x+i,  y+i)     for x,y in D]+[33]   for i in range(9)]

        blocks = [[int(x) if not isinstance(x,tuple) else (x[1]%3)*9+(x[0]%9) for x in S]
                  for S in A+B+C+D+[range(27,34)]]
    elif v == 46:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(1,0),(3,0),(9,0),(0,1)]
        B = [(2,0),(6,0),(5,0),(0,1)]
        C = [(0,0),(1,1),(4,2)]
        D = [(0,0),(2,1),(7,2)]
        E = [(0,0),(0,1),(0,2)]

        A = [[(x+i,  y+j) for x,y in A]        for i in range(13) for j in range(3)]
        B = [[(x+i,  y+j) for x,y in B]        for i in range(13) for j in range(3)]
        C = [[(x+i,  y+j) for x,y in C]+[39+j] for i in range(13) for j in range(3)]
        D = [[(x+i,  y+j) for x,y in D]+[42+j] for i in range(13) for j in range(3)]
        E = [[(x+i,  y+i) for x,y in E]+[45]   for i in range(13)]

        blocks = [[int(x) if not isinstance(x,tuple) else (x[1]%3)*13+(x[0]%13) for x in S]
                  for S in A+B+C+D+E+[range(39,46)]]

    elif v == 58:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0,0),(1,0),(4,0),( 5,1)]
        B = [(0,0),(2,0),(8,0),(11,1)]
        C = [(0,0),(5,0),(2,1),(12,1)]
        D = [(0,0),(8,1),(7,2)]
        E = [(0,0),(6,1),(4,2)]
        F = [(0,0),(0,1),(0,2)]

        A = [[(x+i,  y+j) for x,y in A]        for i in range(17) for j in range(3)]
        B = [[(x+i,  y+j) for x,y in B]        for i in range(17) for j in range(3)]
        C = [[(x+i,  y+j) for x,y in C]        for i in range(17) for j in range(3)]
        D = [[(x+i,  y+j) for x,y in D]+[51+j] for i in range(17) for j in range(3)]
        E = [[(x+i,  y+j) for x,y in E]+[54+j] for i in range(17) for j in range(3)]
        F = [[(x+i,  y+i) for x,y in F]+[57]   for i in range(17)]

        blocks = [[int(x) if not isinstance(x,tuple) else (x[1]%3)*17+(x[0]%17) for x in S]
                  for S in A+B+C+D+E+F+[range(51,58)]]

    elif v == 70:
        # [BJL99] (p527,vol1), but originally Brouwer
        A = [(0,0),(1,0),(5,1),(13,1)]
        B = [(0,0),(4,0),(20,1),(10,1)]
        C = [(0,0),(16,0),(17,1),(19,1)]
        D = [(0,0),(2,1),(8,1),(11,1)]
        E = [(0,0),(3,2),(9,1)]
        F = [(0,0),(7,0),(14,1)]
        H = [(0,0),(0,1),(0,2)]

        A = [[(x+i,  y+j)       for x,y in A]        for i in range(21) for j in range(3)]
        B = [[(x+i,  y+j)       for x,y in B]        for i in range(21) for j in range(3)]
        C = [[(x+i,  y+j)       for x,y in C]        for i in range(21) for j in range(3)]
        D = [[(x+i,  y+j)       for x,y in D]        for i in range(21) for j in range(3)]
        E = [[(x+i,  y+j)       for x,y in E]+[63+j] for i in range(21) for j in range(3)]
        F = [[(x+3*i+j, y+ii+j) for x,y in F]+[66+j] for i in range( 7) for j in range(3) for ii in range(3)]
        H = [[(x+i,  y+i)       for x,y in H]+[69]   for i in range(21)]

        blocks = [[int(x) if not isinstance(x,tuple) else (x[1]%3)*21+(x[0]%21)
                   for x in S]
                  for S in A+B+C+D+E+F+H+[range(63,70)]]

    elif v == 82:
        # This construction is Theorem IX.3.16 from [BJL99] (p.627).
        #
        # A (15,{4},{3})-GDD from a (16,4)-BIBD
        from group_divisible_designs import group_divisible_design
        from orthogonal_arrays       import transversal_design
        GDD = group_divisible_design(3*5,K=[4],G=[3],check=False)
        TD  = transversal_design(5,5)

        # A (75,{4},{15})-GDD
        GDD2 = [[3*B[x//3]+x%3 for x in BB] for B in TD for BB in GDD]

        # We now complete the (75,{4},{15})-GDD into a (82,{4,7})-PBD. For this,
        # we add 7 new points that are added to all groups of size 15.
        #
        # On these groups a (15+7,{4,7})-PBD is pasted, in such a way that the 7
        # new points are a set of the final PBD
        PBD22 = PBD_4_7(15+7)
        S = next(SS for SS in PBD22 if len(SS) == 7) # a set of size 7
        PBD22.relabel({v:i for i,v in enumerate([i for i in range(15+7) if i not in S] + S)})

        for B in PBD22:
            if B == S:
                continue
            for i in range(5):
                GDD2.append([x+i*15 if x<15 else x+60 for x in B])

        GDD2.append(range(75,82))
        blocks = GDD2

    elif v == 94:
        # IX.4.5.l from [BJL99].
        #
        # take 4 parallel lines from an affine plane of order 7, and a 5th
        # one. This is a (31,{4,5,7})-BIBD. And 94=3*31+1.
        from sage.combinat.designs.block_design import AffineGeometryDesign
        AF = AffineGeometryDesign(2,1,7)
        parall = []
        plus_one = None
        for S in AF:
            if all(all(x not in SS for x in S) for SS in parall):
                parall.append(S)
            elif plus_one is None:
                plus_one = S
            if len(parall) == 4 and plus_one is not None:
                break
        X = set(sum(parall,plus_one))

        S_4_5_7 = [X.intersection(S) for S in AF]
        S_4_5_7 = [S for S in S_4_5_7 if len(S)>1]
        S_4_5_7 = PairwiseBalancedDesign(X,
                                         blocks = S_4_5_7,
                                         K = [4,5,7],
                                         check=False)
        S_4_5_7.relabel()
        return PBD_4_7_from_Y(S_4_5_7,check=check)

    elif v == 127 or v == 142:
        # IX.4.5.o from [BJL99].
        #
        # Attach two or seven infinite points to a (40,4)-RBIBD to get a
        # (42,{4,5},{1,2,7})-GDD or a (47,{4,5},{1,2,7})-GDD
        points_to_add = 2 if v == 127 else 7
        rBIBD4 = v_4_1_rbibd(40)
        GDD = [S+[40+i] if i<points_to_add else S
               for i,classs in enumerate(rBIBD4._classes)
               for S in classs]
        if points_to_add == 7:
            GDD.append(range(40,40+points_to_add))
            groups = [[x] for x in range(40+points_to_add)]
        else:
            groups = [[x] for x in range(40)]
            groups.append(range(40,40+points_to_add))
        GDD = GroupDivisibleDesign(40+points_to_add,
                                   groups = groups,
                                   blocks = GDD,
                                   K = [2,4,5,7],
                                   check = False,
                                   copy = False)

        return PBD_4_7_from_Y(GDD,check=check)

    elif v%6 == 1 and GDD_4_2((v-1)/6,existence=True):
        # VII.5.17 from [BJL99]
        gdd = GDD_4_2((v-1)/6)
        return PBD_4_7_from_Y(gdd,check=check)

    elif v == 202:
        # IV.4.5.p from [BJL99]
        PBD = PBD_4_7(22,check=False)
        PBD = PBD_4_7_from_Y(PBD,check=False)
        return PBD_4_7_from_Y(PBD,check=check)

    elif balanced_incomplete_block_design(v,4,existence=True):
        return balanced_incomplete_block_design(v,4)
    elif balanced_incomplete_block_design(v,7,existence=True):
        return balanced_incomplete_block_design(v,7)
    else:
        from sage.combinat.designs.orthogonal_arrays import orthogonal_array
        # IX.4.5.m from [BJL99].
        #
        # This construction takes a TD(5,g) and truncates its last column to
        # size u: it yields a (4g+u,{4,5},{g,u})-GDD. If there exists a
        # (3g+1,{4,7})-PBD and a (3u+1,{4,7})-PBD, then we can apply the x->3x+1
        # construction on the truncated transversal design (which is a GDD).
        #
        # We write vv = 4g+u while satisfying the hypotheses.
        vv = (v-1)/3
        for g in range((vv+5-1)//5,vv//4+1):
            u = vv-4*g
            if (orthogonal_array(5,g,existence=True) and
                PBD_4_7(3*g+1,existence=True)        and
                PBD_4_7(3*u+1,existence=True)):
                from orthogonal_arrays import transversal_design
                domain = set(range(vv))
                GDD = transversal_design(5,g)
                GDD = GroupDivisibleDesign(vv,
                                           groups = [[x for x in gr if x in domain] for gr in GDD.groups()],
                                           blocks = [[x for x in B if x in domain] for B in GDD],
                                           G = set([g,u]),
                                           K = [4,5],
                                           check=False)
                return PBD_4_7_from_Y(GDD,check=check)

    return PairwiseBalancedDesign(v,
                                  blocks = blocks,
                                  K = [4,7],
                                  check = check,
                                  copy = False)
Ejemplo n.º 3
0
def OrthogonalArrayBlockGraph(k, n, OA=None):
    r"""
    Return the graph of an `OA(k,n)`.

    The intersection graph of the blocks of a transversal design with parameters
    `(k,n)`, or `TD(k,n)` for short, is a strongly regular graph (unless it is a
    complete graph). Its parameters `(v,k',\lambda,\mu)` are determined by the
    parameters `k,n` via:

    .. MATH::

        v=n^2, k'=k(n-1), \lambda=(k-1)(k-2)+n-2, \mu=k(k-1)

    As transversal designs and orthogonal arrays (OA for short) are equivalent
    objects, this graph can also be built from the blocks of an `OA(k,n)`, two
    of them being adjacent if one of their coordinates match.

    For more information on these graphs, see `Andries Brouwer's page
    on Orthogonal Array graphs <https://www.win.tue.nl/~aeb/graphs/OA.html>`_.

    .. WARNING::

        - Brouwer's website uses the notation `OA(n,k)` instead of `OA(k,n)`

        - For given parameters `k` and `n` there can be many `OA(k,n)` : the
          graphs returned are not uniquely defined by their parameters (see the
          examples below).

        - If the function is called only with the parameter ``k`` and ``n`` the
          results might be different with two versions of Sage, or even worse :
          some could not be available anymore.

    .. SEEALSO::

        :mod:`sage.combinat.designs.orthogonal_arrays`

    INPUT:

    - ``k,n`` (integers)

    - ``OA`` -- An orthogonal array. If set to ``None`` (default) then
      :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` is
      called to compute an `OA(k,n)`.

    EXAMPLES::

        sage: G = graphs.OrthogonalArrayBlockGraph(5,5); G
        OA(5,5): Graph on 25 vertices
        sage: G.is_strongly_regular(parameters=True)
        (25, 20, 15, 20)
        sage: G = graphs.OrthogonalArrayBlockGraph(4,10); G
        OA(4,10): Graph on 100 vertices
        sage: G.is_strongly_regular(parameters=True)
        (100, 36, 14, 12)

    Two graphs built from different orthogonal arrays are also different::

        sage: k=4;n=10
        sage: OAa = designs.orthogonal_arrays.build(k,n)
        sage: OAb = [[(x+1)%n for x in R] for R in OAa]
        sage: set(map(tuple,OAa)) == set(map(tuple,OAb))
        False
        sage: Ga = graphs.OrthogonalArrayBlockGraph(k,n,OAa)
        sage: Gb = graphs.OrthogonalArrayBlockGraph(k,n,OAb)
        sage: Ga == Gb
        False

    As ``OAb`` was obtained from ``OAa`` by a relabelling the two graphs are
    isomorphic::

        sage: Ga.is_isomorphic(Gb)
        True

    But there are examples of `OA(k,n)` for which the resulting graphs are not
    isomorphic::

        sage: oa0 = [[0, 0, 1], [0, 1, 3], [0, 2, 0], [0, 3, 2],
        ....:        [1, 0, 3], [1, 1, 1], [1, 2, 2], [1, 3, 0],
        ....:        [2, 0, 0], [2, 1, 2], [2, 2, 1], [2, 3, 3],
        ....:        [3, 0, 2], [3, 1, 0], [3, 2, 3], [3, 3, 1]]
        sage: oa1 = [[0, 0, 1], [0, 1, 0], [0, 2, 3], [0, 3, 2],
        ....:        [1, 0, 3], [1, 1, 2], [1, 2, 0], [1, 3, 1],
        ....:        [2, 0, 0], [2, 1, 1], [2, 2, 2], [2, 3, 3],
        ....:        [3, 0, 2], [3, 1, 3], [3, 2, 1], [3, 3, 0]]
        sage: g0 = graphs.OrthogonalArrayBlockGraph(3,4,oa0)
        sage: g1 = graphs.OrthogonalArrayBlockGraph(3,4,oa1)
        sage: g0.is_isomorphic(g1)
        False

    But nevertheless isospectral::

        sage: g0.spectrum()
        [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3]
        sage: g1.spectrum()
        [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3]

    Note that the graph ``g0`` is actually isomorphic to the affine polar graph
    `VO^+(4,2)`::

        sage: graphs.AffineOrthogonalPolarGraph(4,2,'+').is_isomorphic(g0)
        True

    TESTS::

        sage: G = graphs.OrthogonalArrayBlockGraph(4,6)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build an OA(4,6)!
        sage: G = graphs.OrthogonalArrayBlockGraph(8,2)
        Traceback (most recent call last):
        ...
        ValueError: There is no OA(8,2). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) !
    """
    if n > 1 and k >= n + 2:
        raise ValueError(
            "There is no OA({},{}). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) !"
            .format(k, n))

    from itertools import combinations

    if OA is None:
        from sage.combinat.designs.orthogonal_arrays import orthogonal_array
        OA = orthogonal_array(k, n)
    else:
        assert len(OA) == n**2
        assert n == 0 or k == len(OA[0])

    OA = map(tuple, OA)

    d = [[[] for j in range(n)] for i in range(k)]
    for R in OA:
        for i, x in enumerate(R):
            d[i][x].append(R)

    g = Graph()
    for l in d:
        for ll in l:
            g.add_edges(combinations(ll, 2))

    g.name("OA({},{})".format(k, n))

    return g
Ejemplo n.º 4
0
def OrthogonalArrayBlockGraph(k,n,OA=None):
    r"""
    Returns the graph of an `OA(k,n)`.

    The intersection graph of the blocks of a transversal design with parameters
    `(k,n)`, or `TD(k,n)` for short, is a strongly regular graph (unless it is a
    complete graph). Its parameters `(v,k',\lambda,\mu)` are determined by the
    parameters `k,n` via:

    .. MATH::

        v=n^2, k'=k(n-1), \lambda=(k-1)(k-2)+n-2, \mu=k(k-1)

    As transversal designs and orthogonal arrays (OA for short) are equivalent
    objects, this graph can also be built from the blocks of an `OA(k,n)`, two
    of them being adjacent if one of their coordinates match.

    For more information on these graphs, see `Andries Brouwer's page
    on Orthogonal Array graphs <www.win.tue.nl/~aeb/graphs/OA.html>`_.

    .. WARNING::

        - Brouwer's website uses the notation `OA(n,k)` instead of `OA(k,n)`

        - For given parameters `k` and `n` there can be many `OA(k,n)` : the
          graphs returned are not uniquely defined by their parameters (see the
          examples below).

        - If the function is called only with the parameter ``k`` and ``n`` the
          results might be different with two versions of Sage, or even worse :
          some could not be available anymore.

    .. SEEALSO::

        :mod:`sage.combinat.designs.orthogonal_arrays`

    INPUT:

    - ``k,n`` (integers)

    - ``OA`` -- An orthogonal array. If set to ``None`` (default) then
      :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` is
      called to compute an `OA(k,n)`.

    EXAMPLES::

        sage: G = graphs.OrthogonalArrayBlockGraph(5,5); G
        OA(5,5): Graph on 25 vertices
        sage: G.is_strongly_regular(parameters=True)
        (25, 20, 15, 20)
        sage: G = graphs.OrthogonalArrayBlockGraph(4,10); G
        OA(4,10): Graph on 100 vertices
        sage: G.is_strongly_regular(parameters=True)
        (100, 36, 14, 12)

    Two graphs built from different orthogonal arrays are also different::

        sage: k=4;n=10
        sage: OAa = designs.orthogonal_arrays.build(k,n)
        sage: OAb = [[(x+1)%n for x in R] for R in OAa]
        sage: set(map(tuple,OAa)) == set(map(tuple,OAb))
        False
        sage: Ga = graphs.OrthogonalArrayBlockGraph(k,n,OAa)
        sage: Gb = graphs.OrthogonalArrayBlockGraph(k,n,OAb)
        sage: Ga == Gb
        False

    As ``OAb`` was obtained from ``OAa`` by a relabelling the two graphs are
    isomorphic::

        sage: Ga.is_isomorphic(Gb)
        True

    But there are examples of `OA(k,n)` for which the resulting graphs are not
    isomorphic::

        sage: oa0 = [[0, 0, 1], [0, 1, 3], [0, 2, 0], [0, 3, 2],
        ....:        [1, 0, 3], [1, 1, 1], [1, 2, 2], [1, 3, 0],
        ....:        [2, 0, 0], [2, 1, 2], [2, 2, 1], [2, 3, 3],
        ....:        [3, 0, 2], [3, 1, 0], [3, 2, 3], [3, 3, 1]]
        sage: oa1 = [[0, 0, 1], [0, 1, 0], [0, 2, 3], [0, 3, 2],
        ....:        [1, 0, 3], [1, 1, 2], [1, 2, 0], [1, 3, 1],
        ....:        [2, 0, 0], [2, 1, 1], [2, 2, 2], [2, 3, 3],
        ....:        [3, 0, 2], [3, 1, 3], [3, 2, 1], [3, 3, 0]]
        sage: g0 = graphs.OrthogonalArrayBlockGraph(3,4,oa0)
        sage: g1 = graphs.OrthogonalArrayBlockGraph(3,4,oa1)
        sage: g0.is_isomorphic(g1)
        False

    But nevertheless isospectral::

        sage: g0.spectrum()
        [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3]
        sage: g1.spectrum()
        [9, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3]

    Note that the graph ``g0`` is actually isomorphic to the affine polar graph
    `VO^+(4,2)`::

        sage: graphs.AffineOrthogonalPolarGraph(4,2,'+').is_isomorphic(g0)
        True

    TESTS::

        sage: G = graphs.OrthogonalArrayBlockGraph(4,6)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build an OA(4,6)!
        sage: G = graphs.OrthogonalArrayBlockGraph(8,2)
        Traceback (most recent call last):
        ...
        ValueError: There is no OA(8,2). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) !
    """
    if n>1 and k>=n+2:
        raise ValueError("There is no OA({},{}). Beware, Brouwer's website uses OA(n,k) instead of OA(k,n) !".format(k,n))

    from itertools import combinations

    if OA is None:
        from sage.combinat.designs.orthogonal_arrays import orthogonal_array
        OA = orthogonal_array(k,n)
    else:
        assert len(OA) == n**2
        assert n == 0 or k == len(OA[0])

    OA = map(tuple,OA)

    d = [[[] for j in range(n)] for i in range(k)]
    for R in OA:
        for i,x in enumerate(R):
            d[i][x].append(R)

    g = Graph()
    for l in d:
        for ll in l:
            g.add_edges(combinations(ll,2))

    g.name("OA({},{})".format(k,n))

    return g
Ejemplo n.º 5
0
def mutually_orthogonal_latin_squares(k,n, partitions = False, check = True, existence=False):
    r"""
    Return `k` Mutually Orthogonal `n\times n` Latin Squares (MOLS).

    For more information on Mutually Orthogonal Latin Squares, see
    :mod:`~sage.combinat.designs.latin_squares`.

    INPUT:

    - ``k`` (integer) -- number of MOLS. If ``k=None`` it is set to the largest
      value available.

    - ``n`` (integer) -- size of the latin square.

    - ``partition`` (boolean) -- a Latin Square can be seen as 3 partitions of
      the `n^2` cells of the array into `n` sets of size `n`, respectively :

      * The partition of rows
      * The partition of columns
      * The partition of number (cells numbered with 0, cells numbered with 1,
        ...)

      These partitions have the additional property that any two sets from
      different partitions intersect on exactly one element.

      When ``partition`` is set to ``True``, this function returns a list of `k+2`
      partitions satisfying this intersection property instead of the `k+2` MOLS
      (though the data is exactly the same in both cases).

    - ``existence`` (boolean) -- instead of building the design, return:

        - ``True`` -- meaning that Sage knows how to build the design

        - ``Unknown`` -- meaning that Sage does not know how to build the
          design, but that the design may exist (see :mod:`sage.misc.unknown`).

        - ``False`` -- meaning that the design does not exist.

      .. NOTE::

          When ``k=None`` and ``existence=True`` the function returns an
          integer, i.e. the largest `k` such that we can build a `k` MOLS of
          order `n`.

    - ``check`` -- (boolean) Whether to check that output is correct before
      returning it. As this is expected to be useless (but we are cautious
      guys), you may want to disable it whenever you want speed. Set to
      ``True`` by default.

    EXAMPLES::

        sage: designs.mutually_orthogonal_latin_squares(4,5)
        [
        [0 2 4 1 3]  [0 3 1 4 2]  [0 4 3 2 1]  [0 1 2 3 4]
        [4 1 3 0 2]  [3 1 4 2 0]  [2 1 0 4 3]  [4 0 1 2 3]
        [3 0 2 4 1]  [1 4 2 0 3]  [4 3 2 1 0]  [3 4 0 1 2]
        [2 4 1 3 0]  [4 2 0 3 1]  [1 0 4 3 2]  [2 3 4 0 1]
        [1 3 0 2 4], [2 0 3 1 4], [3 2 1 0 4], [1 2 3 4 0]
        ]

        sage: designs.mutually_orthogonal_latin_squares(3,7)
        [
        [0 2 4 6 1 3 5]  [0 3 6 2 5 1 4]  [0 4 1 5 2 6 3]
        [6 1 3 5 0 2 4]  [5 1 4 0 3 6 2]  [4 1 5 2 6 3 0]
        [5 0 2 4 6 1 3]  [3 6 2 5 1 4 0]  [1 5 2 6 3 0 4]
        [4 6 1 3 5 0 2]  [1 4 0 3 6 2 5]  [5 2 6 3 0 4 1]
        [3 5 0 2 4 6 1]  [6 2 5 1 4 0 3]  [2 6 3 0 4 1 5]
        [2 4 6 1 3 5 0]  [4 0 3 6 2 5 1]  [6 3 0 4 1 5 2]
        [1 3 5 0 2 4 6], [2 5 1 4 0 3 6], [3 0 4 1 5 2 6]
        ]

        sage: designs.mutually_orthogonal_latin_squares(2,5,partitions=True)
        [[[0, 1, 2, 3, 4],
          [5, 6, 7, 8, 9],
          [10, 11, 12, 13, 14],
          [15, 16, 17, 18, 19],
          [20, 21, 22, 23, 24]],
         [[0, 5, 10, 15, 20],
          [1, 6, 11, 16, 21],
          [2, 7, 12, 17, 22],
          [3, 8, 13, 18, 23],
          [4, 9, 14, 19, 24]],
         [[0, 8, 11, 19, 22],
          [3, 6, 14, 17, 20],
          [1, 9, 12, 15, 23],
          [4, 7, 10, 18, 21],
          [2, 5, 13, 16, 24]],
         [[0, 9, 13, 17, 21],
          [2, 6, 10, 19, 23],
          [4, 8, 12, 16, 20],
          [1, 5, 14, 18, 22],
          [3, 7, 11, 15, 24]]]

    What is the maximum number of MOLS of size 8 that Sage knows how to build?::

        sage: designs.orthogonal_arrays.largest_available_k(8)-2
        7

    If you only want to know if Sage is able to build a given set of
    MOLS, query the ``orthogonal_arrays.*`` functions::

        sage: designs.orthogonal_arrays.is_available(5+2, 5) # 5 MOLS of order 5
        False
        sage: designs.orthogonal_arrays.is_available(4+2,6) # 4 MOLS of order 6
        False

    Sage, however, is not able to prove that the second MOLS do not exist::

        sage: designs.orthogonal_arrays.exists(4+2,6) # 4 MOLS of order 6
        Unknown

    If you ask for such a MOLS then you will respectively get an informative
    ``EmptySetError`` or ``NotImplementedError``::

        sage: designs.mutually_orthogonal_latin_squares(5, 5)
        Traceback (most recent call last):
        ...
        EmptySetError: There exist at most n-1 MOLS of size n if n>=2.
        sage: designs.mutually_orthogonal_latin_squares(4,6)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build 4 MOLS of order 6

    TESTS:

    The special case `n=1`::

        sage: designs.mutually_orthogonal_latin_squares(3, 1)
        [[0], [0], [0]]
        sage: designs.mutually_orthogonal_latin_squares(None, 1)
        Traceback (most recent call last):
        ...
        ValueError: there are no bound on k when 0<=n<=1
        sage: designs.mutually_orthogonal_latin_squares(2,10)
        [
        [1 8 9 0 2 4 6 3 5 7]  [1 7 6 5 0 9 8 2 3 4]
        [7 2 8 9 0 3 5 4 6 1]  [8 2 1 7 6 0 9 3 4 5]
        [6 1 3 8 9 0 4 5 7 2]  [9 8 3 2 1 7 0 4 5 6]
        [5 7 2 4 8 9 0 6 1 3]  [0 9 8 4 3 2 1 5 6 7]
        [0 6 1 3 5 8 9 7 2 4]  [2 0 9 8 5 4 3 6 7 1]
        [9 0 7 2 4 6 8 1 3 5]  [4 3 0 9 8 6 5 7 1 2]
        [8 9 0 1 3 5 7 2 4 6]  [6 5 4 0 9 8 7 1 2 3]
        [2 3 4 5 6 7 1 8 9 0]  [3 4 5 6 7 1 2 8 0 9]
        [3 4 5 6 7 1 2 0 8 9]  [5 6 7 1 2 3 4 0 9 8]
        [4 5 6 7 1 2 3 9 0 8], [7 1 2 3 4 5 6 9 8 0]
        ]
    """
    from sage.combinat.designs.orthogonal_arrays import orthogonal_array
    from sage.matrix.constructor import Matrix
    from sage.arith.all import factor
    from .database import MOLS_constructions

    # Is k is None we find the largest available
    if k is None:
        from sage.misc.superseded import deprecation
        deprecation(17034,"please use designs.orthogonal_arrays.largest_available_k instead of k=None")
        if n == 0 or n == 1:
            if existence:
                from sage.rings.infinity import Infinity
                return Infinity
            raise ValueError("there are no bound on k when 0<=n<=1")

        k = orthogonal_array(None,n,existence=True) - 2
        if existence:
            return k

    if existence:
        from sage.misc.superseded import deprecation
        deprecation(17034,"please use designs.orthogonal_arrays.is_available/exists instead of existence=True")

    if n == 1:
        if existence:
            return True
        matrices = [Matrix([[0]])]*k

    elif k >= n:
        if existence:
            return False
        raise EmptySetError("There exist at most n-1 MOLS of size n if n>=2.")

    elif n in MOLS_constructions and k <= MOLS_constructions[n][0]:
        if existence:
            return True
        _, construction = MOLS_constructions[n]

        matrices = construction()[:k]

    elif orthogonal_array(k+2,n,existence=True) is not Unknown:
        # Forwarding non-existence results
        if orthogonal_array(k+2,n,existence=True):
            if existence:
                return True
        else:
            if existence:
                return False
            raise EmptySetError("There does not exist {} MOLS of order {}!".format(k,n))

        # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..."
        OA = sorted(orthogonal_array(k+2,n,check=False))

        # We first define matrices as lists of n^2 values
        matrices = [[] for _ in range(k)]
        for L in OA:
            for i in range(2,k+2):
                matrices[i-2].append(L[i])

        # The real matrices
        matrices = [[M[i*n:(i+1)*n] for i in range(n)] for M in matrices]
        matrices = [Matrix(M) for M in matrices]

    else:
        if existence:
            return Unknown
        raise NotImplementedError("I don't know how to build {} MOLS of order {}".format(k,n))

    if check:
        assert are_mutually_orthogonal_latin_squares(matrices)

    # partitions have been requested but have not been computed yet
    if partitions is True:
        partitions = [[[i*n+j for j in range(n)] for i in range(n)],
                      [[j*n+i for j in range(n)] for i in range(n)]]
        for m in matrices:
            partition = [[] for i in range(n)]
            for i in range(n):
                for j in range(n):
                    partition[m[i,j]].append(i*n+j)
            partitions.append(partition)

    if partitions:
        return partitions
    else:
        return matrices
Ejemplo n.º 6
0
def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, existence=False, who_asked=tuple()):
    r"""
    Returns `k` Mutually Orthogonal `n\times n` Latin Squares (MOLS).

    For more information on Latin Squares and MOLS, see
    :mod:`~sage.combinat.designs.latin_squares` or the :wikipedia:`Latin_square`,
    or even the
    :wikipedia:`Wikipedia entry on MOLS <Graeco-Latin_square#Mutually_orthogonal_Latin_squares>`.

    INPUT:

    - ``n`` (integer) -- size of the latin square.

    - ``k`` (integer) -- number of MOLS. If ``k=None`` it is set to the largest
      value available.

    - ``partition`` (boolean) -- a Latin Square can be seen as 3 partitions of
      the `n^2` cells of the array into `n` sets of size `n`, respectively :

      * The partition of rows
      * The partition of columns
      * The partition of number (cells numbered with 0, cells numbered with 1,
        ...)

      These partitions have the additional property that any two sets from
      different partitions intersect on exactly one element.

      When ``partition`` is set to ``True``, this function returns a list of `k+2`
      partitions satisfying this intersection property instead of the `k+2` MOLS
      (though the data is exactly the same in both cases).

    - ``existence`` (boolean) -- instead of building the design, returns:

        - ``True`` -- meaning that Sage knows how to build the design

        - ``Unknown`` -- meaning that Sage does not know how to build the
          design, but that the design may exist (see :mod:`sage.misc.unknown`).

        - ``False`` -- meaning that the design does not exist.

      .. NOTE::

          When ``k=None`` and ``existence=True`` the function returns an
          integer, i.e. the largest `k` such that we can build a `k` MOLS of
          order `n`.

    - ``check`` -- (boolean) Whether to check that output is correct before
      returning it. As this is expected to be useless (but we are cautious
      guys), you may want to disable it whenever you want speed. Set to
      ``True`` by default.

    - ``who_asked`` (internal use only) -- because of the equivalence between
      OA/TD/MOLS, each of the three constructors calls the others. We must keep
      track of who calls who in order to avoid infinite loops. ``who_asked`` is
      the tuple of the other functions that were called before this one.

    EXAMPLES::

        sage: designs.mutually_orthogonal_latin_squares(5,4)
        [
        [0 2 4 1 3]  [0 3 1 4 2]  [0 4 3 2 1]  [0 1 2 3 4]
        [4 1 3 0 2]  [3 1 4 2 0]  [2 1 0 4 3]  [4 0 1 2 3]
        [3 0 2 4 1]  [1 4 2 0 3]  [4 3 2 1 0]  [3 4 0 1 2]
        [2 4 1 3 0]  [4 2 0 3 1]  [1 0 4 3 2]  [2 3 4 0 1]
        [1 3 0 2 4], [2 0 3 1 4], [3 2 1 0 4], [1 2 3 4 0]
        ]

        sage: designs.mutually_orthogonal_latin_squares(7,3)
        [
        [0 2 4 6 1 3 5]  [0 3 6 2 5 1 4]  [0 4 1 5 2 6 3]
        [6 1 3 5 0 2 4]  [5 1 4 0 3 6 2]  [4 1 5 2 6 3 0]
        [5 0 2 4 6 1 3]  [3 6 2 5 1 4 0]  [1 5 2 6 3 0 4]
        [4 6 1 3 5 0 2]  [1 4 0 3 6 2 5]  [5 2 6 3 0 4 1]
        [3 5 0 2 4 6 1]  [6 2 5 1 4 0 3]  [2 6 3 0 4 1 5]
        [2 4 6 1 3 5 0]  [4 0 3 6 2 5 1]  [6 3 0 4 1 5 2]
        [1 3 5 0 2 4 6], [2 5 1 4 0 3 6], [3 0 4 1 5 2 6]
        ]

        sage: designs.mutually_orthogonal_latin_squares(5,2,partitions=True)
        [[[0, 1, 2, 3, 4],
          [5, 6, 7, 8, 9],
          [10, 11, 12, 13, 14],
          [15, 16, 17, 18, 19],
          [20, 21, 22, 23, 24]],
         [[0, 5, 10, 15, 20],
          [1, 6, 11, 16, 21],
          [2, 7, 12, 17, 22],
          [3, 8, 13, 18, 23],
          [4, 9, 14, 19, 24]],
         [[0, 8, 11, 19, 22],
          [3, 6, 14, 17, 20],
          [1, 9, 12, 15, 23],
          [4, 7, 10, 18, 21],
          [2, 5, 13, 16, 24]],
         [[0, 9, 13, 17, 21],
          [2, 6, 10, 19, 23],
          [4, 8, 12, 16, 20],
          [1, 5, 14, 18, 22],
          [3, 7, 11, 15, 24]]]

    What is the maximum number of MOLS of size 8 that Sage knows how to build?::

        sage: designs.mutually_orthogonal_latin_squares(8,None,existence=True)
        7

    If you only want to know if Sage is able to build a given set of MOLS, just
    set the argument ``existence`` to ``True``::

        sage: designs.mutually_orthogonal_latin_squares(5, 5, existence=True)
        False
        sage: designs.mutually_orthogonal_latin_squares(6, 4, existence=True)
        Unknown

    If you ask for such a MOLS then you will respecively get an informative
    ``EmptySetError`` or ``NotImplementedError``::

        sage: designs.mutually_orthogonal_latin_squares(5, 5)
        Traceback (most recent call last):
        ...
        EmptySetError: There exist at most n-1 MOLS of size n if n>=2.
        sage: designs.mutually_orthogonal_latin_squares(6, 4)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build these MOLS!

    TESTS:

    The special case `n=1`::

        sage: designs.mutually_orthogonal_latin_squares(1, 3)
        [[0], [0], [0]]
        sage: designs.mutually_orthogonal_latin_squares(1, None, existence=True)
        +Infinity
        sage: designs.mutually_orthogonal_latin_squares(1, None)
        Traceback (most recent call last):
        ...
        ValueError: there are no bound on k when n=1.
        sage: designs.mutually_orthogonal_latin_squares(10,2,existence=True)
        True
        sage: designs.mutually_orthogonal_latin_squares(10,2)
        [
        [1 8 9 0 2 4 6 3 5 7]  [1 7 6 5 0 9 8 2 3 4]
        [7 2 8 9 0 3 5 4 6 1]  [8 2 1 7 6 0 9 3 4 5]
        [6 1 3 8 9 0 4 5 7 2]  [9 8 3 2 1 7 0 4 5 6]
        [5 7 2 4 8 9 0 6 1 3]  [0 9 8 4 3 2 1 5 6 7]
        [0 6 1 3 5 8 9 7 2 4]  [2 0 9 8 5 4 3 6 7 1]
        [9 0 7 2 4 6 8 1 3 5]  [4 3 0 9 8 6 5 7 1 2]
        [8 9 0 1 3 5 7 2 4 6]  [6 5 4 0 9 8 7 1 2 3]
        [2 3 4 5 6 7 1 8 9 0]  [3 4 5 6 7 1 2 8 0 9]
        [3 4 5 6 7 1 2 0 8 9]  [5 6 7 1 2 3 4 0 9 8]
        [4 5 6 7 1 2 3 9 0 8], [7 1 2 3 4 5 6 9 8 0]
        ]
    """
    from sage.combinat.designs.orthogonal_arrays import orthogonal_array
    from sage.matrix.constructor import Matrix
    from sage.rings.arith import factor
    from database import MOLS_constructions

    # Is k is None we find the largest available
    if k is None:
        if n == 1:
            if existence:
                from sage.rings.infinity import Infinity
                return Infinity
            raise ValueError("there are no bound on k when n=1.")

        k = orthogonal_array(None,n,existence=True) - 2
        if existence:
            return k

    if n == 1:
        if existence:
            return True
        matrices = [Matrix([[0]])]*k

    elif k >= n:
        if existence:
            return False
        raise EmptySetError("There exist at most n-1 MOLS of size n if n>=2.")

    elif n in MOLS_constructions and k <= MOLS_constructions[n][0]:
        if existence:
            return True
        _, construction = MOLS_constructions[n]

        matrices = construction()[:k]

    elif (orthogonal_array not in who_asked and
        orthogonal_array(k+2,n,existence=True,who_asked = who_asked+(mutually_orthogonal_latin_squares,)) is not Unknown):

        # Forwarding non-existence results
        if orthogonal_array(k+2,n,existence=True,who_asked = who_asked+(mutually_orthogonal_latin_squares,)):
            if existence:
                return True
        else:
            if existence:
                return False
            raise EmptySetError("These MOLS do not exist!")

        OA = orthogonal_array(k+2,n,check=False, who_asked = who_asked+(mutually_orthogonal_latin_squares,))
        OA.sort() # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..."

        # We first define matrices as lists of n^2 values
        matrices = [[] for _ in range(k)]
        for L in OA:
            for i in range(2,k+2):
                matrices[i-2].append(L[i])

        # The real matrices
        matrices = [[M[i*n:(i+1)*n] for i in range(n)] for M in matrices]
        matrices = [Matrix(M) for M in matrices]

    else:
        if existence:
            return Unknown
        raise NotImplementedError("I don't know how to build these MOLS!")

    if check:
        assert are_mutually_orthogonal_latin_squares(matrices)

    # partitions have been requested but have not been computed yet
    if partitions is True:
        partitions = [[[i*n+j for j in range(n)] for i in range(n)],
                      [[j*n+i for j in range(n)] for i in range(n)]]
        for m in matrices:
            partition = [[] for i in range(n)]
            for i in range(n):
                for j in range(n):
                    partition[m[i,j]].append(i*n+j)
            partitions.append(partition)

    if partitions:
        return partitions
    else:
        return matrices
def mutually_orthogonal_latin_squares(n,
                                      k,
                                      partitions=False,
                                      check=True,
                                      existence=False,
                                      who_asked=tuple()):
    r"""
    Returns `k` Mutually Orthogonal `n\times n` Latin Squares (MOLS).

    For more information on Latin Squares and MOLS, see
    :mod:`~sage.combinat.designs.latin_squares` or the :wikipedia:`Latin_square`,
    or even the
    :wikipedia:`Wikipedia entry on MOLS <Graeco-Latin_square#Mutually_orthogonal_Latin_squares>`.

    INPUT:

    - ``n`` (integer) -- size of the latin square.

    - ``k`` (integer) -- number of MOLS. If ``k=None`` it is set to the largest
      value available.

    - ``partition`` (boolean) -- a Latin Square can be seen as 3 partitions of
      the `n^2` cells of the array into `n` sets of size `n`, respectively :

      * The partition of rows
      * The partition of columns
      * The partition of number (cells numbered with 0, cells numbered with 1,
        ...)

      These partitions have the additional property that any two sets from
      different partitions intersect on exactly one element.

      When ``partition`` is set to ``True``, this function returns a list of `k+2`
      partitions satisfying this intersection property instead of the `k+2` MOLS
      (though the data is exactly the same in both cases).

    - ``existence`` (boolean) -- instead of building the design, returns:

        - ``True`` -- meaning that Sage knows how to build the design

        - ``Unknown`` -- meaning that Sage does not know how to build the
          design, but that the design may exist (see :mod:`sage.misc.unknown`).

        - ``False`` -- meaning that the design does not exist.

      .. NOTE::

          When ``k=None`` and ``existence=True`` the function returns an
          integer, i.e. the largest `k` such that we can build a `k` MOLS of
          order `n`.

    - ``check`` -- (boolean) Whether to check that output is correct before
      returning it. As this is expected to be useless (but we are cautious
      guys), you may want to disable it whenever you want speed. Set to
      ``True`` by default.

    - ``who_asked`` (internal use only) -- because of the equivalence between
      OA/TD/MOLS, each of the three constructors calls the others. We must keep
      track of who calls who in order to avoid infinite loops. ``who_asked`` is
      the tuple of the other functions that were called before this one.

    EXAMPLES::

        sage: designs.mutually_orthogonal_latin_squares(5,4)
        [
        [0 2 4 1 3]  [0 3 1 4 2]  [0 4 3 2 1]  [0 1 2 3 4]
        [4 1 3 0 2]  [3 1 4 2 0]  [2 1 0 4 3]  [4 0 1 2 3]
        [3 0 2 4 1]  [1 4 2 0 3]  [4 3 2 1 0]  [3 4 0 1 2]
        [2 4 1 3 0]  [4 2 0 3 1]  [1 0 4 3 2]  [2 3 4 0 1]
        [1 3 0 2 4], [2 0 3 1 4], [3 2 1 0 4], [1 2 3 4 0]
        ]

        sage: designs.mutually_orthogonal_latin_squares(7,3)
        [
        [0 2 4 6 1 3 5]  [0 3 6 2 5 1 4]  [0 4 1 5 2 6 3]
        [6 1 3 5 0 2 4]  [5 1 4 0 3 6 2]  [4 1 5 2 6 3 0]
        [5 0 2 4 6 1 3]  [3 6 2 5 1 4 0]  [1 5 2 6 3 0 4]
        [4 6 1 3 5 0 2]  [1 4 0 3 6 2 5]  [5 2 6 3 0 4 1]
        [3 5 0 2 4 6 1]  [6 2 5 1 4 0 3]  [2 6 3 0 4 1 5]
        [2 4 6 1 3 5 0]  [4 0 3 6 2 5 1]  [6 3 0 4 1 5 2]
        [1 3 5 0 2 4 6], [2 5 1 4 0 3 6], [3 0 4 1 5 2 6]
        ]

        sage: designs.mutually_orthogonal_latin_squares(5,2,partitions=True)
        [[[0, 1, 2, 3, 4],
          [5, 6, 7, 8, 9],
          [10, 11, 12, 13, 14],
          [15, 16, 17, 18, 19],
          [20, 21, 22, 23, 24]],
         [[0, 5, 10, 15, 20],
          [1, 6, 11, 16, 21],
          [2, 7, 12, 17, 22],
          [3, 8, 13, 18, 23],
          [4, 9, 14, 19, 24]],
         [[0, 8, 11, 19, 22],
          [3, 6, 14, 17, 20],
          [1, 9, 12, 15, 23],
          [4, 7, 10, 18, 21],
          [2, 5, 13, 16, 24]],
         [[0, 9, 13, 17, 21],
          [2, 6, 10, 19, 23],
          [4, 8, 12, 16, 20],
          [1, 5, 14, 18, 22],
          [3, 7, 11, 15, 24]]]

    What is the maximum number of MOLS of size 8 that Sage knows how to build?::

        sage: designs.mutually_orthogonal_latin_squares(8,None,existence=True)
        7

    If you only want to know if Sage is able to build a given set of MOLS, just
    set the argument ``existence`` to ``True``::

        sage: designs.mutually_orthogonal_latin_squares(5, 5, existence=True)
        False
        sage: designs.mutually_orthogonal_latin_squares(6, 4, existence=True)
        Unknown

    If you ask for such a MOLS then you will respecively get an informative
    ``EmptySetError`` or ``NotImplementedError``::

        sage: designs.mutually_orthogonal_latin_squares(5, 5)
        Traceback (most recent call last):
        ...
        EmptySetError: There exist at most n-1 MOLS of size n if n>=2.
        sage: designs.mutually_orthogonal_latin_squares(6, 4)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build these MOLS!

    TESTS:

    The special case `n=1`::

        sage: designs.mutually_orthogonal_latin_squares(1, 3)
        [[0], [0], [0]]
        sage: designs.mutually_orthogonal_latin_squares(1, None, existence=True)
        +Infinity
        sage: designs.mutually_orthogonal_latin_squares(1, None)
        Traceback (most recent call last):
        ...
        ValueError: there are no bound on k when n=1.
        sage: designs.mutually_orthogonal_latin_squares(10,2,existence=True)
        True
        sage: designs.mutually_orthogonal_latin_squares(10,2)
        [
        [1 8 9 0 2 4 6 3 5 7]  [1 7 6 5 0 9 8 2 3 4]
        [7 2 8 9 0 3 5 4 6 1]  [8 2 1 7 6 0 9 3 4 5]
        [6 1 3 8 9 0 4 5 7 2]  [9 8 3 2 1 7 0 4 5 6]
        [5 7 2 4 8 9 0 6 1 3]  [0 9 8 4 3 2 1 5 6 7]
        [0 6 1 3 5 8 9 7 2 4]  [2 0 9 8 5 4 3 6 7 1]
        [9 0 7 2 4 6 8 1 3 5]  [4 3 0 9 8 6 5 7 1 2]
        [8 9 0 1 3 5 7 2 4 6]  [6 5 4 0 9 8 7 1 2 3]
        [2 3 4 5 6 7 1 8 9 0]  [3 4 5 6 7 1 2 8 0 9]
        [3 4 5 6 7 1 2 0 8 9]  [5 6 7 1 2 3 4 0 9 8]
        [4 5 6 7 1 2 3 9 0 8], [7 1 2 3 4 5 6 9 8 0]
        ]
    """
    from sage.combinat.designs.orthogonal_arrays import orthogonal_array
    from sage.matrix.constructor import Matrix
    from sage.rings.arith import factor
    from database import MOLS_constructions

    # Is k is None we find the largest available
    if k is None:
        if n == 1:
            if existence:
                from sage.rings.infinity import Infinity
                return Infinity
            raise ValueError("there are no bound on k when n=1.")

        k = orthogonal_array(None, n, existence=True) - 2
        if existence:
            return k

    if n == 1:
        if existence:
            return True
        matrices = [Matrix([[0]])] * k

    elif k >= n:
        if existence:
            return False
        raise EmptySetError("There exist at most n-1 MOLS of size n if n>=2.")

    elif n in MOLS_constructions and k <= MOLS_constructions[n][0]:
        if existence:
            return True
        _, construction = MOLS_constructions[n]

        matrices = construction()[:k]

    elif (orthogonal_array not in who_asked and orthogonal_array(
            k + 2,
            n,
            existence=True,
            who_asked=who_asked +
        (mutually_orthogonal_latin_squares, )) is not Unknown):

        # Forwarding non-existence results
        if orthogonal_array(k + 2,
                            n,
                            existence=True,
                            who_asked=who_asked +
                            (mutually_orthogonal_latin_squares, )):
            if existence:
                return True
        else:
            if existence:
                return False
            raise EmptySetError("These MOLS do not exist!")

        OA = orthogonal_array(k + 2,
                              n,
                              check=False,
                              who_asked=who_asked +
                              (mutually_orthogonal_latin_squares, ))
        OA.sort(
        )  # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..."

        # We first define matrices as lists of n^2 values
        matrices = [[] for _ in range(k)]
        for L in OA:
            for i in range(2, k + 2):
                matrices[i - 2].append(L[i])

        # The real matrices
        matrices = [[M[i * n:(i + 1) * n] for i in range(n)] for M in matrices]
        matrices = [Matrix(M) for M in matrices]

    else:
        if existence:
            return Unknown
        raise NotImplementedError("I don't know how to build these MOLS!")

    if check:
        assert are_mutually_orthogonal_latin_squares(matrices)

    # partitions have been requested but have not been computed yet
    if partitions is True:
        partitions = [[[i * n + j for j in range(n)] for i in range(n)],
                      [[j * n + i for j in range(n)] for i in range(n)]]
        for m in matrices:
            partition = [[] for i in range(n)]
            for i in range(n):
                for j in range(n):
                    partition[m[i, j]].append(i * n + j)
            partitions.append(partition)

    if partitions:
        return partitions
    else:
        return matrices