Path: blob/master/sage/combinat/designs/incidence_structures.py
4069 views
"""1Incidence structures.23An incidence structure is specified by a list of points, blocks,4and an incidence matrix ([1], [2]).56Classes:78IncidenceStructure910This software is released under the terms of the GNU General Public11License, version 2 or above (your choice). For details on12licensing, see the accompanying documentation.1314REFERENCES:1516- [1] Block designs and incidence structures from wikipedia,17http://en.wikipedia.org/wiki/Block_design18http://en.wikipedia.org/wiki/Incidence_structure1920- [2] E. Assmus, J. Key, Designs and their codes, CUP, 1992.2122This is a significantly modified form of part of the module23block_design.py (version 0.6) written by Peter Dobcsanyi24[email protected].2526Copyright 2007-2008 by David Joyner [email protected], Peter27Dobcsanyi [email protected].28"""2930from sage.matrix.matrix_space import MatrixSpace31from sage.rings.integer_ring import ZZ32from sage.rings.arith import binomial33from sage.misc.decorators import rename_keyword3435### utility functions -------------------------------------------------------3637def coordinatewise_product(L):38"""39L is a list of n-vectors or lists all of length n with a common40parent. This returns the vector whose i-th coordinate is the41product of the i-th coordinates of the vectors.4243EXAMPLES::4445sage: from sage.combinat.designs.incidence_structures import coordinatewise_product46sage: L = [[1,2,3],[-1,-1,-1],[5,7,11]]47sage: coordinatewise_product(L)48[-5, -14, -33]49"""50n = len(L[0])51ans = [1]*n52for x in L:53ans = [ans[i]*x[i] for i in range(n)]54return ans5556def IncidenceStructureFromMatrix(M, name=None):57"""58M must be a (0,1)-matrix. Creates a set of "points" from the rows59and a set of "blocks" from the columns.6061EXAMPLES::6263sage: from sage.combinat.designs.block_design import BlockDesign64sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])65sage: M = BD1.incidence_matrix()66sage: BD2 = IncidenceStructureFromMatrix(M)67sage: BD1 == BD268True69"""70nm = name71v = len(M.rows())72b = len(M.columns())73#points = range(v)74blocks = []75for i in range(b):76B = []77for j in range(v):78if M[i,j]!=0:79B.append(j)80blocks.append(B)81return IncidenceStructure(range(v), blocks, name=nm)8283class IncidenceStructure(object):84"""85This the base class for block designs.86"""87def __init__(self, pts, blks, inc_mat=None, name=None, test=True):88"""89The parameters are a pair pts, blks, both of which are a list (blks90is a list of lists). If each B in blks is contained in pts then the91incidence matrix inc_mat need not (and should not) be given.92Otherwise, inc_mat should be the ptsxblks (0,1)-matrix A for which93A_i,j=1 iff blks[j] is incident with pts[i].9495Optional keywords are: "inc_mat" (for giving the (0,1)-incidence96matrix), and "name" (a string, such as "Fano plane"). "test" (True97or False) - if True then each block must be a list of pts.9899EXAMPLES::100101sage: IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])102Incidence structure with 7 points and 7 blocks103104Points are sorted ::105106sage: 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]])107sage: BD1.points()108[0, 1, 2, 3, 4, 5, 6]109110TESTS:111112The following shows that Trac Ticket #11333 is fixed. ::113114sage: A = IncidenceStructure([0,1],[[0]])115sage: B = IncidenceStructure([1,0],[[0]])116sage: B==A117True118119REFERENCES:120121- E. Assmus, J. Key, Designs and their codes, CUP, 1992.122"""123bs = []124self.pnts = pts125self.pnts.sort()126v, blocks = len(pts), blks127for block in blocks:128if test:129for x in block:130if not(x in self.pnts):131raise ValueError('Point %s is not in the base set.'%x)132try:133y = block[:]134y.sort()135bs.append(y)136except:137bs.append(block)138bs.sort(cmp)139self.v = v140self.blcks = bs141self.name = name142self._incidence_matrix = inc_mat143144def __iter__(self):145"""146Iterator over the blocks.147148EXAMPLE::149150sage: sts = sage.combinat.designs.block_design.steiner_triple_system(9)151sage: list(sts)152[[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]]153"""154155return iter(self.blcks)156157158def __repr__(self):159"""160A print method.161162EXAMPLES::163164sage: from sage.combinat.designs.block_design import BlockDesign165sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])166sage: BD167Incidence structure with 7 points and 7 blocks168"""169repr = 'Incidence structure with %s points and %s blocks'%(len(self.pnts),len(self.blcks))170return repr171172def __str__(self):173"""174A print method.175176EXAMPLES::177178sage: from sage.combinat.designs.block_design import BlockDesign179sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])180sage: print BD181BlockDesign<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]]>182sage: 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]])183sage: print BD184IncidenceStructure<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]]>185"""186if self.name:187repr = '%s<points=%s, blocks=%s>'%(self.name, self.pnts, self.blcks)188else:189repr = 'IncidenceStructure<points=%s, blocks=%s>'%( self.pnts, self.blcks)190return repr191192def automorphism_group(self):193"""194Returns the subgroup of the automorphism group of the incidence195graph which respects the P B partition. This is (isomorphic to) the196automorphism group of the block design, although the degrees197differ.198199EXAMPLES::200201sage: from sage.combinat.designs.block_design import BlockDesign202sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])203sage: G = BD.automorphism_group(); G204Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)]205sage: BD = BlockDesign(4,[[0],[0,1],[1,2],[3,3]],test=False)206sage: G = BD.automorphism_group(); G207Permutation Group with generators [()]208sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])209sage: G = BD.automorphism_group(); G210Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)]211"""212from sage.groups.perm_gps.partn_ref.refinement_matrices import MatrixStruct213from sage.groups.perm_gps.permgroup import PermutationGroup214from sage.groups.perm_gps.permgroup_named import SymmetricGroup215M1 = self.incidence_matrix()216M2 = MatrixStruct(M1)217M2.run()218gens = M2.automorphism_group()[0]219v = len(self.points())220G = SymmetricGroup(v)221gns = []222for g in gens:223L = [j+1 for j in g]224gns.append(G(L))225return PermutationGroup(gns)226227def block_design_checker(self, t, v, k, lmbda, type=None):228"""229This is *not* a wrapper for GAP Design's IsBlockDesign. The GAP230Design function IsBlockDesign231http://www.gap-system.org/Manuals/pkg/design/htm/CHAP004.htmSSEC001.1232apparently simply checks the record structure and no mathematical233properties. Instead, the function below checks some necessary (but234not sufficient) "easy" identities arising from the identity.235236INPUT:237238- ``t`` - the t as in "t-design"239240- ``v`` - the number of points241242- ``k`` - the number of blocks incident to a point243244- ``lmbda`` - each t-tuple of points should be incident with245lmbda blocks246247- ``type`` - can be 'simple' or 'binary' or 'connected'248Depending on the option, this wraps IsBinaryBlockDesign,249IsSimpleBlockDesign, or IsConnectedBlockDesign.250251- Binary: no block has a repeated element.252253- Simple: no block is repeated.254255- Connected: its incidence graph is a connected graph.256257WARNING: This is very fast but can return false positives.258259EXAMPLES::260261sage: from sage.combinat.designs.block_design import BlockDesign262sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])263sage: BD.parameters()264(2, 7, 3, 1)265sage: BD.block_design_checker(2, 7, 3, 1)266True267sage: BD.block_design_checker(2, 7, 3, 1,"binary")268True269sage: BD.block_design_checker(2, 7, 3, 1,"connected")270True271sage: BD.block_design_checker(2, 7, 3, 1,"simple")272True273"""274from sage.sets.set import Set275if not(v == len(self.points())):276return False277b = lmbda*binomial(v,t)/binomial(k,t)278r = int(b*k/v)279if not(b == len(self.blocks())):280return False281if not(ZZ(v).divides(b*k)):282return False283A = self.incidence_matrix()284#k = sum(A.columns()[0])285#r = sum(A.rows()[0])286for i in range(b):287if not(sum(A.columns()[i]) == k):288return False289for i in range(v):290if not(sum(A.rows()[i]) == r):291return False292gD = self._gap_()293if type==None:294return True295if type=="binary":296for b in self.blocks():297if len(b)!=len(Set(b)):298return False299return True300if type=="simple":301B = self.blocks()302for b in B:303if B.count(b)>1:304return False305return True306if type=="connected":307Gamma = self.incidence_graph()308if Gamma.is_connected():309return True310else:311return False312313def blocks(self):314"""315Return the list of blocks.316317EXAMPLES::318319sage: from sage.combinat.designs.block_design import BlockDesign320sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])321sage: BD.blocks()322[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]323"""324B = self.blcks325B.sort()326return B327328def __eq__(self, other):329"""330Returns true if their points and blocks are equal (resp.).331332EXAMPLES::333334sage: from sage.combinat.designs.block_design import BlockDesign335sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])336sage: M = BD1.incidence_matrix()337sage: BD2 = IncidenceStructureFromMatrix(M)338sage: BD1 == BD2339True340"""341bool1 = self.points() == other.points()342bool2 = self.blocks() == other.blocks()343return (bool1 and bool2)344345def block_sizes(self):346"""347Return a list of block's sizes.348349EXAMPLES::350351sage: from sage.combinat.designs.block_design import BlockDesign352sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])353sage: BD.block_sizes()354[3, 3, 3, 3, 3, 3, 3]355"""356self._block_sizes = map(len,self.blocks())357return self._block_sizes358359def _gap_(self):360"""361Return the GAP string describing the design.362363EXAMPLES::364365sage: from sage.combinat.designs.block_design import BlockDesign366sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])367sage: BD._gap_()368'BlockDesign(7,[[1, 2, 3], [1, 4, 5], [1, 6, 7], [2, 4, 6], [2, 5, 7], [3, 4, 7], [3, 5, 6]])'369"""370from sage.sets.set import Set371B = self.blocks()372v = len(self.points())373gB = []374for b in B:375gB.append([x+1 for x in b])376return "BlockDesign("+str(v)+","+str(gB)+")"377378@rename_keyword(deprecated='Sage version 4.6', method="algorithm")379def dual_incidence_structure(self, algorithm=None):380"""381Wraps GAP Design's DualBlockDesign (see [1]). The dual of a block382design may not be a block design.383384Also can be called with ``dual_design``.385386REQUIRES: algorithm="gap" option requires GAP's Design package.387algorithm=None option does *not* require GAP's Design.388389EXAMPLES::390391sage: from sage.combinat.designs.block_design import BlockDesign392sage: D = BlockDesign(4, [[0,2],[1,2,3],[2,3]], test=False)393sage: D394Incidence structure with 4 points and 3 blocks395sage: D.dual_design()396Incidence structure with 3 points and 4 blocks397sage: print D.dual_design(algorithm="gap") # optional - gap_design398IncidenceStructure<points=[0, 1, 2], blocks=[[0], [0, 1, 2], [1], [1, 2]]>399sage: 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")400sage: BD401Incidence structure with 7 points and 7 blocks402sage: print BD.dual_design(algorithm="gap") # optional - gap_design403IncidenceStructure<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]]>404sage: BD.dual_incidence_structure()405Incidence structure with 7 points and 7 blocks406407REFERENCE:408409- Soicher, Leonard, Design package manual, available at410http://www.gap-system.org/Manuals/pkg/design/htm/CHAP003.htm411"""412from sage.interfaces.gap import gap, GapElement413from sage.sets.set import Set414from sage.misc.flatten import flatten415from sage.combinat.designs.block_design import BlockDesign416from sage.misc.functional import transpose417if algorithm=="gap":418gap.eval('LoadPackage("design")')419gD = self._gap_()420gap.eval("DD:=DualBlockDesign("+gD+")")421v = eval(gap.eval("DD.v"))422gblcks = eval(gap.eval("DD.blocks"))423gB = []424for b in gblcks:425gB.append([x-1 for x in b])426return IncidenceStructure(range(v), gB, name=None, test=False)427pts = self.blocks()428M = transpose(self.incidence_matrix())429blks = self.points()430return IncidenceStructure(pts, blks, M, name=None, test=False)431432dual_design = dual_incidence_structure # to preserve standard terminology433434def incidence_matrix(self):435"""436Return the incidence matrix A of the design. A is a (v x b) matrix437defined by: A[i,j] = 1 if i is in block B_j 0 otherwise438439EXAMPLES::440441sage: 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]])442sage: BD.block_sizes()443[3, 3, 3, 3, 3, 3, 3]444sage: BD.incidence_matrix()445[1 1 1 0 0 0 0]446[1 0 0 1 1 0 0]447[1 0 0 0 0 1 1]448[0 1 0 1 0 1 0]449[0 1 0 0 1 0 1]450[0 0 1 1 0 0 1]451[0 0 1 0 1 1 0]452"""453if self._incidence_matrix!=None:454return self._incidence_matrix455else:456v = len(self.points())457blks = self.blocks()458b = len(blks)459MS = MatrixSpace(ZZ,v,b)460A = MS(0)461#A = NUM.zeros((v,b), NUM.Int)462for i in range(v):463for j, b in enumerate(blks):464if i in b:465A[i,j] = 1466self._incidence_matrix = A467return A468469def incidence_graph(self):470"""471Returns the incidence graph of the design, where the incidence472matrix of the design is the adjacency matrix of the graph.473474EXAMPLE::475476sage: 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]])477sage: BD.incidence_graph()478Bipartite graph on 14 vertices479sage: A = BD.incidence_matrix()480sage: Graph(block_matrix([[A*0,A],[A.transpose(),A*0]])) == BD.incidence_graph()481True482483REFERENCE:484485- Sage Reference Manual on Graphs486"""487from sage.graphs.bipartite_graph import BipartiteGraph488A = self.incidence_matrix()489return BipartiteGraph(A)490#same as return Graph(block_matrix([[A*0,A],[A.transpose(),A*0]]))491492def is_block_design(self):493"""494Returns a pair True, pars if the incidence structure is a t-design,495for some t, where pars is the list of parameters [t, v, k, lmbda].496The largest possible t is returned, provided t=10.497498EXAMPLES::499500sage: 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]])501sage: BD.is_block_design()502(True, [2, 7, 3, 1])503sage: BD.block_design_checker(2, 7, 3, 1)504True505sage: BD = WittDesign(9) # requires optional gap package 'design'506sage: BD.is_block_design() # requires optional gap package 'design'507(True, [2, 9, 3, 1])508sage: BD = WittDesign(12) # requires optional gap package 'design'509sage: BD.is_block_design() # requires optional gap package 'design'510(True, [5, 12, 6, 1])511sage: BD = AffineGeometryDesign(3, 1, GF(2))512sage: BD.is_block_design()513(True, [2, 8, 2, 2])514"""515from sage.combinat.designs.incidence_structures import coordinatewise_product516from sage.combinat.combinat import unordered_tuples, combinations517from sage.coding.linear_code import hamming_weight518A = self.incidence_matrix()519v = len(self.points())520b = len(self.blocks())521k = sum(A.columns()[0])522rowsA = A.rows()523VS = rowsA[0].parent()524r = sum(rowsA[0])525for i in range(b):526if not(sum(A.columns()[i]) == k):527return False528for i in range(v):529if not(sum(A.rows()[i]) == r):530return False531t_found_yet = False532lambdas = []533for t in range(2,min(v,11)):534#print t535L1 = combinations(range(v),t)536L2 = [[rowsA[i] for i in L] for L in L1]537#print t,len(L2)538lmbda = hamming_weight(VS(coordinatewise_product(L2[0])))539lambdas.append(lmbda)540pars = [t,v,k,lmbda]541#print pars542for ell in L2:543a = hamming_weight(VS(coordinatewise_product(ell)))544if not(a == lmbda) or a==0:545if not(t_found_yet):546pars = [t-1,v,k,lambdas[t-3]]547return False, pars548else:549#print pars, lambdas550pars = [t-1,v,k,lambdas[t-3]]551return True, pars552t_found_yet = True553pars = [t-1,v,k,lambdas[t-3]]554return True, pars555556def parameters(self, t=2):557"""558Returns (t,v,k,lambda). Does not check if the input is a block559design. Uses t=2 by default.560561EXAMPLES::562563sage: from sage.combinat.designs.block_design import BlockDesign564sage: 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")565sage: BD.parameters()566(2, 7, 3, 1)567sage: BD.parameters(t=3)568(3, 7, 3, 0)569"""570v = len(self.points())571blks = self.blocks()572k = len(blks[int(0)])573b = len(blks)574#A = self.incidence_matrix()575#r = sum(A.rows()[0])576lmbda = int(b/(binomial(v,t)/binomial(k,t)))577return (t,v,k,lmbda)578579def points(self):580"""581Returns the list of points.582583EXAMPLES::584585sage: from sage.combinat.designs.block_design import BlockDesign586sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])587sage: BD.points()588[0, 1, 2, 3, 4, 5, 6]589"""590return self.pnts591592def points_from_gap(self):593"""594Literally pushes this block design over to GAP and returns the595points of that. Other than debugging, usefulness is unclear.596597REQUIRES: GAP's Design package.598599EXAMPLES::600601sage: from sage.combinat.designs.block_design import BlockDesign602sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])603sage: BD.points_from_gap() # requires optional gap package 'design'604[1, 2, 3, 4, 5, 6, 7]605"""606from sage.interfaces.gap import gap, GapElement607from sage.sets.set import Set608gap.eval('LoadPackage("design")')609gD = self._gap_()610gP = gap.eval("BlockDesignPoints("+gD+")").replace("..",",")611return range(eval(gP)[0],eval(gP)[1]+1)612613614615616617