def is_transversal_design(B, k, n, verbose=False):
    r"""
    Check that a given set of blocks ``B`` is a transversal design.

    See :func:`~sage.combinat.designs.orthogonal_arrays.transversal_design`
    for a definition.

    INPUT:

    - ``B`` -- the list of blocks

    - ``k, n`` -- integers

    - ``verbose`` (boolean) -- whether to display information about what is
      going wrong.

    .. NOTE::

        The tranversal design must have `\{0, \ldots, kn-1\}` as a ground set,
        partitioned as `k` sets of size `n`: `\{0, \ldots, k-1\} \sqcup
        \{k, \ldots, 2k-1\} \sqcup \cdots \sqcup \{k(n-1), \ldots, kn-1\}`.

    EXAMPLES::

        sage: TD = designs.transversal_design(5, 5, check=True) # indirect doctest
        sage: from sage.combinat.designs.orthogonal_arrays import is_transversal_design
        sage: is_transversal_design(TD, 5, 5)
        True
        sage: is_transversal_design(TD, 4, 4)
        False
    """
    return is_orthogonal_array([[x % n for x in R] for R in B],
                               k,
                               n,
                               verbose=verbose)
Exemple #2
0
def is_transversal_design(B,k,n, verbose=False):
    r"""
    Check that a given set of blocks ``B`` is a transversal design.

    See :func:`~sage.combinat.designs.orthogonal_arrays.transversal_design`
    for a definition.

    INPUT:

    - ``B`` -- the list of blocks

    - ``k, n`` -- integers

    - ``verbose`` (boolean) -- whether to display information about what is
      going wrong.

    .. NOTE::

        The tranversal design must have `\{0, \ldots, kn-1\}` as a ground set,
        partitioned as `k` sets of size `n`: `\{0, \ldots, k-1\} \sqcup
        \{k, \ldots, 2k-1\} \sqcup \cdots \sqcup \{k(n-1), \ldots, kn-1\}`.

    EXAMPLES::

        sage: TD = designs.transversal_design(5, 5, check=True) # indirect doctest
        sage: from sage.combinat.designs.orthogonal_arrays import is_transversal_design
        sage: is_transversal_design(TD, 5, 5)
        True
        sage: is_transversal_design(TD, 4, 4)
        False
    """
    return is_orthogonal_array([[x%n for x in R] for R in B],k,n,verbose=verbose)
Exemple #3
0
def construction_3_3(k,n,m,i):
    r"""
    Return an `OA(k,nm+i)`.

    This is Wilson's construction with `i` truncated columns of size 1 and such
    that a block `B_0` of the incomplete OA intersects all truncated columns. As
    a consequence, all other blocks intersect only `0` or `1` of the last `i`
    columns. This allow to consider the block `B_0` only up to its first `k`
    coordinates and then use a `OA(k,i)` instead of a `OA(k,m+i) - i.OA(k,1)`.

    This is construction 3.3 from [AC07]_.

    INPUT:

    - ``k,n,m,i`` (integers) such that the following designs are available :
      `OA(k,n),OA(k,m),OA(k,m+1),OA(k,r)`.

    .. SEEALSO::

        :func:`find_construction_3_3`

    EXAMPLES::

        sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_3
        sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_3_3
        sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
        sage: k=11;n=177
        sage: is_orthogonal_array(construction_3_3(*find_construction_3_3(k,n)[1]),k,n,2)
        True
    """
    from orthogonal_arrays import wilson_construction, OA_relabel, incomplete_orthogonal_array
    # Builds an OA(k+i,n) containing a block [0]*(k+i)
    OA = incomplete_orthogonal_array(k+i,n,(1,))
    OA = [[(x+1)%n for x in B] for B in OA]

    # Truncated version
    OA = [B[:k]+[0 if x == 0 else None for x in B[k:]] for B in OA]

    OA = wilson_construction(OA,k,n,m,i,[1]*i,check=False)[:-i]
    matrix = [range(m)+range(n*m,n*m+i)]*k
    OA.extend(OA_relabel(orthogonal_array(k,m+i),k,m+i,matrix=matrix))
    assert is_orthogonal_array(OA,k,n*m+i)
    return OA
Exemple #4
0
def construction_3_6(k,n,m,i):
    r"""
    Return a `OA(k,nm+i)`

    This is Wilson's construction with `r` columns of order `1`, in which each
    block intersects at most two truncated columns. Such a design exists when
    `n` is a prime power and is returned by :func:`OA_and_oval`.

    INPUT:

    - ``k,n,m,i`` (integers) -- `n` must be a prime power. The following designs
      must be available: `OA(k+r,q),OA(k,m),OA(k,m+1),OA(k,m+2)`.

    This is construction 3.6 from [AC07]_.

    .. SEEALSO::

        - :func:`construction_3_6`
        - :func:`OA_and_oval`

    EXAMPLES::

        sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_6
        sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_3_6
        sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
        sage: k=8;n=95
        sage: is_orthogonal_array(construction_3_6(*find_construction_3_6(k,n)[1]),k,n,2)
        True
    """
    from orthogonal_arrays import wilson_construction
    OA = OA_and_oval(n)
    OA = [B[:k+i] for B in OA]
    OA = [B[:k] + [x if x==0 else None for x in B[k:]] for B in OA]
    OA = wilson_construction(OA,k,n,m,i,[1]*i)
    assert is_orthogonal_array(OA,k,n*m+i)
    return OA
Exemple #5
0
def orthogonal_array(k,n,t=2,check=True,existence=False):
    r"""
    Return an orthogonal array of parameters `k,n,t`.

    An orthogonal array of parameters `k,n,t` is a matrix with `k` columns
    filled with integers from `[n]` in such a way that for any `t` columns, each
    of the `n^t` possible rows occurs exactly once. In
    particular, the matrix has `n^t` rows.

    More general definitions sometimes involve a `\lambda` parameter, and we
    assume here that `\lambda=1`.

    For more information on orthogonal arrays, see
    :wikipedia:`Orthogonal_array`.

    INPUT:

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

    - ``n`` -- (integer) number of symbols

    - ``t`` -- (integer; default: 2) -- strength of the array

    - ``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.

    - ``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 `TD(k,n)`.

    OUTPUT:

    The kind of output depends on the input:

    - if ``existence=False`` (the default) then the output is a list of lists
      that represent an orthogonal array with parameters ``k`` and ``n``

    - if ``existence=True`` and ``k`` is an integer, then the function returns a
      troolean: either ``True``, ``Unknown`` or ``False``

    - if ``existence=True`` and ``k=None`` then the output is the largest value
      of ``k`` for which Sage knows how to compute a `TD(k,n)`.

    .. NOTE::

        This method implements theorems from [Stinson2004]_. See the code's
        documentation for details.

    .. SEEALSO::

        When `t=2` an orthogonal array is also a transversal design (see
        :func:`transversal_design`) and a family of mutually orthogonal latin
        squares (see
        :func:`~sage.combinat.designs.latin_squares.mutually_orthogonal_latin_squares`).

    EXAMPLES::

        sage: designs.orthogonal_array(3,2)
        [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]

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

    What is the largest value of `k` for which Sage knows how to compute a
    `OA(k,14,2)`?::

        sage: designs.orthogonal_array(None,14,existence=True)
        6

    If you ask for an orthogonal array that does not exist, then the function
    either raise an ``EmptySetError`` (if it knows that such an orthogonal array
    does not exist) or a ``NotImplementedError``::

        sage: designs.orthogonal_array(4,2)
        Traceback (most recent call last):
        ...
        EmptySetError: No Orthogonal Array exists when k>=n+t except when n<=1
        sage: designs.orthogonal_array(12,20)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build an OA(12,20)!

    Note that these errors correspond respectively to the answers ``False`` and
    ``Unknown`` when the parameter ``existence`` is set to ``True``::

        sage: designs.orthogonal_array(4,2,existence=True)
        False
        sage: designs.orthogonal_array(12,20,existence=True)
        Unknown

    TESTS:

    The special cases `n=0,1`::

        sage: designs.orthogonal_array(3,0)
        []
        sage: designs.orthogonal_array(3,1)
        [[0, 0, 0]]
        sage: designs.orthogonal_array(None,0,existence=True)
        +Infinity
        sage: designs.orthogonal_array(None,1,existence=True)
        +Infinity
        sage: designs.orthogonal_array(None,1)
        Traceback (most recent call last):
        ...
        ValueError: there is no upper bound on k when 0<=n<=1
        sage: designs.orthogonal_array(None,0)
        Traceback (most recent call last):
        ...
        ValueError: there is no upper bound on k when 0<=n<=1
        sage: designs.orthogonal_array(16,0)
        []
        sage: designs.orthogonal_array(16,1)
        [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

    when `t>2` and `k=None`::

        sage: t = 3
        sage: designs.orthogonal_array(None,5,t=t,existence=True) == t
        True
        sage: _ = designs.orthogonal_array(t,5,t)
    """

    from latin_squares import mutually_orthogonal_latin_squares
    from database import OA_constructions, MOLS_constructions
    from block_design import projective_plane, projective_plane_to_OA
    from orthogonal_arrays_recursive import find_recursive_construction

    assert n>=0

    # If k is set to None we find the largest value available
    if k is None:
        if n == 0 or n == 1:
            if existence:
                from sage.rings.infinity import Infinity
                return Infinity
            raise ValueError("there is no upper bound on k when 0<=n<=1")
        elif t == 2 and projective_plane(n,existence=True):
            k = n+1
        else:
            for k in range(t-1,n+2):
                if not orthogonal_array(k+1,n,t=t,existence=True):
                    break
        if existence:
            return k

    if k < t:
        raise ValueError("undefined for k<t")

    if existence and _OA_cache_get(k,n) is not None and t == 2:
        return _OA_cache_get(k,n)

    may_be_available = _OA_cache_construction_available(k,n) is not False

    if n <= 1:
        if existence:
            return True
        OA = [[0]*k]*n

    elif k >= n+t:
        # When t=2 then k<n+t as it is equivalent to the existence of n-1 MOLS.
        # When t>2 the submatrix defined by the rows whose first t-2 elements
        # are 0s yields a OA with t=2 and k-(t-2) columns. Thus k-(t-2) < n+2,
        # i.e. k<n+t.
        if existence:
            return False
        raise EmptySetError("No Orthogonal Array exists when k>=n+t except when n<=1")

    elif k <= t:
        if existence:
            return True

        from itertools import product
        return map(list, product(range(n), repeat=k))

    elif t != 2:
        if existence:
            return Unknown
        raise NotImplementedError("Only trivial orthogonal arrays are implemented for t>=2")

    elif k <= 3:
        if existence:
            return True
        return [[i,j,(i+j)%n] for i in xrange(n) for j in xrange(n)]

    # projective spaces are equivalent to OA(n+1,n,2)
    elif (projective_plane(n, existence=True) or
           (k == n+1 and projective_plane(n, existence=True) is False)):
        _OA_cache_set(n+1,n,projective_plane(n, existence=True))
        if k == n+1:
            if existence:
                return projective_plane(n, existence=True)
            p = projective_plane(n, check=False)
            OA = projective_plane_to_OA(p, check=False)
        else:
            if existence:
                return True
            p = projective_plane(n, check=False)
            OA = [l[:k] for l in projective_plane_to_OA(p, check=False)]

    # Constructions from the database
    elif may_be_available and n in OA_constructions and k <= OA_constructions[n][0]:
        _OA_cache_set(OA_constructions[n][0],n,True)
        if existence:
            return True
        _, construction = OA_constructions[n]

        OA = OA_from_wider_OA(construction(),k)

    # Constructions from the database II
    elif may_be_available and k <= 6 and n == 12:
        _OA_cache_set(6,12,True)

        if existence:
            return True
        else:
            from database import TD_6_12
            TD = TD_6_12()
            OA = [[x%n for x in R] for R in TD]

    # Constructions from the database III
    # Section 6.5.1 from [Stinson2004]
    elif may_be_available and n in MOLS_constructions and k-2 <= MOLS_constructions[n][0]:
        _OA_cache_set(MOLS_constructions[n][0]+2,n,True)

        if existence:
            return True
        else:
            construction = MOLS_constructions[n][1]
            mols = construction()
            OA = [[i,j]+[m[i,j] for m in mols]
                  for i in range(n) for j in range(n)]
            OA = OA_from_wider_OA(OA,k)

    elif may_be_available and find_recursive_construction(k,n):
        _OA_cache_set(k,n,True)
        if existence:
            return True
        f,args = find_recursive_construction(k,n)
        OA = f(*args)

    else:
        _OA_cache_set(k,n,Unknown)
        if existence:
            return Unknown
        raise NotImplementedError("I don't know how to build an OA({},{})!".format(k,n))

    if check:
        assert is_orthogonal_array(OA,k,n,t)

    return OA
Exemple #6
0
def wilson_construction(OA,k,r,m,n_trunc,u,check=True):
    r"""
    Return a `OA(k,rm+u)` from a truncated `OA(k+s,r)` by Wilson's construction.

    Let `OA` be a truncated `OA(k+s,r)` with `s` truncated columns of sizes
    `u_1,...,u_s`, whose blocks have sizes in `\{k+b_1,...,k+b_t\}`. If there
    exist:

    - An `OA(k,m+b_i) - b_i.OA(k,1)` for every `1\leq i\leq t`

    - An `OA(k,u_i)` for every `1\leq i\leq s`

    Then there exists an `OA(k,rm+\sum u_i)`. The construction is a
    generalization of Lemma 3.16 in [HananiBIBD]_.

    INPUT:

    - ``OA`` -- an incomplete orthogonal array with ``k+n_trunc`` columns. The
      elements of a column of size `c` must belong to `\{0,...,c\}`. The missing
      entries of a block are represented by ``None`` values.

    - ``k,r,m,n_trunc`` (integers)

    - ``u`` (list) -- a list of length ``n_trunc`` such that column ``k+i`` has
      size ``u[i]``.

    - ``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.

    REFERENCE:

    .. [HananiBIBD] Balanced incomplete block designs and related designs,
      Haim Hanani,
      Discrete Mathematics 11.3 (1975) pages 255-369.

    EXAMPLES::

        sage: from sage.combinat.designs.orthogonal_arrays import wilson_construction
        sage: from sage.combinat.designs.orthogonal_arrays import OA_relabel
        sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_wilson_decomposition_with_one_truncated_group
        sage: total = 0
        sage: for k in range(3,8):
        ....:    for n in range(1,30):
        ....:        if find_wilson_decomposition_with_one_truncated_group(k,n):
        ....:            total += 1
        ....:            f, args = find_wilson_decomposition_with_one_truncated_group(k,n)
        ....:            _ = f(*args)
        sage: print total
        41
    """
    n = r*m+sum(u)
    master_design = OA

    assert n_trunc == len(u)

    # Computing the sizes of the blocks by filtering out None entries
    block_sizes = set(sum(xx!=None for xx in B) for B in OA)

    # For each block of size k+i we need a OA(k,m+i)-i.OA(k,1)
    OA_k_mpi = {i-k+m: incomplete_orthogonal_array(k, i-k+m, (1,)*(i-k)) for i in block_sizes}

    # For each truncated column of size uu we need a OA(k,uu)
    OA_k_u = {uu: orthogonal_array(k, uu) for uu in u}

    # Building the actual design ! The set of integers is :
    # 0*m+0...0*m+(m-1)|...|(r-1)m+0...(r-1)m+(m-1)|mr+0...mr+(r1-1)|mr+r1...mr+r1+r2-1
    OA = []
    for B in master_design:
        # The missing entries belong to the last n_trunc columns
        assert all(x is not None for x in B[:k])
        n_in_truncated = n_trunc-B.count(None)

        # We replace the block with a OA(k,m+n_in_truncated) properly relabelled
        matrix = [range(i*m,(i+1)*m) for i in B[:k]]
        c = r*m
        for i in range(n_trunc):
            if B[k+i] is not None:
                for C in matrix:
                    C.append(c+B[k+i])
            c += u[i]
        OA.extend(OA_relabel(OA_k_mpi[m+n_in_truncated],k,m+n_in_truncated,matrix=matrix))

    # The missing OA(k,uu)
    c = r*m
    for uu in u:
        OA.extend(OA_relabel(OA_k_u[uu],k,u,matrix=[range(c,c+uu)]*k))
        c += uu

    if check:
        from designs_pyx import is_orthogonal_array
        assert is_orthogonal_array(OA,k,n,2)

    return OA
Exemple #7
0
def OA_from_PBD(k,n,PBD, check=True):
    r"""
    Return an `OA(k,n)` from a PBD

    **Construction**

    Let `\mathcal B` be a `(n,K,1)`-PBD. If there exists for every `i\in K` a
    `TD(k,i)-i\times TD(k,1)` (i.e. if there exist `k` idempotent MOLS), then
    one can obtain a `OA(k,n)` by concatenating:

    - A `TD(k,i)-i\times TD(k,1)` defined over the elements of `B` for every `B
      \in \mathcal B`.

    - The rows `(i,...,i)` of length `k` for every `i\in [n]`.

    .. NOTE::

        This function raises an exception when Sage is unable to build the
        necessary designs.

    INPUT:

    - ``k,n`` (integers)

    - ``PBD`` -- a PBD on `0,...,n-1`.

    EXAMPLES:

    We start from the example VI.1.2 from the [DesignHandbook]_ to build an
    `OA(3,10)`::

        sage: from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
        sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
        sage: pbd = [[0,1,2,3],[0,4,5,6],[0,7,8,9],[1,4,7],[1,5,8],
        ....: [1,6,9],[2,4,9],[2,5,7],[2,6,8],[3,4,8],[3,5,9],[3,6,7]]
        sage: oa = OA_from_PBD(3,10,pbd)
        sage: is_orthogonal_array(oa, 3, 10)
        True

    But we cannot build an `OA(4,10)`::

        sage: OA_from_PBD(4,10,pbd)
        Traceback (most recent call last):
        ...
        EmptySetError: There is no OA(n+1,n) - 3.OA(n+1,1) as all blocks do intersect in a projective plane.

    Or an `OA(6,10)`::

        sage: _ = OA_from_PBD(3,6,pbd)
        Traceback (most recent call last):
        ...
        RuntimeError: The PBD covers a point 8 which is not in {0, ..., 5}
    """
    # Size of the sets of the PBD
    K = set(map(len,PBD))
    if check:
        from bibd import _check_pbd
        _check_pbd(PBD, n, K)

    # Building the IOA
    OAs = {i:incomplete_orthogonal_array(k,i,(1,)*i) for i in K}

    OA = []
    # For every block B of the PBD we add to the OA rows covering all pairs of
    # (distinct) coordinates within the elements of B.
    for S in PBD:
        for B in OAs[len(S)]:
            OA.append([S[i] for i in B])

    # Adding the 0..0, 1..1, 2..2 .... rows
    for i in range(n):
        OA.append([i]*k)

    if check:
        assert is_orthogonal_array(OA,k,n,2)

    return OA
Exemple #8
0
def projective_plane_to_OA(pplane, pt=None, check=True):
    r"""
    Return the orthogonal array built from the projective plane ``pplane``.

    The orthogonal array `OA(n+1,n,2)` is obtained from the projective plane
    ``pplane`` by removing the point ``pt`` and the `n+1` lines that pass
    through it`. These `n+1` lines form the `n+1` groups while the remaining
    `n^2+n` lines form the transversals.

    INPUT:

    - ``pplane`` - a projective plane as a 2-design

    - ``pt`` - a point in the projective plane ``pplane``. If it is not provided
      then it is set to `n^2 + 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: from sage.combinat.designs.block_design import projective_plane_to_OA
        sage: p2 = designs.DesarguesianProjectivePlaneDesign(2,point_coordinates=False)
        sage: projective_plane_to_OA(p2)
        [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
        sage: p3 = designs.DesarguesianProjectivePlaneDesign(3,point_coordinates=False)
        sage: projective_plane_to_OA(p3)
        [[0, 0, 0, 0],
         [0, 1, 2, 1],
         [0, 2, 1, 2],
         [1, 0, 2, 2],
         [1, 1, 1, 0],
         [1, 2, 0, 1],
         [2, 0, 1, 1],
         [2, 1, 0, 2],
         [2, 2, 2, 0]]

        sage: pp = designs.DesarguesianProjectivePlaneDesign(16,point_coordinates=False)
        sage: _ = projective_plane_to_OA(pp, pt=0)
        sage: _ = projective_plane_to_OA(pp, pt=3)
        sage: _ = projective_plane_to_OA(pp, pt=7)
    """
    from bibd import _relabel_bibd
    pplane = pplane.blocks()
    n = len(pplane[0]) - 1

    if pt is None:
        pt = n**2+n

    assert len(pplane) == n**2+n+1, "pplane is not a projective plane"
    assert all(len(B) == n+1 for B in pplane), "pplane is not a projective plane"

    pplane = _relabel_bibd(pplane,n**2+n+1,p=n**2+n)
    OA = [[x%n for x in sorted(X)] for X in pplane if not n**2+n in X]

    assert len(OA) == n**2, "pplane is not a projective plane"

    if check:
        from designs_pyx import is_orthogonal_array
        is_orthogonal_array(OA,n+1,n,2)

    return OA
Exemple #9
0
def projective_plane_to_OA(pplane, pt=None, check=True):
    r"""
    Return the orthogonal array built from the projective plane ``pplane``.

    The orthogonal array `OA(n+1,n,2)` is obtained from the projective plane
    ``pplane`` by removing the point ``pt`` and the `n+1` lines that pass
    through it`. These `n+1` lines form the `n+1` groups while the remaining
    `n^2+n` lines form the transversals.

    INPUT:

    - ``pplane`` - a projective plane as a 2-design

    - ``pt`` - a point in the projective plane ``pplane``. If it is not provided
      then it is set to `n^2 + 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: from sage.combinat.designs.block_design import projective_plane_to_OA
        sage: p2 = designs.DesarguesianProjectivePlaneDesign(2)
        sage: projective_plane_to_OA(p2)
        [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
        sage: p3 = designs.DesarguesianProjectivePlaneDesign(3)
        sage: projective_plane_to_OA(p3)
        [[0, 0, 0, 0],
         [0, 1, 2, 1],
         [0, 2, 1, 2],
         [1, 0, 2, 2],
         [1, 1, 1, 0],
         [1, 2, 0, 1],
         [2, 0, 1, 1],
         [2, 1, 0, 2],
         [2, 2, 2, 0]]

        sage: pp = designs.DesarguesianProjectivePlaneDesign(16)
        sage: _ = projective_plane_to_OA(pp, pt=0)
        sage: _ = projective_plane_to_OA(pp, pt=3)
        sage: _ = projective_plane_to_OA(pp, pt=7)
    """
    from bibd import _relabel_bibd
    pplane = pplane.blocks()
    n = len(pplane[0]) - 1

    if pt is None:
        pt = n**2 + n

    assert len(pplane) == n**2 + n + 1, "pplane is not a projective plane"
    assert all(len(B) == n + 1
               for B in pplane), "pplane is not a projective plane"

    pplane = _relabel_bibd(pplane, n**2 + n + 1, p=n**2 + n)
    OA = [[x % n for x in sorted(X)] for X in pplane if not n**2 + n in X]

    assert len(OA) == n**2, "pplane is not a projective plane"

    if check:
        from designs_pyx import is_orthogonal_array
        is_orthogonal_array(OA, n + 1, n, 2)

    return OA
def are_mutually_orthogonal_latin_squares(l, verbose=False):
    r"""
    Check wether the list of matrices in ``l`` form mutually orthogonal latin
    squares.

    INPUT:

    - ``verbose`` - if ``True`` then print why the list of matrices provided are
      not mutually orthogonal latin squares

    EXAMPLES::

        sage: from sage.combinat.designs.latin_squares import are_mutually_orthogonal_latin_squares
        sage: m1 = matrix([[0,1,2],[2,0,1],[1,2,0]])
        sage: m2 = matrix([[0,1,2],[1,2,0],[2,0,1]])
        sage: m3 = matrix([[0,1,2],[2,0,1],[1,2,0]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2])
        True
        sage: are_mutually_orthogonal_latin_squares([m1,m3])
        False
        sage: are_mutually_orthogonal_latin_squares([m2,m3])
        True
        sage: are_mutually_orthogonal_latin_squares([m1,m2,m3], verbose=True)
        Squares 0 and 2 are not orthogonal
        False

        sage: m = designs.mutually_orthogonal_latin_squares(7,8)
        sage: are_mutually_orthogonal_latin_squares(m)
        True

    TESTS:

    Not a latin square::

        sage: m1 = matrix([[0,1,0],[2,0,1],[1,2,0]])
        sage: m2 = matrix([[0,1,2],[1,2,0],[2,0,1]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2], verbose=True)
        Matrix 0 is not row latin
        False
        sage: m1 = matrix([[0,1,2],[1,0,2],[1,2,0]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2], verbose=True)
        Matrix 0 is not column latin
        False
        sage: m1 = matrix([[0,0,0],[1,1,1],[2,2,2]])
        sage: m2 = matrix([[0,1,2],[0,1,2],[0,1,2]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2])
        False
    """

    if not l:
        raise ValueError("the list must be non empty")

    n = l[0].ncols()
    k = len(l)
    if any(M.ncols() != n or M.nrows() != n for M in l):
        if verbose:
            print "Not all matrices are square matrices of the same dimensions"
        return False

    # Check that all matrices are latin squares
    for i, M in enumerate(l):
        if any(len(set(R)) != n for R in M):
            if verbose:
                print "Matrix {} is not row latin".format(i)
            return False
        if any(len(set(R)) != n for R in zip(*M)):
            if verbose:
                print "Matrix {} is not column latin".format(i)
            return False

    from designs_pyx import is_orthogonal_array
    return is_orthogonal_array(zip(*[[x for R in M for x in R] for M in l]),
                               k,
                               n,
                               verbose=verbose,
                               terminology="MOLS")
Exemple #11
0
def are_mutually_orthogonal_latin_squares(l, verbose=False):
    r"""
    Check wether the list of matrices in ``l`` form mutually orthogonal latin
    squares.

    INPUT:

    - ``verbose`` - if ``True`` then print why the list of matrices provided are
      not mutually orthogonal latin squares

    EXAMPLES::

        sage: from sage.combinat.designs.latin_squares import are_mutually_orthogonal_latin_squares
        sage: m1 = matrix([[0,1,2],[2,0,1],[1,2,0]])
        sage: m2 = matrix([[0,1,2],[1,2,0],[2,0,1]])
        sage: m3 = matrix([[0,1,2],[2,0,1],[1,2,0]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2])
        True
        sage: are_mutually_orthogonal_latin_squares([m1,m3])
        False
        sage: are_mutually_orthogonal_latin_squares([m2,m3])
        True
        sage: are_mutually_orthogonal_latin_squares([m1,m2,m3], verbose=True)
        Squares 0 and 2 are not orthogonal
        False

        sage: m = designs.mutually_orthogonal_latin_squares(7,8)
        sage: are_mutually_orthogonal_latin_squares(m)
        True

    TESTS:

    Not a latin square::

        sage: m1 = matrix([[0,1,0],[2,0,1],[1,2,0]])
        sage: m2 = matrix([[0,1,2],[1,2,0],[2,0,1]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2], verbose=True)
        Matrix 0 is not row latin
        False
        sage: m1 = matrix([[0,1,2],[1,0,2],[1,2,0]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2], verbose=True)
        Matrix 0 is not column latin
        False
        sage: m1 = matrix([[0,0,0],[1,1,1],[2,2,2]])
        sage: m2 = matrix([[0,1,2],[0,1,2],[0,1,2]])
        sage: are_mutually_orthogonal_latin_squares([m1,m2])
        False
    """

    if not l:
        raise ValueError("the list must be non empty")

    n = l[0].ncols()
    k = len(l)
    if any(M.ncols() != n or M.nrows() != n for M in l):
        if verbose:
            print "Not all matrices are square matrices of the same dimensions"
        return False

    # Check that all matrices are latin squares
    for i,M in enumerate(l):
        if any(len(set(R)) != n for R in M):
            if verbose:
                print "Matrix {} is not row latin".format(i)
            return False
        if any(len(set(R)) != n for R in zip(*M)):
            if verbose:
                print "Matrix {} is not column latin".format(i)
            return False

    from designs_pyx import is_orthogonal_array
    return is_orthogonal_array(zip(*[[x for R in M for x in R] for M in l]),k,n, verbose=verbose, terminology="MOLS")
Exemple #12
0
def orthogonal_array(k, n, t=2, check=True, existence=False):
    r"""
    Return an orthogonal array of parameters `k,n,t`.

    An orthogonal array of parameters `k,n,t` is a matrix with `k` columns
    filled with integers from `[n]` in such a way that for any `t` columns, each
    of the `n^t` possible rows occurs exactly once. In
    particular, the matrix has `n^t` rows.

    More general definitions sometimes involve a `\lambda` parameter, and we
    assume here that `\lambda=1`.

    For more information on orthogonal arrays, see
    :wikipedia:`Orthogonal_array`.

    INPUT:

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

    - ``n`` -- (integer) number of symbols

    - ``t`` -- (integer; default: 2) -- strength of the array

    - ``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.

    - ``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 `TD(k,n)`.

    OUTPUT:

    The kind of output depends on the input:

    - if ``existence=False`` (the default) then the output is a list of lists
      that represent an orthogonal array with parameters ``k`` and ``n``

    - if ``existence=True`` and ``k`` is an integer, then the function returns a
      troolean: either ``True``, ``Unknown`` or ``False``

    - if ``existence=True`` and ``k=None`` then the output is the largest value
      of ``k`` for which Sage knows how to compute a `TD(k,n)`.

    .. NOTE::

        This method implements theorems from [Stinson2004]_. See the code's
        documentation for details.

    .. SEEALSO::

        When `t=2` an orthogonal array is also a transversal design (see
        :func:`transversal_design`) and a family of mutually orthogonal latin
        squares (see
        :func:`~sage.combinat.designs.latin_squares.mutually_orthogonal_latin_squares`).

    EXAMPLES::

        sage: designs.orthogonal_array(3,2)
        [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]

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

    What is the largest value of `k` for which Sage knows how to compute a
    `OA(k,14,2)`?::

        sage: designs.orthogonal_array(None,14,existence=True)
        6

    If you ask for an orthogonal array that does not exist, then the function
    either raise an ``EmptySetError`` (if it knows that such an orthogonal array
    does not exist) or a ``NotImplementedError``::

        sage: designs.orthogonal_array(4,2)
        Traceback (most recent call last):
        ...
        EmptySetError: No Orthogonal Array exists when k>=n+t except when n<=1
        sage: designs.orthogonal_array(12,20)
        Traceback (most recent call last):
        ...
        NotImplementedError: I don't know how to build an OA(12,20)!

    Note that these errors correspond respectively to the answers ``False`` and
    ``Unknown`` when the parameter ``existence`` is set to ``True``::

        sage: designs.orthogonal_array(4,2,existence=True)
        False
        sage: designs.orthogonal_array(12,20,existence=True)
        Unknown

    TESTS:

    The special cases `n=0,1`::

        sage: designs.orthogonal_array(3,0)
        []
        sage: designs.orthogonal_array(3,1)
        [[0, 0, 0]]
        sage: designs.orthogonal_array(None,0,existence=True)
        +Infinity
        sage: designs.orthogonal_array(None,1,existence=True)
        +Infinity
        sage: designs.orthogonal_array(None,1)
        Traceback (most recent call last):
        ...
        ValueError: there is no upper bound on k when 0<=n<=1
        sage: designs.orthogonal_array(None,0)
        Traceback (most recent call last):
        ...
        ValueError: there is no upper bound on k when 0<=n<=1
        sage: designs.orthogonal_array(16,0)
        []
        sage: designs.orthogonal_array(16,1)
        [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

    when `t>2` and `k=None`::

        sage: t = 3
        sage: designs.orthogonal_array(None,5,t=t,existence=True) == t
        True
        sage: _ = designs.orthogonal_array(t,5,t)
    """

    from latin_squares import mutually_orthogonal_latin_squares
    from database import OA_constructions, MOLS_constructions
    from block_design import projective_plane, projective_plane_to_OA
    from orthogonal_arrays_recursive import find_recursive_construction

    assert n >= 0

    # If k is set to None we find the largest value available
    if k is None:
        if n == 0 or n == 1:
            if existence:
                from sage.rings.infinity import Infinity
                return Infinity
            raise ValueError("there is no upper bound on k when 0<=n<=1")
        elif t == 2 and projective_plane(n, existence=True):
            k = n + 1
        else:
            for k in range(t - 1, n + 2):
                if not orthogonal_array(k + 1, n, t=t, existence=True):
                    break
        if existence:
            return k

    if k < t:
        raise ValueError("undefined for k<t")

    if existence and _OA_cache_get(k, n) is not None and t == 2:
        return _OA_cache_get(k, n)

    may_be_available = _OA_cache_construction_available(k, n) is not False

    if n <= 1:
        if existence:
            return True
        OA = [[0] * k] * n

    elif k >= n + t:
        # When t=2 then k<n+t as it is equivalent to the existence of n-1 MOLS.
        # When t>2 the submatrix defined by the rows whose first t-2 elements
        # are 0s yields a OA with t=2 and k-(t-2) columns. Thus k-(t-2) < n+2,
        # i.e. k<n+t.
        if existence:
            return False
        raise EmptySetError(
            "No Orthogonal Array exists when k>=n+t except when n<=1")

    elif k <= t:
        if existence:
            return True

        from itertools import product
        return map(list, product(range(n), repeat=k))

    elif t != 2:
        if existence:
            return Unknown
        raise NotImplementedError(
            "Only trivial orthogonal arrays are implemented for t>=2")

    elif k <= 3:
        if existence:
            return True
        return [[i, j, (i + j) % n] for i in xrange(n) for j in xrange(n)]

    # projective spaces are equivalent to OA(n+1,n,2)
    elif (projective_plane(n, existence=True)
          or (k == n + 1 and projective_plane(n, existence=True) is False)):
        _OA_cache_set(n + 1, n, projective_plane(n, existence=True))
        if k == n + 1:
            if existence:
                return projective_plane(n, existence=True)
            p = projective_plane(n, check=False)
            OA = projective_plane_to_OA(p, check=False)
        else:
            if existence:
                return True
            p = projective_plane(n, check=False)
            OA = [l[:k] for l in projective_plane_to_OA(p, check=False)]

    # Constructions from the database
    elif may_be_available and n in OA_constructions and k <= OA_constructions[
            n][0]:
        _OA_cache_set(OA_constructions[n][0], n, True)
        if existence:
            return True
        _, construction = OA_constructions[n]

        OA = OA_from_wider_OA(construction(), k)

    # Constructions from the database II
    elif may_be_available and k <= 6 and n == 12:
        _OA_cache_set(6, 12, True)

        if existence:
            return True
        else:
            from database import TD_6_12
            TD = TD_6_12()
            OA = [[x % n for x in R] for R in TD]

    # Constructions from the database III
    # Section 6.5.1 from [Stinson2004]
    elif may_be_available and n in MOLS_constructions and k - 2 <= MOLS_constructions[
            n][0]:
        _OA_cache_set(MOLS_constructions[n][0] + 2, n, True)

        if existence:
            return True
        else:
            construction = MOLS_constructions[n][1]
            mols = construction()
            OA = [[i, j] + [m[i, j] for m in mols] for i in range(n)
                  for j in range(n)]
            OA = OA_from_wider_OA(OA, k)

    elif may_be_available and find_recursive_construction(k, n):
        _OA_cache_set(k, n, True)
        if existence:
            return True
        f, args = find_recursive_construction(k, n)
        OA = f(*args)

    else:
        _OA_cache_set(k, n, Unknown)
        if existence:
            return Unknown
        raise NotImplementedError(
            "I don't know how to build an OA({},{})!".format(k, n))

    if check:
        assert is_orthogonal_array(OA, k, n, t)

    return OA
Exemple #13
0
def wilson_construction(OA, k, r, m, n_trunc, u, check=True):
    r"""
    Return a `OA(k,rm+u)` from a truncated `OA(k+s,r)` by Wilson's construction.

    Let `OA` be a truncated `OA(k+s,r)` with `s` truncated columns of sizes
    `u_1,...,u_s`, whose blocks have sizes in `\{k+b_1,...,k+b_t\}`. If there
    exist:

    - An `OA(k,m+b_i) - b_i.OA(k,1)` for every `1\leq i\leq t`

    - An `OA(k,u_i)` for every `1\leq i\leq s`

    Then there exists an `OA(k,rm+\sum u_i)`. The construction is a
    generalization of Lemma 3.16 in [HananiBIBD]_.

    INPUT:

    - ``OA`` -- an incomplete orthogonal array with ``k+n_trunc`` columns. The
      elements of a column of size `c` must belong to `\{0,...,c\}`. The missing
      entries of a block are represented by ``None`` values.

    - ``k,r,m,n_trunc`` (integers)

    - ``u`` (list) -- a list of length ``n_trunc`` such that column ``k+i`` has
      size ``u[i]``.

    - ``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.

    REFERENCE:

    .. [HananiBIBD] Balanced incomplete block designs and related designs,
      Haim Hanani,
      Discrete Mathematics 11.3 (1975) pages 255-369.

    EXAMPLES::

        sage: from sage.combinat.designs.orthogonal_arrays import wilson_construction
        sage: from sage.combinat.designs.orthogonal_arrays import OA_relabel
        sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_wilson_decomposition_with_one_truncated_group
        sage: total = 0
        sage: for k in range(3,8):
        ....:    for n in range(1,30):
        ....:        if find_wilson_decomposition_with_one_truncated_group(k,n):
        ....:            total += 1
        ....:            f, args = find_wilson_decomposition_with_one_truncated_group(k,n)
        ....:            _ = f(*args)
        sage: print total
        41
    """
    n = r * m + sum(u)
    master_design = OA

    assert n_trunc == len(u)

    # Computing the sizes of the blocks by filtering out None entries
    block_sizes = set(sum(xx != None for xx in B) for B in OA)

    # For each block of size k+i we need a OA(k,m+i)-i.OA(k,1)
    OA_k_mpi = {
        i - k + m: incomplete_orthogonal_array(k, i - k + m, (1, ) * (i - k))
        for i in block_sizes
    }

    # For each truncated column of size uu we need a OA(k,uu)
    OA_k_u = {uu: orthogonal_array(k, uu) for uu in u}

    # Building the actual design ! The set of integers is :
    # 0*m+0...0*m+(m-1)|...|(r-1)m+0...(r-1)m+(m-1)|mr+0...mr+(r1-1)|mr+r1...mr+r1+r2-1
    OA = []
    for B in master_design:
        # The missing entries belong to the last n_trunc columns
        assert all(x is not None for x in B[:k])
        n_in_truncated = n_trunc - B.count(None)

        # We replace the block with a OA(k,m+n_in_truncated) properly relabelled
        matrix = [range(i * m, (i + 1) * m) for i in B[:k]]
        c = r * m
        for i in range(n_trunc):
            if B[k + i] is not None:
                for C in matrix:
                    C.append(c + B[k + i])
            c += u[i]
        OA.extend(
            OA_relabel(OA_k_mpi[m + n_in_truncated],
                       k,
                       m + n_in_truncated,
                       matrix=matrix))

    # The missing OA(k,uu)
    c = r * m
    for uu in u:
        OA.extend(OA_relabel(OA_k_u[uu], k, u, matrix=[range(c, c + uu)] * k))
        c += uu

    if check:
        from designs_pyx import is_orthogonal_array
        assert is_orthogonal_array(OA, k, n, 2)

    return OA
Exemple #14
0
def OA_from_PBD(k, n, PBD, check=True):
    r"""
    Return an `OA(k,n)` from a PBD

    **Construction**

    Let `\mathcal B` be a `(n,K,1)`-PBD. If there exists for every `i\in K` a
    `TD(k,i)-i\times TD(k,1)` (i.e. if there exist `k` idempotent MOLS), then
    one can obtain a `OA(k,n)` by concatenating:

    - A `TD(k,i)-i\times TD(k,1)` defined over the elements of `B` for every `B
      \in \mathcal B`.

    - The rows `(i,...,i)` of length `k` for every `i\in [n]`.

    .. NOTE::

        This function raises an exception when Sage is unable to build the
        necessary designs.

    INPUT:

    - ``k,n`` (integers)

    - ``PBD`` -- a PBD on `0,...,n-1`.

    EXAMPLES:

    We start from the example VI.1.2 from the [DesignHandbook]_ to build an
    `OA(3,10)`::

        sage: from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
        sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
        sage: pbd = [[0,1,2,3],[0,4,5,6],[0,7,8,9],[1,4,7],[1,5,8],
        ....: [1,6,9],[2,4,9],[2,5,7],[2,6,8],[3,4,8],[3,5,9],[3,6,7]]
        sage: oa = OA_from_PBD(3,10,pbd)
        sage: is_orthogonal_array(oa, 3, 10)
        True

    But we cannot build an `OA(4,10)`::

        sage: OA_from_PBD(4,10,pbd)
        Traceback (most recent call last):
        ...
        EmptySetError: There is no OA(n+1,n) - 3.OA(n+1,1) as all blocks do intersect in a projective plane.

    Or an `OA(6,10)`::

        sage: _ = OA_from_PBD(3,6,pbd)
        Traceback (most recent call last):
        ...
        RuntimeError: The PBD covers a point 8 which is not in {0, ..., 5}
    """
    # Size of the sets of the PBD
    K = set(map(len, PBD))
    if check:
        from bibd import _check_pbd
        _check_pbd(PBD, n, K)

    # Building the IOA
    OAs = {i: incomplete_orthogonal_array(k, i, (1, ) * i) for i in K}

    OA = []
    # For every block B of the PBD we add to the OA rows covering all pairs of
    # (distinct) coordinates within the elements of B.
    for S in PBD:
        for B in OAs[len(S)]:
            OA.append([S[i] for i in B])

    # Adding the 0..0, 1..1, 2..2 .... rows
    for i in range(n):
        OA.append([i] * k)

    if check:
        assert is_orthogonal_array(OA, k, n, 2)

    return OA
Exemple #15
0
def construction_q_x(k,q,x,check=True):
    r"""
    Return an `OA(k,(q-1)*(q-x)+x+2)` using the `q-x` construction.

    Let `v=(q-1)*(q-x)+x+2`. If there exists a projective plane of order `q`
    (e.g. when `q` is a prime power) and `0<x<q` then there exists a
    `(v-1,\{q-x-1,q-x+1\})`-GDD of type `(q-1)^{q-x}(x+1)^1` (see [Greig99]_ or
    Theorem 2.50, section IV.2.3 of [DesignHandbook]_). By adding to the ground
    set one point contained in all groups of the GDD, one obtains a
    `(v,\{q-x-1,q-x+1,q,x+2\})`-PBD with exactly one set of size `x+2`.

    Thus, assuming that we have the following:

    - `OA(k,q-x-1)-(q-x-1).OA(k,1)`
    - `OA(k,q-x+1)-(q-x+1).OA(k,1)`
    - `OA(k,q)-q.OA(k,1)`
    - `OA(k,x+2)`

    Then we can build from the PBD an `OA(k,v)`.

    Construction of the PBD (shared by Julian R. Abel):

        Start with a resolvable `(q^2,q,1)`-BIBD and put the points into a `q\times q`
        array so that rows form a parallel class and columns form another.

        Now delete:

        - All `x(q-1)` points from the first `x` columns and not in the first
          row

        - All `q-x` points in the last `q-x` columns AND the first row.

        Then add a point `p_1` to the blocks that are rows. Add a second point
        `p_2` to the `q-x` blocks that are columns of size `q-1`, plus the first
        row of size `x+1`.

    INPUT:

    - ``k,q,x`` -- integers such that `0<x<q` and such that Sage can build:

        - A projective plane of order `q`
        - `OA(k,q-x-1)-(q-x-1).OA(k,1)`
        - `OA(k,q-x+1)-(q-x+1).OA(k,1)`
        - `OA(k,q)-q.OA(k,1)`
        - `OA(k,x+2)`

    - ``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.

    .. SEEALSO::

        - :func:`find_q_x`
        - :func:`~sage.combinat.designs.block_design.projective_plane`
        - :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array`
        - :func:`~sage.combinat.designs.orthogonal_arrays.OA_from_PBD`

    EXAMPLES::

        sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_q_x
        sage: _ = construction_q_x(9,16,6)

    REFERENCES:

    .. [Greig99] Designs from projective planes and PBD bases
      Malcolm Greig
      Journal of Combinatorial Designs
      vol. 7, num. 5, pp. 341--374
      1999
    """
    from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
    from sage.combinat.designs.orthogonal_arrays import incomplete_orthogonal_array

    n = (q-1)*(q-x)+x+2

    # We obtain the qxq matrix from a OA(q,q)-q.OA(1,q). We will need to add
    # blocks corresponding to the rows/columns
    OA = incomplete_orthogonal_array(q,q,(1,)*q)
    TD = [[i*q+xx for i,xx in enumerate(B)] for B in OA]

    # Add rows, extended with p1 and p2
    p1 = q**2
    p2 = p1+1
    TD.extend([[ii*q+i for ii in range(q)]+[p1] for i in range(1,q)])
    TD.append( [ii*q   for ii in range(q)]+[p1,p2])

    # Add Columns. We do not add some columns which would have size 1 after we
    # delete points.
    #
    # TD.extend([range(i*q,(i+1)*q) for i in range(x)])
    TD.extend([range(i*q,(i+1)*q)+[p2] for i in range(x,q)])

    points_to_delete = set([i*q+j for i in range(x) for j in range(1,q)]+[i*q for i in range(x,q)])
    points_to_keep = set(range(q**2+2))-points_to_delete
    relabel = {i:j for j,i in enumerate(points_to_keep)}

    # PBD is a (n,[q,q-x-1,q-x+1,x+2])-PBD
    PBD = [[relabel[xx] for xx in B if not xx in points_to_delete] for B in TD]

    # Taking the unique block of size x+2
    assert map(len,PBD).count(x+2)==1
    for B in PBD:
        if len(B) == x+2:
            break

    # We call OA_from_PBD without the block of size x+2 as there may not exist a
    # OA(k,x+2)-(x+2).OA(k,1)
    PBD.remove(B)
    OA = OA_from_PBD(k,(q-1)*(q-x)+x+2,PBD,check=False)

    # Filling the hole
    for xx in B:
        OA.remove([xx]*k)

    for BB in orthogonal_array(k,x+2):
        OA.append([B[x] for x in BB])

    if check:
        assert is_orthogonal_array(OA,k,n,2)

    return OA