Path: blob/master/src/sage/combinat/designs/incidence_structures.py
8818 views
"""1Incidence structures.23An incidence structure is specified by a list of points, blocks, and4an incidence matrix ([1]_, [2]_).56REFERENCES:78.. [1] Block designs and incidence structures from wikipedia,9:wikipedia:`Block_design`10:wikipedia:`Incidence_structure`1112.. [2] E. Assmus, J. Key, Designs and their codes, CUP, 1992.1314AUTHORS:1516- Peter Dobcsanyi and David Joyner (2007-2008)1718This is a significantly modified form of part of the module block_design.py19(version 0.6) written by Peter Dobcsanyi [email protected].202122Classes and methods23-------------------24"""25#***************************************************************************26# Copyright (C) 2007 #27# #28# Peter Dobcsanyi and David Joyner #29# <[email protected]> <[email protected]> #30# #31# #32# Distributed under the terms of the GNU General Public License (GPL) #33# as published by the Free Software Foundation; either version 2 of #34# the License, or (at your option) any later version. #35# http://www.gnu.org/licenses/ #36#***************************************************************************3738from sage.matrix.matrix_space import MatrixSpace39from sage.rings.integer_ring import ZZ40from sage.rings.arith import binomial4142### utility functions -------------------------------------------------------434445def coordinatewise_product(L):46"""47Returns the coordinatewise product of a list of vectors.4849INPUT:5051- ``L`` is a list of `n`-vectors or lists all of length `n` with a common52parent. This returns the vector whose `i`-th coordinate is the product of53the `i`-th coordinates of the vectors.5455EXAMPLES::5657sage: from sage.combinat.designs.incidence_structures import coordinatewise_product58sage: L = [[1,2,3],[-1,-1,-1],[5,7,11]]59sage: coordinatewise_product(L)60[-5, -14, -33]61"""62n = len(L[0])63ans = [1]*n64for x in L:65ans = [ans[i]*x[i] for i in range(n)]66return ans6768def IncidenceStructureFromMatrix(M, name=None):69"""70Builds and incidence structure from a matrix.7172INPUT:7374- ``M`` -- a binary matrix. Creates a set of "points" from the rows and a75set of "blocks" from the columns.7677EXAMPLES::7879sage: from sage.combinat.designs.block_design import BlockDesign80sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])81sage: M = BD1.incidence_matrix()82sage: BD2 = IncidenceStructureFromMatrix(M)83sage: BD1 == BD284True85"""86nm = name87v = len(M.rows())88b = len(M.columns())89#points = range(v)90blocks = []91for i in range(b):92B = []93for j in range(v):94if M[i, j] != 0:95B.append(j)96blocks.append(B)97return IncidenceStructure(range(v), blocks, name=nm)9899100class IncidenceStructure(object):101"""102This the base class for block designs.103"""104def __init__(self, pts, blks, inc_mat=None, name=None, test=True):105"""106INPUT:107108- ``pts, blks`` -- a list of points, and a list of lists (list of blocks).109110If each `B` in ``blks`` is contained in ``pts`` then the incidence111matrix ` inc_mat`` need not (and should not) be given. Otherwise,112``inc_mat`` should be the ``ptsxblks`` `(0,1)`-matrix `A` for which113`A_i,j=1` iff ``blks[j]`` is incident with ``pts[i]``.114115- ``inc_mat`` (for giving the `(0,1)`-incidence matrix)116117- ``name`` (a string, such as "Fano plane").118119- ``test`` (boolean) - if ``True``, then each block must be a list of pts.120121EXAMPLES::122123sage: IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])124Incidence structure with 7 points and 7 blocks125126Points are sorted ::127128sage: BD1 = IncidenceStructure([4,6,0,3,2,5,1],[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])129sage: BD1.points()130[0, 1, 2, 3, 4, 5, 6]131132TESTS:133134The following shows that :trac:`11333` is fixed. ::135136sage: A = IncidenceStructure([0,1],[[0]])137sage: B = IncidenceStructure([1,0],[[0]])138sage: B == A139True140141REFERENCES:142143- E. Assmus, J. Key, Designs and their codes, CUP, 1992.144"""145bs = []146self.pnts = pts147self.pnts.sort()148v, blocks = len(pts), blks149for block in blocks:150if test:151for x in block:152if not(x in self.pnts):153raise ValueError('Point %s is not in the base set.' % x)154try:155y = block[:]156y.sort()157bs.append(y)158except Exception:159bs.append(block)160bs.sort(cmp)161self.v = v162self.blcks = bs163self.name = name164self._incidence_matrix = inc_mat165166def __iter__(self):167"""168Iterator over the blocks.169170EXAMPLE::171172sage: sts = designs.steiner_triple_system(9)173sage: list(sts)174[[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]]175"""176177return iter(self.blcks)178179def __repr__(self):180"""181A print method.182183EXAMPLES::184185sage: from sage.combinat.designs.block_design import BlockDesign186sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])187sage: BD188Incidence structure with 7 points and 7 blocks189"""190repr = 'Incidence structure with %s points and %s blocks' % (len(self.pnts), len(self.blcks))191return repr192193def __str__(self):194"""195A print method.196197EXAMPLES::198199sage: from sage.combinat.designs.block_design import BlockDesign200sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])201sage: print BD202BlockDesign<points=[0, 1, 2, 3, 4, 5, 6], blocks=[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]>203sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])204sage: print BD205IncidenceStructure<points=[0, 1, 2, 3, 4, 5, 6], blocks=[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]>206"""207if self.name:208repr = '%s<points=%s, blocks=%s>' % (self.name, self.pnts,209self.blcks)210else:211repr = 'IncidenceStructure<points=%s, blocks=%s>' % (self.pnts,212self.blcks)213return repr214215def automorphism_group(self):216"""217Returns the subgroup of the automorphism group of the incidence graph218which respects the P B partition. It is (isomorphic to) the automorphism219group of the block design, although the degrees differ.220221EXAMPLES::222223sage: from sage.combinat.designs.block_design import BlockDesign224sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])225sage: G = BD.automorphism_group(); G226Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)]227sage: BD = BlockDesign(4,[[0],[0,1],[1,2],[3,3]],test=False)228sage: G = BD.automorphism_group(); G229Permutation Group with generators [()]230sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])231sage: G = BD.automorphism_group(); G232Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)]233"""234from sage.groups.perm_gps.partn_ref.refinement_matrices import MatrixStruct235from sage.groups.perm_gps.permgroup import PermutationGroup236from sage.groups.perm_gps.permgroup_named import SymmetricGroup237M1 = self.incidence_matrix()238M2 = MatrixStruct(M1)239M2.run()240gens = M2.automorphism_group()[0]241v = len(self.points())242G = SymmetricGroup(v)243gns = []244for g in gens:245L = [j+1 for j in g]246gns.append(G(L))247return PermutationGroup(gns)248249def block_design_checker(self, t, v, k, lmbda, type=None):250"""251This is *not* a wrapper for GAP Design's IsBlockDesign. The GAP252Design function IsBlockDesign253http://www.gap-system.org/Manuals/pkg/design/htm/CHAP004.htm254apparently simply checks the record structure and no mathematical255properties. Instead, the function below checks some necessary (but256not sufficient) "easy" identities arising from the identity.257258INPUT:259260- ``t`` - the t as in "t-design"261262- ``v`` - the number of points263264- ``k`` - the number of blocks incident to a point265266- ``lmbda`` - each t-tuple of points should be incident with267lmbda blocks268269- ``type`` - can be 'simple' or 'binary' or 'connected'270Depending on the option, this wraps IsBinaryBlockDesign,271IsSimpleBlockDesign, or IsConnectedBlockDesign.272273- Binary: no block has a repeated element.274275- Simple: no block is repeated.276277- Connected: its incidence graph is a connected graph.278279WARNING: This is very fast but can return false positives.280281EXAMPLES::282283sage: from sage.combinat.designs.block_design import BlockDesign284sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])285sage: BD.is_block_design()286(True, [2, 7, 3, 1])287sage: BD.block_design_checker(2, 7, 3, 1)288True289sage: BD.block_design_checker(2, 7, 3, 1,"binary")290True291sage: BD.block_design_checker(2, 7, 3, 1,"connected")292True293sage: BD.block_design_checker(2, 7, 3, 1,"simple")294True295"""296from sage.sets.set import Set297if not(v == len(self.points())):298return False299b = lmbda*binomial(v, t)/binomial(k, t)300r = int(b*k/v)301if not(b == len(self.blocks())):302return False303if not(ZZ(v).divides(b*k)):304return False305A = self.incidence_matrix()306#k = sum(A.columns()[0])307#r = sum(A.rows()[0])308for i in range(b):309if not(sum(A.columns()[i]) == k):310return False311for i in range(v):312if not(sum(A.rows()[i]) == r):313return False314if type is None:315return True316if type == "binary":317for b in self.blocks():318if len(b) != len(Set(b)):319return False320return True321if type == "simple":322B = self.blocks()323for b in B:324if B.count(b) > 1:325return False326return True327if type == "connected":328Gamma = self.incidence_graph()329if Gamma.is_connected():330return True331else:332return False333334def blocks(self):335"""336Return the list of blocks.337338EXAMPLES::339340sage: from sage.combinat.designs.block_design import BlockDesign341sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])342sage: BD.blocks()343[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]344"""345B = self.blcks346B.sort()347return B348349def __eq__(self, other):350"""351Returns true if their points and blocks are equal (resp.).352353EXAMPLES::354355sage: from sage.combinat.designs.block_design import BlockDesign356sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])357sage: M = BD1.incidence_matrix()358sage: BD2 = IncidenceStructureFromMatrix(M)359sage: BD1 == BD2360True361"""362bool1 = self.points() == other.points()363bool2 = self.blocks() == other.blocks()364return (bool1 and bool2)365366def block_sizes(self):367"""368Return a list of block's sizes.369370EXAMPLES::371372sage: from sage.combinat.designs.block_design import BlockDesign373sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])374sage: BD.block_sizes()375[3, 3, 3, 3, 3, 3, 3]376"""377self._block_sizes = map(len, self.blocks())378return self._block_sizes379380def _gap_(self):381"""382Return the GAP string describing the design.383384EXAMPLES::385386sage: from sage.combinat.designs.block_design import BlockDesign387sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])388sage: BD._gap_()389'BlockDesign(7,[[1, 2, 3], [1, 4, 5], [1, 6, 7], [2, 4, 6], [2, 5, 7], [3, 4, 7], [3, 5, 6]])'390"""391B = self.blocks()392v = len(self.points())393gB = []394for b in B:395gB.append([x+1 for x in b])396return "BlockDesign("+str(v)+","+str(gB)+")"397398def dual_incidence_structure(self, algorithm=None):399"""400Wraps GAP Design's DualBlockDesign (see [1]). The dual of a block401design may not be a block design.402403Also can be called with ``dual_design``.404405.. NOTE:406407The algorithm="gap" option requires GAP's Design package (included in408the gap_packages Sage spkg).409410EXAMPLES::411412sage: from sage.combinat.designs.block_design import BlockDesign413sage: D = BlockDesign(4, [[0,2],[1,2,3],[2,3]], test=False)414sage: D415Incidence structure with 4 points and 3 blocks416sage: D.dual_design()417Incidence structure with 3 points and 4 blocks418sage: print D.dual_design(algorithm="gap") # optional - gap_design419IncidenceStructure<points=[0, 1, 2], blocks=[[0], [0, 1, 2], [1], [1, 2]]>420sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="FanoPlane")421sage: BD422Incidence structure with 7 points and 7 blocks423sage: print BD.dual_design(algorithm="gap") # optional - gap_design424IncidenceStructure<points=[0, 1, 2, 3, 4, 5, 6], blocks=[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]>425sage: BD.dual_incidence_structure()426Incidence structure with 7 points and 7 blocks427428REFERENCE:429430- Soicher, Leonard, Design package manual, available at431http://www.gap-system.org/Manuals/pkg/design/htm/CHAP003.htm432"""433from sage.interfaces.gap import gap434from sage.misc.functional import transpose435if algorithm == "gap":436gap.load_package("design")437gD = self._gap_()438gap.eval("DD:=DualBlockDesign("+gD+")")439v = eval(gap.eval("DD.v"))440gblcks = eval(gap.eval("DD.blocks"))441gB = []442for b in gblcks:443gB.append([x-1 for x in b])444return IncidenceStructure(range(v), gB, name=None, test=False)445pts = self.blocks()446M = transpose(self.incidence_matrix())447blks = self.points()448return IncidenceStructure(pts, blks, M, name=None, test=False)449450dual_design = dual_incidence_structure # to preserve standard terminology451452def incidence_matrix(self):453"""454Return the incidence matrix `A` of the design. A is a `(v \times b)`455matrix defined by: ``A[i,j] = 1`` if ``i`` is in block ``B_j`` and 0456otherwise.457458EXAMPLES::459460sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])461sage: BD.block_sizes()462[3, 3, 3, 3, 3, 3, 3]463sage: BD.incidence_matrix()464[1 1 1 0 0 0 0]465[1 0 0 1 1 0 0]466[1 0 0 0 0 1 1]467[0 1 0 1 0 1 0]468[0 1 0 0 1 0 1]469[0 0 1 1 0 0 1]470[0 0 1 0 1 1 0]471"""472if not self._incidence_matrix is None:473return self._incidence_matrix474else:475v = len(self.points())476blks = self.blocks()477b = len(blks)478MS = MatrixSpace(ZZ, v, b)479A = MS(0)480#A = NUM.zeros((v,b), NUM.Int)481for i in range(v):482for j, b in enumerate(blks):483if i in b:484A[i, j] = 1485self._incidence_matrix = A486return A487488def incidence_graph(self):489"""490Returns the incidence graph of the design, where the incidence491matrix of the design is the adjacency matrix of the graph.492493EXAMPLE::494495sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])496sage: BD.incidence_graph()497Bipartite graph on 14 vertices498sage: A = BD.incidence_matrix()499sage: Graph(block_matrix([[A*0,A],[A.transpose(),A*0]])) == BD.incidence_graph()500True501502REFERENCE:503504- Sage Reference Manual on Graphs505"""506from sage.graphs.bipartite_graph import BipartiteGraph507A = self.incidence_matrix()508return BipartiteGraph(A)509#same as return Graph(block_matrix([[A*0,A],[A.transpose(),A*0]]))510511def is_block_design(self):512"""513Returns a pair ``True, pars`` if the incidence structure is a514`t`-design, for some `t`, where ``pars`` is the list of parameters `(t,515v, k, lmbda)`. The largest possible `t` is returned, provided `t=10`.516517EXAMPLES::518519sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])520sage: BD.is_block_design()521(True, [2, 7, 3, 1])522sage: BD.block_design_checker(2, 7, 3, 1)523True524sage: BD = designs.WittDesign(9) # optional - gap_packages (design package)525sage: BD.is_block_design() # optional - gap_packages (design package)526(True, [2, 9, 3, 1])527sage: BD = designs.WittDesign(12) # optional - gap_packages (design package)528sage: BD.is_block_design() # optional - gap_packages (design package)529(True, [5, 12, 6, 1])530sage: BD = designs.AffineGeometryDesign(3, 1, GF(2))531sage: BD.is_block_design()532(True, [2, 8, 2, 1])533"""534from sage.rings.arith import binomial535from itertools import combinations536v = len(self.points())537b = len(self.blcks)538539# Definition and consistency of 'k' and 'r'540#541# r_list stores the degree of each point542k = len(self.blcks[0])543r_list = [0]*v544for block in self.blcks:545if len(block) != k:546return False547for x in block:548r_list[x] += 1549550r = r_list[0]551if any(x!=r for x in r_list):552return False553554# Definition and consistency of 'l' (lambda) and 't'555t_found_yet = False556557for t in range(2,min(v,k+1)):558559# Is lambda an integer ?560if (b*binomial(k,t)) % binomial(v,t) == 0:561l = (b*binomial(k,t))/binomial(v,t)562else:563continue564565# Associates to every t-subset of [v] the number of its occurrences566# as a subset of a block567t_counts = {}568for block in self.blcks:569for t_set in combinations(sorted(block),t):570t_counts[t_set] = t_counts.get(t_set,0)+1571572# Checking the consistency of l573l_values = t_counts.values()574575if all(l == x for x in l_values):576t_found_yet = True577t_lambda = t,l578579if t_found_yet:580t,l = t_lambda581return (True, [t,v,k,l])582else:583return (False, [0,0,0,0])584585def parameters(self, t=None):586"""587Returns `(t,v,k,lambda)`. Does not check if the input is a block588design.589590INPUT:591592- ``t`` -- `t` such that the design is a `t`-design.593594EXAMPLES::595596sage: from sage.combinat.designs.block_design import BlockDesign597sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="FanoPlane")598sage: BD.parameters(t=2)599(2, 7, 3, 1)600sage: BD.parameters(t=3)601(3, 7, 3, 0)602"""603if t is None:604from sage.misc.superseded import deprecation605deprecation(15664, "the 't' argument will become mandatory soon. 2"+606" is used when none is provided.")607t = 2608609v = len(self.points())610blks = self.blocks()611k = len(blks[int(0)])612b = len(blks)613#A = self.incidence_matrix()614#r = sum(A.rows()[0])615lmbda = int(b/(binomial(v, t)/binomial(k, t)))616return (t, v, k, lmbda)617618def points(self):619"""620Returns the list of points.621622EXAMPLES::623624sage: from sage.combinat.designs.block_design import BlockDesign625sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])626sage: BD.points()627[0, 1, 2, 3, 4, 5, 6]628"""629return self.pnts630631def points_from_gap(self):632"""633Literally pushes this block design over to GAP and returns the634points of that. Other than debugging, usefulness is unclear.635636REQUIRES: GAP's Design package.637638EXAMPLES::639640sage: from sage.combinat.designs.block_design import BlockDesign641sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])642sage: BD.points_from_gap() # optional - gap_packages (design package)643doctest:1: DeprecationWarning: Unless somebody protests this method will be removed, as nobody seems to know why it is there.644See http://trac.sagemath.org/14499 for details.645[1, 2, 3, 4, 5, 6, 7]646"""647from sage.misc.superseded import deprecation648deprecation(14499, ('Unless somebody protests this method will be '649'removed, as nobody seems to know why it is there.'))650from sage.interfaces.gap import gap651gap.load_package("design")652gD = self._gap_()653gP = gap.eval("BlockDesignPoints("+gD+")").replace("..", ",")654return range(eval(gP)[0], eval(gP)[1]+1)655656657