Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
r""" Cython functions for combinatorial designs
This module implements the design methods that need to be somewhat efficient.
Functions --------- """ from __future__ import print_function, absolute_import
include "sage/data_structures/bitset.pxi"
from libc.string cimport memset from cysignals.memory cimport sig_malloc, sig_calloc, sig_realloc, sig_free
from sage.misc.unknown import Unknown
def is_orthogonal_array(OA, int k, int n, int t=2, verbose=False, terminology="OA"): r""" Check that the integer matrix `OA` is an `OA(k,n,t)`.
See :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` for a definition.
INPUT:
- ``OA`` -- the Orthogonal Array to be tested
- ``k,n,t`` (integers) -- only implemented for `t=2`.
- ``verbose`` (boolean) -- whether to display some information when ``OA`` is not an orthogonal array `OA(k,n)`.
- ``terminology`` (string) -- how to phrase the information when ``verbose = True``. Possible values are `"OA"`, `"MOLS"`.
EXAMPLES::
sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array sage: OA = designs.orthogonal_arrays.build(8,9) sage: is_orthogonal_array(OA,8,9) True sage: is_orthogonal_array(OA,8,10) False sage: OA[4][3] = 1 sage: is_orthogonal_array(OA,8,9) False sage: is_orthogonal_array(OA,8,9,verbose=True) Columns 0 and 3 are not orthogonal False sage: is_orthogonal_array(OA,8,9,verbose=True,terminology="MOLS") Squares 0 and 3 are not orthogonal False
TESTS::
sage: is_orthogonal_array(OA,8,9,t=3) Traceback (most recent call last): ... NotImplementedError: only implemented for t=2 sage: is_orthogonal_array([[3]*8],8,9,verbose=True) The number of rows is 1 instead of 9^2=81 False sage: is_orthogonal_array([[3]*8],8,9,verbose=True,terminology="MOLS") All squares do not have dimension n^2=9^2 False sage: is_orthogonal_array([[3]*7],8,9,verbose=True) Some row does not have length 8 False sage: is_orthogonal_array([[3]*7],8,9,verbose=True,terminology="MOLS") The number of squares is not 6 False
Up to relabelling, there is a unique `OA(3,2)`. So their number is just the cardinality of the relabeling group which is `S_2^3 \times S_3` and has cardinality `48`::
sage: from itertools import product sage: n = 0 sage: for a in product(product((0,1), repeat=3), repeat=4): ....: if is_orthogonal_array(a,3,2): ....: n += 1 sage: n 48 """ cdef int x
cdef int i,j,l
# A copy of OA
cdef unsigned short * C1 cdef unsigned short * C2
# failed malloc ? raise MemoryError
# Filling OAc if verbose: print({"OA" : "{} is not in the interval [0..{}]".format(x,n-1), "MOLS" : "Entry {} was expected to be in the interval [0..{}]".format(x,n-1)}[terminology]) sig_free(OAc) return False
# A bitset to keep track of pairs of values cdef bitset_t seen
def is_group_divisible_design(groups,blocks,v,G=None,K=None,lambd=1,verbose=False): r""" Checks that input is a Group Divisible Design on `\{0,...,v-1\}`
For more information on Group Divisible Designs, see :class:`~sage.combinat.designs.group_divisible_designs.GroupDivisibleDesign`.
INPUT:
- ``groups`` -- a partition of `X`. If set to ``None`` the groups are guessed automatically, and the function returns ``(True, guessed_groups)`` instead of ``True``
- ``blocks`` -- collection of blocks
- ``v`` (integers) -- size of the ground set assumed to be `X=\{0,...,v-1\}`.
- ``G`` -- list of integers of which the sizes of the groups must be elements. Set to ``None`` (automatic guess) by default.
- ``K`` -- list of integers of which the sizes of the blocks must be elements. Set to ``None`` (automatic guess) by default.
- ``lambd`` -- value of `\lambda`. Set to `1` by default.
- ``verbose`` (boolean) -- whether to display some information when the design is not a GDD.
EXAMPLES::
sage: from sage.combinat.designs.designs_pyx import is_group_divisible_design sage: TD = designs.transversal_design(4,10) sage: groups = [list(range(i*10,(i+1)*10)) for i in range(4)] sage: is_group_divisible_design(groups,TD,40,lambd=1) True
TESTS::
sage: TD = designs.transversal_design(4,10) sage: groups = [list(range(i*10,(i+1)*10)) for i in range(4)] sage: is_group_divisible_design(groups,TD,40,lambd=2,verbose=True) the pair (0,10) has been seen 1 times but lambda=2 False sage: is_group_divisible_design([[1,2],[3,4]],[[1,2]],40,lambd=1,verbose=True) groups is not a partition of [0,...,39] False sage: is_group_divisible_design([list(range(40))],[[1,2]],40,lambd=1,verbose=True) the pair (1,2) belongs to a group but appears in some block False sage: is_group_divisible_design([list(range(40))],[[2,2]],40,lambd=1,verbose=True) The following block has repeated elements: [2, 2] False sage: is_group_divisible_design([list(range(40))],[["e",2]],40,lambd=1,verbose=True) e does not belong to [0,...,39] False sage: is_group_divisible_design([list(range(40))],[list(range(40))],40,G=[5],lambd=1,verbose=True) a group has size 40 while G=[5] False sage: is_group_divisible_design([list(range(40))],[["e",2]],40,K=[1],lambd=1,verbose=True) a block has size 2 while K=[1] False
sage: p = designs.projective_plane(3) sage: is_group_divisible_design(None, p.blocks(), 13) (True, [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]]) sage: is_group_divisible_design(None, p.blocks()*2, 13, verbose=True) the pair (0,1) has been seen 2 times but lambda=1 False sage: is_group_divisible_design(None, p.blocks()*2, 13, lambd=2) (True, [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]]) """ cdef int i,ii,j,jj,s,isok
if verbose: print("v={} and lambda={} must be non-negative integers".format(v,l)) return False
# Block sizes are element of K
# Check that "groups" consists of disjoints sets whose union has length n
# Checks that the blocks are indeed sets and do not repeat elements
# Check that the groups/blocks belong to [0,...,n-1]
raise MemoryError
# Counts the number of occurrences of each pair of points
# Guess the groups (if necessary)
# Group sizes are element of G
# Checks that two points of the same group were never covered
# We fill the entries with what is expected by the next loop
# Checking that what should be equal to lambda IS equal to lambda
def is_pairwise_balanced_design(blocks,v,K=None,lambd=1,verbose=False): r""" Checks that input is a Pairwise Balanced Design (PBD) on `\{0,...,v-1\}`
For more information on Pairwise Balanced Designs (PBD), see :class:`~sage.combinat.designs.bibd.PairwiseBalancedDesign`.
INPUT:
- ``blocks`` -- collection of blocks
- ``v`` (integers) -- size of the ground set assumed to be `X=\{0,...,v-1\}`.
- ``K`` -- list of integers of which the sizes of the blocks must be elements. Set to ``None`` (automatic guess) by default.
- ``lambd`` -- value of `\lambda`. Set to `1` by default.
- ``verbose`` (boolean) -- whether to display some information when the design is not a PBD.
EXAMPLES::
sage: from sage.combinat.designs.designs_pyx import is_pairwise_balanced_design sage: sts = designs.steiner_triple_system(9) sage: is_pairwise_balanced_design(sts,9,[3],1) True sage: TD = designs.transversal_design(4,10).blocks() sage: groups = [list(range(i*10,(i+1)*10)) for i in range(4)] sage: is_pairwise_balanced_design(TD+groups,40,[4,10],1,verbose=True) True
TESTS::
sage: from sage.combinat.designs.designs_pyx import is_pairwise_balanced_design sage: is_pairwise_balanced_design(TD+groups,40,[4,10],2,verbose=True) the pair (0,1) has been seen 1 times but lambda=2 False sage: is_pairwise_balanced_design(TD+groups,40,[10],1,verbose=True) a block has size 4 while K=[10] False sage: is_pairwise_balanced_design([[2,2]],40,[2],1,verbose=True) The following block has repeated elements: [2, 2] False sage: is_pairwise_balanced_design([["e",2]],40,[2],1,verbose=True) e does not belong to [0,...,39] False """ blocks,
def is_projective_plane(blocks, verbose=False): r""" Test whether the blocks form a projective plane on `\{0,...,v-1\}`
A *projective plane* is an incidence structure that has the following properties:
1. Given any two distinct points, there is exactly one line incident with both of them. 2. Given any two distinct lines, there is exactly one point incident with both of them. 3. There are four points such that no line is incident with more than two of them.
For more informations, see :wikipedia:`Projective_plane`.
:meth:`~IncidenceStructure.is_t_design` can also check if an incidence structure is a projective plane with the parameters `v=k^2+k+1`, `t=2` and `l=1`.
INPUT:
- ``blocks`` -- collection of blocks
- ``verbose`` -- whether to print additional information
EXAMPLES::
sage: from sage.combinat.designs.designs_pyx import is_projective_plane sage: p = designs.projective_plane(4) sage: b = p.blocks() sage: is_projective_plane(b, verbose=True) True
sage: p = designs.projective_plane(2) sage: b = p.blocks() sage: is_projective_plane(b) True sage: b[0][2] = 5 sage: is_projective_plane(b, verbose=True) the pair (0,5) has been seen 2 times but lambda=1 False
sage: is_projective_plane([[0,1,2],[1,2,4]], verbose=True) the pair (0,3) has been seen 0 times but lambda=1 False
sage: is_projective_plane([[1]], verbose=True) First block has less than 3 points. False
sage: p = designs.projective_plane(2) sage: b = p.blocks() sage: b[2].append(4) sage: is_projective_plane(b, verbose=True) a block has size 4 while K=[3] False """ if verbose: print('There is no block.') return False blocks, lambd=1,
def is_difference_matrix(M,G,k,lmbda=1,verbose=False): r""" Test if `M` is a `(G,k,\lambda)`-difference matrix.
A matrix `M` is a `(G,k,\lambda)`-difference matrix if its entries are element of `G`, and if for any two rows `R,R'` of `M` and `x\in G` there are exactly `\lambda` values `i` such that `R_i-R'_i=x`.
INPUT:
- ``M`` -- a matrix with entries from ``G``
- ``G`` -- a group
- ``k`` -- integer
- ``lmbda`` (integer) -- set to `1` by default.
- ``verbose`` (boolean) -- whether to print some information when the answer is ``False``.
EXAMPLES::
sage: from sage.combinat.designs.designs_pyx import is_difference_matrix sage: q = 3**3 sage: F = GF(q,'x') sage: M = [[x*y for y in F] for x in F] sage: is_difference_matrix(M,F,q,verbose=1) True
sage: B = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ....: [0, 1, 2, 3, 4, 2, 3, 4, 0, 1], ....: [0, 2, 4, 1, 3, 3, 0, 2, 4, 1]] sage: G = GF(5) sage: B = [[G(b) for b in R] for R in B] sage: is_difference_matrix(list(zip(*B)),G,3,2) True
Bad input::
sage: for R in M: R.append(None) sage: is_difference_matrix(M,F,q,verbose=1) The matrix has 28 columns but k=27 False sage: for R in M: _=R.pop(-1) sage: M.append([None]*3**3) sage: is_difference_matrix(M,F,q,verbose=1) The matrix has 28 rows instead of lambda(|G|-1+2u)+mu=1(27-1+2.0)+1=27 False sage: _= M.pop(-1) sage: for R in M: R[-1] = 0 sage: is_difference_matrix(M,F,q,verbose=1) Columns 0 and 26 generate 0 exactly 27 times instead of the expected mu(=1) False sage: for R in M: R[-1] = 1 sage: M[-1][-1] = 0 sage: is_difference_matrix(M,F,q,verbose=1) Columns 0 and 26 do not generate all elements of G exactly lambda(=1) times. The element x appeared 0 times as a difference. False """
def is_quasi_difference_matrix(M,G,int k,int lmbda,int mu,int u,verbose=False): r""" Test if the matrix is a `(G,k;\lambda,\mu;u)`-quasi-difference matrix
Let `G` be an abelian group of order `n`. A `(n,k;\lambda,\mu;u)`-quasi-difference matrix (QDM) is a matrix `Q_{ij}` with `\lambda(n-1+2u)+\mu` rows and `k` columns, with each entry either equal to ``None`` (i.e. the 'missing entries') or to an element of `G`. Each column contains exactly `\lambda u` empty entries, and each row contains at most one ``None``. Furthermore, for each `1\leq i<j\leq k`, the multiset
.. MATH::
\{q_{li}-q_{lj}:1\leq l\leq \lambda (n-1+2u)+\mu, \text{ with } q_{li}\text{ and }q_{lj}\text{ not empty}\}
contains `\lambda` times every nonzero element of `G` and contains `\mu` times `0`.
INPUT:
- ``M`` -- a matrix with entries from ``G`` (or equal to ``None`` for missing entries)
- ``G`` -- a group
- ``k,lmbda,mu,u`` -- integers
- ``verbose`` (boolean) -- whether to print some information when the answer is ``False``.
EXAMPLES:
Differences matrices::
sage: from sage.combinat.designs.designs_pyx import is_quasi_difference_matrix sage: q = 3**3 sage: F = GF(q,'x') sage: M = [[x*y for y in F] for x in F] sage: is_quasi_difference_matrix(M,F,q,1,1,0,verbose=1) True
sage: B = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ....: [0, 1, 2, 3, 4, 2, 3, 4, 0, 1], ....: [0, 2, 4, 1, 3, 3, 0, 2, 4, 1]] sage: G = GF(5) sage: B = [[G(b) for b in R] for R in B] sage: is_quasi_difference_matrix(list(zip(*B)),G,3,2,2,0) True
A quasi-difference matrix from the database::
sage: from sage.combinat.designs.database import QDM sage: G,M = QDM[38,1][37,1,1,1][1]() sage: is_quasi_difference_matrix(M,G,k=6,lmbda=1,mu=1,u=1) True
Bad input::
sage: is_quasi_difference_matrix(M,G,k=6,lmbda=1,mu=1,u=3,verbose=1) The matrix has 39 rows instead of lambda(|G|-1+2u)+mu=1(37-1+2.3)+1=43 False sage: is_quasi_difference_matrix(M,G,k=6,lmbda=1,mu=2,u=1,verbose=1) The matrix has 39 rows instead of lambda(|G|-1+2u)+mu=1(37-1+2.1)+2=40 False sage: M[3][1] = None sage: is_quasi_difference_matrix(M,G,k=6,lmbda=1,mu=1,u=1,verbose=1) Row 3 contains more than one empty entry False sage: M[3][1] = 1 sage: M[6][1] = None sage: is_quasi_difference_matrix(M,G,k=6,lmbda=1,mu=1,u=1,verbose=1) Column 1 contains 2 empty entries instead of the expected lambda.u=1.1=1 False """
cdef int i,j,ii cdef bint bit
# Height of the matrix
# Width of the matrix
# When |G|=0 return True
# Map group element with integers
# Allocations sig_free(x_minus_y) sig_free(x_minus_y_data) sig_free(G_seen) sig_free(M_c) raise MemoryError
# The "x-y" table. If g_i, g_j \in G, then x_minus_y[i][j] is equal to # group_to_int[g_i-g_j]. # # In order to handle empty values represented by n, we have # x_minus_y[?][n]=x_minus_y[n][?]=n
# Elements of G
# Empty values
# A copy of the matrix
# Each row contains at most one empty entry
# Each column contains lmbda*u empty entries
# We are now ready to test every pair of columns
"exactly lambda(={}) times. The element {} appeared {} "
# Cached information for OA constructions (see .pxd file for more info)
_OA_cache = <cache_entry *> sig_malloc(2*sizeof(cache_entry)) if (_OA_cache == NULL): sig_free(_OA_cache) raise MemoryError _OA_cache[0].max_true = -1 _OA_cache[1].max_true = -1 _OA_cache_size = 2
cpdef _OA_cache_set(int k,int n,truth_value): r""" Sets a value in the OA cache of existence results
INPUT:
- ``k,n`` (integers)
- ``truth_value`` -- one of ``True,False,Unknown`` """ global _OA_cache, _OA_cache_size cdef int i sig_free(_OA_cache) raise MemoryError
else:
cpdef _OA_cache_get(int k,int n): r""" Gets a value from the OA cache of existence results
INPUT:
``k,n`` (integers) """
cpdef _OA_cache_construction_available(int k,int n): r""" Tests if a construction is implemented using the cache's information
INPUT:
- ``k,n`` (integers) """ else: |