Path: blob/master/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx
4077 views
"""1Graph-theoretic partition backtrack functions23DOCTEST:4sage: import sage.groups.perm_gps.partn_ref.refinement_graphs56REFERENCE:78[1] McKay, Brendan D. Practical Graph Isomorphism. Congressus Numerantium,9Vol. 30 (1981), pp. 45-87.1011"""1213#*****************************************************************************14# Copyright (C) 2006 - 2011 Robert L. Miller <[email protected]>15#16# Distributed under the terms of the GNU General Public License (GPL)17# http://www.gnu.org/licenses/18#*****************************************************************************1920include 'data_structures_pyx.pxi' # includes bitsets2122def isomorphic(G1, G2, partn, ordering2, dig, use_indicator_function, sparse=False):23"""24Tests whether two graphs are isomorphic.2526sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic2728sage: G = Graph(2)29sage: H = Graph(2)30sage: isomorphic(G, H, [[0,1]], [0,1], 0, 1)31{0: 0, 1: 1}32sage: isomorphic(G, H, [[0,1]], [0,1], 0, 1)33{0: 0, 1: 1}34sage: isomorphic(G, H, [[0],[1]], [0,1], 0, 1)35{0: 0, 1: 1}36sage: isomorphic(G, H, [[0],[1]], [1,0], 0, 1)37{0: 1, 1: 0}3839sage: G = Graph(3)40sage: H = Graph(3)41sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)42{0: 0, 1: 1, 2: 2}43sage: G.add_edge(0,1)44sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)45False46sage: H.add_edge(1,2)47sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)48{0: 1, 1: 2, 2: 0}4950"""51cdef PartitionStack *part52cdef int *output, *ordering53cdef CGraph G54cdef GraphStruct GS1 = GraphStruct()55cdef GraphStruct GS2 = GraphStruct()56cdef GraphStruct GS57cdef int i, j, k, n = -1, cell_len58cdef list partition, cell5960from sage.graphs.all import Graph, DiGraph61from sage.graphs.generic_graph import GenericGraph62from copy import copy63which_G = 164for G_in in [G1, G2]:65if which_G == 1:66GS = GS167first=True68else:69GS = GS270first=False71if isinstance(G_in, GenericGraph):72if n == -1:73n = G_in.num_verts()74elif n != G_in.num_verts():75return False76if G_in.vertices() != range(n):77G_in = copy(G_in)78to = G_in.relabel(return_map=True)79frm = {}80for v in to.iterkeys():81frm[to[v]] = v82if first:83partition = [[to[v] for v in cell] for cell in partn]84else:85if first:86partition = partn87to = range(n)88frm = to89if sparse:90G = SparseGraph(n)91else:92G = DenseGraph(n)93if G_in.is_directed():94for i,j in G_in.edge_iterator(labels=False):95G.add_arc(i,j)96else:97for i,j in G_in.edge_iterator(labels=False):98G.add_arc(i,j)99G.add_arc(j,i)100elif isinstance(G_in, CGraph):101G = <CGraph> G_in102if n == -1:103n = G.num_verts104elif n != G.num_verts:105return False106to = {}107for a in G.verts(): to[a]=a108frm = to109if first:110partition = partn111else:112raise TypeError("G must be a Sage graph.")113if first: frm1=frm;to1=to114else: frm2=frm;to2=to115GS.G = G116GS.directed = 1 if dig else 0117GS.use_indicator = 1 if use_indicator_function else 0118which_G += 1119120if n == 0:121return {}122123part = PS_from_list(partition)124ordering = <int *> sage_malloc(n * sizeof(int))125if part is NULL or ordering is NULL:126if part is not NULL: PS_dealloc(part)127if ordering is not NULL: sage_free(ordering)128raise MemoryError129for i from 0 <= i < n:130ordering[i] = to2[ordering2[i]]131132GS1.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))133GS2.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))134if GS1.scratch is NULL or GS2.scratch is NULL:135if GS1.scratch is not NULL: sage_free(GS1.scratch)136if GS2.scratch is not NULL: sage_free(GS2.scratch)137PS_dealloc(part)138sage_free(ordering)139raise MemoryError140141output = double_coset(<void *>GS1, <void *>GS2, part, ordering, n, &all_children_are_equivalent, &refine_by_degree, &compare_graphs, NULL)142143PS_dealloc(part)144sage_free(ordering)145sage_free(GS1.scratch)146sage_free(GS2.scratch)147148if output is NULL:149return False150else:151output_py = dict([[frm1[i], frm2[output[i]]] for i from 0 <= i < n])152sage_free(output)153return output_py154155def search_tree(G_in, partition, lab=True, dig=False, dict_rep=False, certify=False,156verbosity=0, use_indicator_function=True, sparse=True,157base=False, order=False):158"""159Compute canonical labels and automorphism groups of graphs.160161INPUT:162G_in -- a Sage graph163partition -- a list of lists representing a partition of the vertices164lab -- if True, compute and return the canonical label in addition to the165automorphism group.166dig -- set to True for digraphs and graphs with loops. If True, does not167use optimizations based on Lemma 2.25 in [1] that are valid only for168simple graphs.169dict_rep -- if True, return a dictionary with keys the vertices of the170input graph G_in and values elements of the set the permutation group171acts on. (The point is that graphs are arbitrarily labelled, often1720..n-1, and permutation groups always act on 1..n. This dictionary173maps vertex labels (such as 0..n-1) to the domain of the permutations.)174certify -- if True, return the permutation from G to its canonical label.175verbosity -- currently ignored176use_indicator_function -- option to turn off indicator function177(True is generally faster)178sparse -- whether to use sparse or dense representation of the graph179(ignored if G is already a CGraph - see sage.graphs.base)180base -- whether to return the first sequence of split vertices (used in181computing the order of the group)182order -- whether to return the order of the automorphism group183184OUTPUT:185Depends on the options. If more than one thing is returned, they are in a186tuple in the following order:187list of generators in list-permutation format -- always188dict -- if dict_rep189graph -- if lab190dict -- if certify191list -- if base192integer -- if order193194DOCTEST:195sage: st = sage.groups.perm_gps.partn_ref.refinement_graphs.search_tree196sage: from sage.graphs.base.dense_graph import DenseGraph197sage: from sage.graphs.base.sparse_graph import SparseGraph198199Graphs on zero vertices:200sage: G = Graph()201sage: st(G, [[]], order=True)202([], Graph on 0 vertices, 1)203204Graphs on one vertex:205sage: G = Graph(1)206sage: st(G, [[0]], order=True)207([], Graph on 1 vertex, 1)208209Graphs on two vertices:210sage: G = Graph(2)211sage: st(G, [[0,1]], order=True)212([[1, 0]], Graph on 2 vertices, 2)213sage: st(G, [[0],[1]], order=True)214([], Graph on 2 vertices, 1)215sage: G.add_edge(0,1)216sage: st(G, [[0,1]], order=True)217([[1, 0]], Graph on 2 vertices, 2)218sage: st(G, [[0],[1]], order=True)219([], Graph on 2 vertices, 1)220221Graphs on three vertices:222sage: G = Graph(3)223sage: st(G, [[0,1,2]], order=True)224([[0, 2, 1], [1, 0, 2]], Graph on 3 vertices, 6)225sage: st(G, [[0],[1,2]], order=True)226([[0, 2, 1]], Graph on 3 vertices, 2)227sage: st(G, [[0],[1],[2]], order=True)228([], Graph on 3 vertices, 1)229sage: G.add_edge(0,1)230sage: st(G, [range(3)], order=True)231([[1, 0, 2]], Graph on 3 vertices, 2)232sage: st(G, [[0],[1,2]], order=True)233([], Graph on 3 vertices, 1)234sage: st(G, [[0,1],[2]], order=True)235([[1, 0, 2]], Graph on 3 vertices, 2)236237The Dodecahedron has automorphism group of size 120:238sage: G = graphs.DodecahedralGraph()239sage: Pi = [range(20)]240sage: st(G, Pi, order=True)[2]241120242243The three-cube has automorphism group of size 48:244sage: G = graphs.CubeGraph(3)245sage: G.relabel()246sage: Pi = [G.vertices()]247sage: st(G, Pi, order=True)[2]24848249250We obtain the same output using different types of Sage graphs:251sage: G = graphs.DodecahedralGraph()252sage: GD = DenseGraph(20)253sage: GS = SparseGraph(20)254sage: for i,j,_ in G.edge_iterator():255... GD.add_arc(i,j); GD.add_arc(j,i)256... GS.add_arc(i,j); GS.add_arc(j,i)257sage: Pi=[range(20)]258sage: a,b = st(G, Pi)259sage: asp,bsp = st(GS, Pi)260sage: ade,bde = st(GD, Pi)261sage: bsg = Graph()262sage: bdg = Graph()263sage: for i in range(20):264... for j in range(20):265... if bsp.has_arc(i,j):266... bsg.add_edge(i,j)267... if bde.has_arc(i,j):268... bdg.add_edge(i,j)269sage: print a, b.graph6_string()270[[0, 19, 3, 2, 6, 5, 4, 17, 18, 11, 10, 9, 13, 12, 16, 15, 14, 7, 8, 1], [0, 1, 8, 9, 13, 14, 7, 6, 2, 3, 19, 18, 17, 4, 5, 15, 16, 12, 11, 10], [1, 8, 9, 10, 11, 12, 13, 14, 7, 6, 2, 3, 4, 5, 15, 16, 17, 18, 19, 0]] S?[PG__OQ@?_?_?P?CO?_?AE?EC?Ac?@O271sage: a == asp272True273sage: a == ade274True275sage: b == bsg276True277sage: b == bdg278True279280Cubes!281sage: C = graphs.CubeGraph(1)282sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order2832284sage: C = graphs.CubeGraph(2)285sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order2868287sage: C = graphs.CubeGraph(3)288sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order28948290sage: C = graphs.CubeGraph(4)291sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order292384293sage: C = graphs.CubeGraph(5)294sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order2953840296sage: C = graphs.CubeGraph(6)297sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order29846080299300One can also turn off the indicator function (note- this will take longer)301sage: D1 = DiGraph({0:[2],2:[0],1:[1]}, loops=True)302sage: D2 = DiGraph({1:[2],2:[1],0:[0]}, loops=True)303sage: a,b = st(D1, [D1.vertices()], dig=True, use_indicator_function=False)304sage: c,d = st(D2, [D2.vertices()], dig=True, use_indicator_function=False)305sage: b==d306True307308This example is due to Chris Godsil:309sage: HS = graphs.HoffmanSingletonGraph()310sage: alqs = [Set(c) for c in (HS.complement()).cliques_maximum()]311sage: Y = Graph([alqs, lambda s,t: len(s.intersection(t))==0])312sage: Y0,Y1 = Y.connected_components_subgraphs()313sage: st(Y0, [Y0.vertices()])[1] == st(Y1, [Y1.vertices()])[1]314True315sage: st(Y0, [Y0.vertices()])[1] == st(HS, [HS.vertices()])[1]316True317sage: st(HS, [HS.vertices()])[1] == st(Y1, [Y1.vertices()])[1]318True319320Certain border cases need to be tested as well:321sage: G = Graph('Fll^G')322sage: a,b,c = st(G, [range(G.num_verts())], order=True); b323Graph on 7 vertices324sage: c32548326sage: G = Graph(21)327sage: st(G, [range(G.num_verts())], order=True)[2] == factorial(21)328True329330sage: G = Graph('^????????????????????{??N??@w??FaGa?PCO@CP?AGa?_QO?Q@G?CcA??cc????Bo????{????F_')331sage: perm = {3:15, 15:3}332sage: H = G.relabel(perm, inplace=False)333sage: st(G, [range(G.num_verts())])[1] == st(H, [range(H.num_verts())])[1]334True335336sage: st(Graph(':Dkw'), [range(5)], lab=False, dig=True)337[[4, 1, 2, 3, 0], [0, 2, 1, 3, 4]]338339"""340cdef CGraph G341cdef int i, j, n342cdef Integer I343cdef aut_gp_and_can_lab *output344cdef PartitionStack *part345from sage.graphs.all import Graph, DiGraph346from sage.graphs.generic_graph import GenericGraph347from copy import copy348if isinstance(G_in, GenericGraph):349n = G_in.num_verts()350if G_in.vertices() != range(n):351G_in = copy(G_in)352to = G_in.relabel(return_map=True)353frm = {}354for v in to.iterkeys():355frm[to[v]] = v356partition = [[to[v] for v in cell] for cell in partition]357else:358to = dict(enumerate(range(n)))359frm = to360if sparse:361G = SparseGraph(n)362else:363G = DenseGraph(n)364if G_in.is_directed():365for i,j in G_in.edge_iterator(labels=False):366G.add_arc(i,j)367else:368for i,j in G_in.edge_iterator(labels=False):369G.add_arc(i,j)370G.add_arc(j,i)371elif isinstance(G_in, CGraph):372G = <CGraph> G_in373n = G.num_verts374to = {}375for a in G.verts(): to[a]=a376frm = to377else:378raise TypeError("G must be a Sage graph.")379380cdef GraphStruct GS = GraphStruct()381GS.G = G382GS.directed = 1 if dig else 0383GS.use_indicator = 1 if use_indicator_function else 0384385if n == 0:386return_tuple = [[]]387if dict_rep:388return_tuple.append({})389if lab:390if isinstance(G_in, GenericGraph):391G_C = copy(G_in)392else:393if isinstance(G, SparseGraph):394G_C = SparseGraph(n)395else:396G_C = DenseGraph(n)397return_tuple.append(G_C)398if certify:399return_tuple.append({})400if base:401return_tuple.append([])402if order:403return_tuple.append(Integer(1))404if len(return_tuple) == 1:405return return_tuple[0]406else:407return tuple(return_tuple)408409GS.scratch = <int *> sage_malloc( (3*G.num_verts + 1) * sizeof(int) )410part = PS_from_list(partition)411if GS.scratch is NULL or part is NULL:412if part is not NULL: PS_dealloc(part)413if GS.scratch is not NULL: sage_free(GS.scratch)414raise MemoryError415416lab_new = lab or certify417output = get_aut_gp_and_can_lab(<void *>GS, part, G.num_verts, &all_children_are_equivalent, &refine_by_degree, &compare_graphs, lab, NULL)418sage_free( GS.scratch )419# prepare output420list_of_gens = []421for i from 0 <= i < output.num_gens:422list_of_gens.append([output.generators[j+i*G.num_verts] for j from 0 <= j < G.num_verts])423return_tuple = [list_of_gens]424if dict_rep:425ddd = {}426for v in frm.iterkeys():427ddd[frm[v]] = v if v != 0 else n428return_tuple.append(ddd)429if lab:430if isinstance(G_in, GenericGraph):431G_C = copy(G_in)432G_C.relabel([output.relabeling[i] for i from 0 <= i < n])433else:434if isinstance(G, SparseGraph):435G_C = SparseGraph(n)436else:437G_C = DenseGraph(n)438for i from 0 <= i < n:439for j in G.out_neighbors(i):440G_C.add_arc(output.relabeling[i],output.relabeling[j])441return_tuple.append(G_C)442if certify:443dd = {}444for i from 0 <= i < G.num_verts:445dd[frm[i]] = output.relabeling[i]446return_tuple.append(dd)447if base:448return_tuple.append([output.group.base_orbits[i][0] for i from 0 <= i < output.group.base_size])449if order:450I = Integer()451SC_order(output.group, 0, I.value)452return_tuple.append(I)453PS_dealloc(part)454sage_free(output.generators)455SC_dealloc(output.group)456if lab_new:457sage_free(output.relabeling)458sage_free(output)459if len(return_tuple) == 1:460return return_tuple[0]461else:462return tuple(return_tuple)463464cdef int refine_by_degree(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len):465r"""466Refines the input partition by checking degrees of vertices to the given467cells.468469INPUT:470PS -- a partition stack, whose finest partition is the partition to be471refined.472S -- a graph struct object, which contains scratch space, the graph in473question, and some flags.474cells_to_refine_by -- a list of pointers to cells to check degrees against475in refining the other cells (updated in place). Must be allocated to476length at least the degree of PS, since the array may grow477ctrb_len -- how many cells in cells_to_refine_by478479OUTPUT:480481An integer invariant under the orbits of $S_n$. That is, if $\gamma$ is a482permutation of the vertices, then483$$ I(G, PS, cells_to_refine_by) = I( \gamma(G), \gamma(PS), \gamma(cells_to_refine_by) ) .$$484485"""486cdef GraphStruct GS = <GraphStruct> S487cdef CGraph G = GS.G488cdef int current_cell_against = 0489cdef int current_cell, i, r490cdef int first_largest_subcell491cdef int invariant = 1492cdef int max_degree493cdef int *degrees = GS.scratch # length 3n+1494cdef bint necessary_to_split_cell495cdef int against_index496while not PS_is_discrete(PS) and current_cell_against < ctrb_len:497invariant += 1498current_cell = 0499while current_cell < PS.degree:500invariant += 50501i = current_cell502necessary_to_split_cell = 0503max_degree = 0504while 1:505degrees[i-current_cell] = degree(PS, G, i, cells_to_refine_by[current_cell_against], 0)506if degrees[i-current_cell] != degrees[0]:507necessary_to_split_cell = 1508if degrees[i-current_cell] > max_degree:509max_degree = degrees[i-current_cell]510i += 1511if PS.levels[i-1] <= PS.depth:512break513# now, i points to the next cell (before refinement)514if necessary_to_split_cell:515invariant += 10516first_largest_subcell = sort_by_function(PS, current_cell, degrees)517invariant += first_largest_subcell + max_degree518against_index = current_cell_against519while against_index < ctrb_len:520if cells_to_refine_by[against_index] == current_cell:521cells_to_refine_by[against_index] = first_largest_subcell522break523against_index += 1524r = current_cell525while 1:526if r == current_cell or PS.levels[r-1] == PS.depth:527if r != first_largest_subcell:528cells_to_refine_by[ctrb_len] = r529ctrb_len += 1530r += 1531if r >= i:532break533invariant += (i - current_cell)534current_cell = i535if GS.directed:536# if we are looking at a digraph, also compute537# the reverse degrees and sort by them538current_cell = 0539while current_cell < PS.degree: # current_cell is still a valid cell540invariant += 20541i = current_cell542necessary_to_split_cell = 0543max_degree = 0544while 1:545degrees[i-current_cell] = degree(PS, G, i, cells_to_refine_by[current_cell_against], 1)546if degrees[i-current_cell] != degrees[0]:547necessary_to_split_cell = 1548if degrees[i-current_cell] > max_degree:549max_degree = degrees[i-current_cell]550i += 1551if PS.levels[i-1] <= PS.depth:552break553# now, i points to the next cell (before refinement)554if necessary_to_split_cell:555invariant += 7556first_largest_subcell = sort_by_function(PS, current_cell, degrees)557invariant += first_largest_subcell + max_degree558against_index = current_cell_against559while against_index < ctrb_len:560if cells_to_refine_by[against_index] == current_cell:561cells_to_refine_by[against_index] = first_largest_subcell562break563against_index += 1564against_index = ctrb_len565r = current_cell566while 1:567if r == current_cell or PS.levels[r-1] == PS.depth:568if r != first_largest_subcell:569cells_to_refine_by[against_index] = r570against_index += 1571ctrb_len += 1572r += 1573if r >= i:574break575invariant += (i - current_cell)576current_cell = i577current_cell_against += 1578if GS.use_indicator:579return invariant580else:581return 0582583cdef int compare_graphs(int *gamma_1, int *gamma_2, void *S1, void *S2):584r"""585Compare gamma_1(S1) and gamma_2(S2).586587Return return -1 if gamma_1(S1) < gamma_2(S2), 0 if gamma_1(S1) ==588gamma_2(S2), 1 if gamma_1(S1) > gamma_2(S2). (Just like the python589\code{cmp}) function.590591INPUT:592gamma_1, gamma_2 -- list permutations (inverse)593S1, S2 -- graph struct objects594595"""596cdef int i, j597cdef GraphStruct GS1 = <GraphStruct> S1598cdef GraphStruct GS2 = <GraphStruct> S2599cdef CGraph G1 = GS1.G600cdef CGraph G2 = GS2.G601for i from 0 <= i < G1.num_verts:602for j from 0 <= j < G1.num_verts:603if G1.has_arc_unsafe(gamma_1[i], gamma_1[j]):604if not G2.has_arc_unsafe(gamma_2[i], gamma_2[j]):605return 1606elif G2.has_arc_unsafe(gamma_2[i], gamma_2[j]):607return -1608return 0609610cdef bint all_children_are_equivalent(PartitionStack *PS, void *S):611"""612Return True if every refinement of the current partition results in the613same structure.614615WARNING:616Converse does not hold in general! See Lemma 2.25 of [1] for details.617618INPUT:619PS -- the partition stack to be checked620S -- a graph struct object621"""622cdef GraphStruct GS = <GraphStruct> S623if GS.directed:624return 0625cdef CGraph G = GS.G626cdef int i, n = PS.degree627cdef bint in_cell = 0628cdef int nontrivial_cells = 0629cdef int total_cells = PS_num_cells(PS)630if n <= total_cells + 4:631return 1632for i from 0 <= i < n-1:633if PS.levels[i] <= PS.depth:634if in_cell:635nontrivial_cells += 1636in_cell = 0637else:638in_cell = 1639if in_cell:640nontrivial_cells += 1641if n == total_cells + nontrivial_cells:642return 1643if n == total_cells + nontrivial_cells + 1:644return 1645return 0646647cdef inline int degree(PartitionStack *PS, CGraph G, int entry, int cell_index, bint reverse):648"""649Returns the number of edges from the vertex corresponding to entry to650vertices in the cell corresponding to cell_index.651652INPUT:653PS -- the partition stack to be checked654S -- a graph struct object655entry -- the position of the vertex in question in the entries of PS656cell_index -- the starting position of the cell in question in the entries657of PS658reverse -- whether to check for arcs in the other direction659"""660cdef int num_arcs = 0661entry = PS.entries[entry]662if not reverse:663while 1:664if G.has_arc_unsafe(PS.entries[cell_index], entry):665num_arcs += 1666if PS.levels[cell_index] > PS.depth:667cell_index += 1668else:669break670else:671while 1:672if G.has_arc_unsafe(entry, PS.entries[cell_index]):673num_arcs += 1674if PS.levels[cell_index] > PS.depth:675cell_index += 1676else:677break678return num_arcs679680def all_labeled_graphs(n):681"""682Returns all labeled graphs on n vertices {0,1,...,n-1}. Used in683classifying isomorphism types (naive approach), and more importantly684in benchmarking the search algorithm.685686EXAMPLE::687688sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import all_labeled_graphs689sage: st = sage.groups.perm_gps.partn_ref.refinement_graphs.search_tree690sage: Glist = {}691sage: Giso = {}692sage: for n in [1..5]: # long time (4s on sage.math, 2011)693... Glist[n] = all_labeled_graphs(n)694... Giso[n] = []695... for g in Glist[n]:696... a, b = st(g, [range(n)])697... inn = False698... for gi in Giso[n]:699... if b == gi:700... inn = True701... if not inn:702... Giso[n].append(b)703sage: for n in Giso: # long time704... print n, len(Giso[n])7051 17062 27073 47084 117095 34710711"""712from sage.graphs.all import Graph713TE = []714for i in range(n):715for j in range(i):716TE.append((i, j))717m = len(TE)718Glist= []719for i in range(2**m):720G = Graph(n)721b = Integer(i).binary()722b = '0'*(m-len(b)) + b723for i in range(m):724if int(b[i]):725G.add_edge(TE[i])726Glist.append(G)727return Glist728729730def random_tests(num=10, n_max=60, perms_per_graph=5):731"""732Tests to make sure that C(gamma(G)) == C(G) for random permutations gamma733and random graphs G, and that isomorphic returns an isomorphism.734735INPUT:736num -- run tests for this many graphs737n_max -- test graphs with at most this many vertices738perms_per_graph -- test each graph with this many random permutations739740DISCUSSION:741742This code generates num random graphs G on at most n_max vertices. The743density of edges is chosen randomly between 0 and 1.744745For each graph G generated, we uniformly generate perms_per_graph random746permutations and verify that the canonical labels of G and the image of G747under the generated permutation are equal, and that the isomorphic function748returns an isomorphism.749750TESTS::751752sage: import sage.groups.perm_gps.partn_ref.refinement_graphs753sage: sage.groups.perm_gps.partn_ref.refinement_graphs.random_tests() # long time754All passed: 200 random tests on 20 graphs.755"""756from sage.misc.prandom import random, randint757from sage.graphs.graph_generators import GraphGenerators758from sage.graphs.digraph_generators import DiGraphGenerators759from sage.combinat.permutation import Permutations760from copy import copy761cdef int i, j, num_tests = 0, num_graphs = 0762GG = GraphGenerators()763DGG = DiGraphGenerators()764for mmm in range(num):765p = random()766n = randint(1, n_max)767S = Permutations(n)768769G = GG.RandomGNP(n, p)770H = copy(G)771for i from 0 <= i < perms_per_graph:772G = copy(H)773G1 = search_tree(G, [G.vertices()])[1]774perm = list(S.random_element())775perm = [perm[j]-1 for j from 0 <= j < n]776G.relabel(perm)777G2 = search_tree(G, [G.vertices()])[1]778if G1 != G2:779print "search_tree FAILURE: graph6-"780print H.graph6_string()781print perm782return783isom = isomorphic(G, H, [range(n)], range(n), 0, 1)784if not isom or G.relabel(isom, inplace=False) != H:785print "isom FAILURE: graph6-"786print H.graph6_string()787print perm788return789790D = DGG.RandomDirectedGNP(n, p)791D.allow_loops(True)792for i from 0 <= i < n:793if random() < p:794D.add_edge(i,i)795E = copy(D)796for i from 0 <= i < perms_per_graph:797D = copy(E)798D1 = search_tree(D, [D.vertices()], dig=True)[1]799perm = list(S.random_element())800perm = [perm[j]-1 for j from 0 <= j < n]801D.relabel(perm)802D2 = search_tree(D, [D.vertices()], dig=True)[1]803if D1 != D2:804print "search_tree FAILURE: dig6-"805print E.dig6_string()806print perm807return808isom = isomorphic(D, E, [range(n)], range(n), 1, 1)809if not isom or D.relabel(isom, inplace=False) != E:810print "isom FAILURE: dig6-"811print E.dig6_string()812print perm813print isom814return815num_tests += 4*perms_per_graph816num_graphs += 2817print "All passed: %d random tests on %d graphs."%(num_tests, num_graphs)818819def orbit_partition(gamma, list_perm=False):820r"""821Assuming that G is a graph on vertices 0,1,...,n-1, and gamma is an822element of SymmetricGroup(n), returns the partition of the vertex823set determined by the orbits of gamma, considered as action on the824set 1,2,...,n where we take 0 = n. In other words, returns the825partition determined by a cyclic representation of gamma.826827INPUT:828829830- ``list_perm`` - if True, assumes831``gamma`` is a list representing the map832`i \mapsto ``gamma``[i]`.833834835EXAMPLES::836837sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import orbit_partition838sage: G = graphs.PetersenGraph()839sage: S = SymmetricGroup(10)840sage: gamma = S('(10,1,2,3,4)(5,6,7)(8,9)')841sage: orbit_partition(gamma)842[[1, 2, 3, 4, 0], [5, 6, 7], [8, 9]]843sage: gamma = S('(10,5)(1,6)(2,7)(3,8)(4,9)')844sage: orbit_partition(gamma)845[[1, 6], [2, 7], [3, 8], [4, 9], [5, 0]]846"""847if list_perm:848n = len(gamma)849seen = [1] + [0]*(n-1)850i = 0851p = 0852partition = [[0]]853while sum(seen) < n:854if gamma[i] != partition[p][0]:855partition[p].append(gamma[i])856i = gamma[i]857seen[i] = 1858else:859for j in range(n):860if seen[j]==0:861i = j862break863partition.append([i])864p += 1865seen[i] = 1866return partition867else:868n = len(gamma.list())869l = []870for i in range(1,n+1):871orb = gamma.orbit(i)872if orb not in l: l.append(orb)873for i in l:874for j in range(len(i)):875if i[j] == n:876i[j] = 0877return l878879def perm_group_elt(lperm):880"""881Given a list permutation of the set 0, 1, ..., n-1, returns the882corresponding PermutationGroupElement where we take 0 = n.883884EXAMPLE::885886sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import perm_group_elt887sage: perm_group_elt([0,2,1])888(1,2)889sage: perm_group_elt([1,2,0])890(1,2,3)891"""892from sage.groups.perm_gps.permgroup_named import SymmetricGroup893n = len(lperm)894S = SymmetricGroup(n)895Part = orbit_partition(lperm, list_perm=True)896gens = []897for z in Part:898if len(z) > 1:899if 0 in z:900zed = z.index(0)901generator = z[:zed] + [n] + z[zed+1:]902gens.append(tuple(generator))903else:904gens.append(tuple(z))905E = S(gens)906return E907908def coarsest_equitable_refinement(CGraph G, list partition, bint directed):909"""910Returns the coarsest equitable refinement of ``partition`` for ``G``.911912This is a helper function for the graph function of the same name.913914DOCTEST (More thorough testing in ``sage/graphs/graph.py``)::915916sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import coarsest_equitable_refinement917sage: from sage.graphs.base.sparse_graph import SparseGraph918sage: coarsest_equitable_refinement(SparseGraph(7), [[0], [1,2,3,4], [5,6]], 0)919[[0], [1, 2, 3, 4], [5, 6]]920921"""922cdef int i, j = 0, k = 0, n = G.num_verts923924# set up partition stack and graph struct925cdef PartitionStack *nu = PS_new(n, 0)926for cell in partition:927for i in cell:928nu.entries[j] = i929nu.levels[j] = n930j += 1931nu.levels[j-1] = 0932PS_move_min_to_front(nu, k, j-1)933k = j934935cdef GraphStruct GS = GraphStruct()936GS.G = G937GS.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))938if GS.scratch is NULL:939PS_dealloc(nu)940raise MemoryError941GS.directed = directed942GS.use_indicator = 0943944# set up cells to refine by945cdef int num_cells = len(partition)946cdef int *alpha = <int *>sage_malloc(n * sizeof(int))947if alpha is NULL:948PS_dealloc(nu)949sage_free(GS.scratch)950raise MemoryError951j = 0952for i from 0 <= i < num_cells:953alpha[i] = j954j += len(partition[i])955956# refine, and get the result957refine_by_degree(nu, <void *>GS, alpha, num_cells)958959eq_part = []960cell = []961for i from 0 <= i < n:962cell.append(nu.entries[i])963if nu.levels[i] <= 0:964eq_part.append(cell)965cell = []966967PS_dealloc(nu)968sage_free(GS.scratch)969sage_free(alpha)970971return eq_part972973def get_orbits(list gens, int n):974"""975Compute orbits given a list of generators of a permutation group, in list976format.977978This is a helper function for automorphism groups of graphs.979980DOCTEST (More thorough testing in ``sage/graphs/graph.py``)::981982sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits983sage: get_orbits([[1,2,3,0,4,5], [0,1,2,3,5,4]], 6)984[[0, 1, 2, 3], [4, 5]]985986"""987cdef int i, j988if len(gens) == 0:989return [[i] for i from 0 <= i < n]990991cdef OrbitPartition *OP = OP_new(n)992cdef int *perm_ints = <int *> sage_malloc(n * sizeof(int))993if perm_ints is NULL:994OP_dealloc(OP)995raise MemoryError996997for gen in gens:998for i from 0 <= i < n:999perm_ints[i] = gen[i]1000OP_merge_list_perm(OP, perm_ints)10011002orbit_dict = {}1003for i from 0 <= i < n:1004j = OP_find(OP, i)1005if orbit_dict.has_key(j):1006orbit_dict[j].append(i)1007else:1008orbit_dict[j] = [i]10091010OP_dealloc(OP)1011sage_free(perm_ints)10121013return orbit_dict.values()101410151016