Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx
4077 views
1
"""
2
Graph-theoretic partition backtrack functions
3
4
DOCTEST:
5
sage: import sage.groups.perm_gps.partn_ref.refinement_graphs
6
7
REFERENCE:
8
9
[1] McKay, Brendan D. Practical Graph Isomorphism. Congressus Numerantium,
10
Vol. 30 (1981), pp. 45-87.
11
12
"""
13
14
#*****************************************************************************
15
# Copyright (C) 2006 - 2011 Robert L. Miller <[email protected]>
16
#
17
# Distributed under the terms of the GNU General Public License (GPL)
18
# http://www.gnu.org/licenses/
19
#*****************************************************************************
20
21
include 'data_structures_pyx.pxi' # includes bitsets
22
23
def isomorphic(G1, G2, partn, ordering2, dig, use_indicator_function, sparse=False):
24
"""
25
Tests whether two graphs are isomorphic.
26
27
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import isomorphic
28
29
sage: G = Graph(2)
30
sage: H = Graph(2)
31
sage: isomorphic(G, H, [[0,1]], [0,1], 0, 1)
32
{0: 0, 1: 1}
33
sage: isomorphic(G, H, [[0,1]], [0,1], 0, 1)
34
{0: 0, 1: 1}
35
sage: isomorphic(G, H, [[0],[1]], [0,1], 0, 1)
36
{0: 0, 1: 1}
37
sage: isomorphic(G, H, [[0],[1]], [1,0], 0, 1)
38
{0: 1, 1: 0}
39
40
sage: G = Graph(3)
41
sage: H = Graph(3)
42
sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)
43
{0: 0, 1: 1, 2: 2}
44
sage: G.add_edge(0,1)
45
sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)
46
False
47
sage: H.add_edge(1,2)
48
sage: isomorphic(G, H, [[0,1,2]], [0,1,2], 0, 1)
49
{0: 1, 1: 2, 2: 0}
50
51
"""
52
cdef PartitionStack *part
53
cdef int *output, *ordering
54
cdef CGraph G
55
cdef GraphStruct GS1 = GraphStruct()
56
cdef GraphStruct GS2 = GraphStruct()
57
cdef GraphStruct GS
58
cdef int i, j, k, n = -1, cell_len
59
cdef list partition, cell
60
61
from sage.graphs.all import Graph, DiGraph
62
from sage.graphs.generic_graph import GenericGraph
63
from copy import copy
64
which_G = 1
65
for G_in in [G1, G2]:
66
if which_G == 1:
67
GS = GS1
68
first=True
69
else:
70
GS = GS2
71
first=False
72
if isinstance(G_in, GenericGraph):
73
if n == -1:
74
n = G_in.num_verts()
75
elif n != G_in.num_verts():
76
return False
77
if G_in.vertices() != range(n):
78
G_in = copy(G_in)
79
to = G_in.relabel(return_map=True)
80
frm = {}
81
for v in to.iterkeys():
82
frm[to[v]] = v
83
if first:
84
partition = [[to[v] for v in cell] for cell in partn]
85
else:
86
if first:
87
partition = partn
88
to = range(n)
89
frm = to
90
if sparse:
91
G = SparseGraph(n)
92
else:
93
G = DenseGraph(n)
94
if G_in.is_directed():
95
for i,j in G_in.edge_iterator(labels=False):
96
G.add_arc(i,j)
97
else:
98
for i,j in G_in.edge_iterator(labels=False):
99
G.add_arc(i,j)
100
G.add_arc(j,i)
101
elif isinstance(G_in, CGraph):
102
G = <CGraph> G_in
103
if n == -1:
104
n = G.num_verts
105
elif n != G.num_verts:
106
return False
107
to = {}
108
for a in G.verts(): to[a]=a
109
frm = to
110
if first:
111
partition = partn
112
else:
113
raise TypeError("G must be a Sage graph.")
114
if first: frm1=frm;to1=to
115
else: frm2=frm;to2=to
116
GS.G = G
117
GS.directed = 1 if dig else 0
118
GS.use_indicator = 1 if use_indicator_function else 0
119
which_G += 1
120
121
if n == 0:
122
return {}
123
124
part = PS_from_list(partition)
125
ordering = <int *> sage_malloc(n * sizeof(int))
126
if part is NULL or ordering is NULL:
127
if part is not NULL: PS_dealloc(part)
128
if ordering is not NULL: sage_free(ordering)
129
raise MemoryError
130
for i from 0 <= i < n:
131
ordering[i] = to2[ordering2[i]]
132
133
GS1.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))
134
GS2.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))
135
if GS1.scratch is NULL or GS2.scratch is NULL:
136
if GS1.scratch is not NULL: sage_free(GS1.scratch)
137
if GS2.scratch is not NULL: sage_free(GS2.scratch)
138
PS_dealloc(part)
139
sage_free(ordering)
140
raise MemoryError
141
142
output = double_coset(<void *>GS1, <void *>GS2, part, ordering, n, &all_children_are_equivalent, &refine_by_degree, &compare_graphs, NULL)
143
144
PS_dealloc(part)
145
sage_free(ordering)
146
sage_free(GS1.scratch)
147
sage_free(GS2.scratch)
148
149
if output is NULL:
150
return False
151
else:
152
output_py = dict([[frm1[i], frm2[output[i]]] for i from 0 <= i < n])
153
sage_free(output)
154
return output_py
155
156
def search_tree(G_in, partition, lab=True, dig=False, dict_rep=False, certify=False,
157
verbosity=0, use_indicator_function=True, sparse=True,
158
base=False, order=False):
159
"""
160
Compute canonical labels and automorphism groups of graphs.
161
162
INPUT:
163
G_in -- a Sage graph
164
partition -- a list of lists representing a partition of the vertices
165
lab -- if True, compute and return the canonical label in addition to the
166
automorphism group.
167
dig -- set to True for digraphs and graphs with loops. If True, does not
168
use optimizations based on Lemma 2.25 in [1] that are valid only for
169
simple graphs.
170
dict_rep -- if True, return a dictionary with keys the vertices of the
171
input graph G_in and values elements of the set the permutation group
172
acts on. (The point is that graphs are arbitrarily labelled, often
173
0..n-1, and permutation groups always act on 1..n. This dictionary
174
maps vertex labels (such as 0..n-1) to the domain of the permutations.)
175
certify -- if True, return the permutation from G to its canonical label.
176
verbosity -- currently ignored
177
use_indicator_function -- option to turn off indicator function
178
(True is generally faster)
179
sparse -- whether to use sparse or dense representation of the graph
180
(ignored if G is already a CGraph - see sage.graphs.base)
181
base -- whether to return the first sequence of split vertices (used in
182
computing the order of the group)
183
order -- whether to return the order of the automorphism group
184
185
OUTPUT:
186
Depends on the options. If more than one thing is returned, they are in a
187
tuple in the following order:
188
list of generators in list-permutation format -- always
189
dict -- if dict_rep
190
graph -- if lab
191
dict -- if certify
192
list -- if base
193
integer -- if order
194
195
DOCTEST:
196
sage: st = sage.groups.perm_gps.partn_ref.refinement_graphs.search_tree
197
sage: from sage.graphs.base.dense_graph import DenseGraph
198
sage: from sage.graphs.base.sparse_graph import SparseGraph
199
200
Graphs on zero vertices:
201
sage: G = Graph()
202
sage: st(G, [[]], order=True)
203
([], Graph on 0 vertices, 1)
204
205
Graphs on one vertex:
206
sage: G = Graph(1)
207
sage: st(G, [[0]], order=True)
208
([], Graph on 1 vertex, 1)
209
210
Graphs on two vertices:
211
sage: G = Graph(2)
212
sage: st(G, [[0,1]], order=True)
213
([[1, 0]], Graph on 2 vertices, 2)
214
sage: st(G, [[0],[1]], order=True)
215
([], Graph on 2 vertices, 1)
216
sage: G.add_edge(0,1)
217
sage: st(G, [[0,1]], order=True)
218
([[1, 0]], Graph on 2 vertices, 2)
219
sage: st(G, [[0],[1]], order=True)
220
([], Graph on 2 vertices, 1)
221
222
Graphs on three vertices:
223
sage: G = Graph(3)
224
sage: st(G, [[0,1,2]], order=True)
225
([[0, 2, 1], [1, 0, 2]], Graph on 3 vertices, 6)
226
sage: st(G, [[0],[1,2]], order=True)
227
([[0, 2, 1]], Graph on 3 vertices, 2)
228
sage: st(G, [[0],[1],[2]], order=True)
229
([], Graph on 3 vertices, 1)
230
sage: G.add_edge(0,1)
231
sage: st(G, [range(3)], order=True)
232
([[1, 0, 2]], Graph on 3 vertices, 2)
233
sage: st(G, [[0],[1,2]], order=True)
234
([], Graph on 3 vertices, 1)
235
sage: st(G, [[0,1],[2]], order=True)
236
([[1, 0, 2]], Graph on 3 vertices, 2)
237
238
The Dodecahedron has automorphism group of size 120:
239
sage: G = graphs.DodecahedralGraph()
240
sage: Pi = [range(20)]
241
sage: st(G, Pi, order=True)[2]
242
120
243
244
The three-cube has automorphism group of size 48:
245
sage: G = graphs.CubeGraph(3)
246
sage: G.relabel()
247
sage: Pi = [G.vertices()]
248
sage: st(G, Pi, order=True)[2]
249
48
250
251
We obtain the same output using different types of Sage graphs:
252
sage: G = graphs.DodecahedralGraph()
253
sage: GD = DenseGraph(20)
254
sage: GS = SparseGraph(20)
255
sage: for i,j,_ in G.edge_iterator():
256
... GD.add_arc(i,j); GD.add_arc(j,i)
257
... GS.add_arc(i,j); GS.add_arc(j,i)
258
sage: Pi=[range(20)]
259
sage: a,b = st(G, Pi)
260
sage: asp,bsp = st(GS, Pi)
261
sage: ade,bde = st(GD, Pi)
262
sage: bsg = Graph()
263
sage: bdg = Graph()
264
sage: for i in range(20):
265
... for j in range(20):
266
... if bsp.has_arc(i,j):
267
... bsg.add_edge(i,j)
268
... if bde.has_arc(i,j):
269
... bdg.add_edge(i,j)
270
sage: print a, b.graph6_string()
271
[[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?@O
272
sage: a == asp
273
True
274
sage: a == ade
275
True
276
sage: b == bsg
277
True
278
sage: b == bdg
279
True
280
281
Cubes!
282
sage: C = graphs.CubeGraph(1)
283
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
284
2
285
sage: C = graphs.CubeGraph(2)
286
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
287
8
288
sage: C = graphs.CubeGraph(3)
289
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
290
48
291
sage: C = graphs.CubeGraph(4)
292
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
293
384
294
sage: C = graphs.CubeGraph(5)
295
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
296
3840
297
sage: C = graphs.CubeGraph(6)
298
sage: gens, order = st(C, [C.vertices()], lab=False, order=True); order
299
46080
300
301
One can also turn off the indicator function (note- this will take longer)
302
sage: D1 = DiGraph({0:[2],2:[0],1:[1]}, loops=True)
303
sage: D2 = DiGraph({1:[2],2:[1],0:[0]}, loops=True)
304
sage: a,b = st(D1, [D1.vertices()], dig=True, use_indicator_function=False)
305
sage: c,d = st(D2, [D2.vertices()], dig=True, use_indicator_function=False)
306
sage: b==d
307
True
308
309
This example is due to Chris Godsil:
310
sage: HS = graphs.HoffmanSingletonGraph()
311
sage: alqs = [Set(c) for c in (HS.complement()).cliques_maximum()]
312
sage: Y = Graph([alqs, lambda s,t: len(s.intersection(t))==0])
313
sage: Y0,Y1 = Y.connected_components_subgraphs()
314
sage: st(Y0, [Y0.vertices()])[1] == st(Y1, [Y1.vertices()])[1]
315
True
316
sage: st(Y0, [Y0.vertices()])[1] == st(HS, [HS.vertices()])[1]
317
True
318
sage: st(HS, [HS.vertices()])[1] == st(Y1, [Y1.vertices()])[1]
319
True
320
321
Certain border cases need to be tested as well:
322
sage: G = Graph('Fll^G')
323
sage: a,b,c = st(G, [range(G.num_verts())], order=True); b
324
Graph on 7 vertices
325
sage: c
326
48
327
sage: G = Graph(21)
328
sage: st(G, [range(G.num_verts())], order=True)[2] == factorial(21)
329
True
330
331
sage: G = Graph('^????????????????????{??N??@w??FaGa?PCO@CP?AGa?_QO?Q@G?CcA??cc????Bo????{????F_')
332
sage: perm = {3:15, 15:3}
333
sage: H = G.relabel(perm, inplace=False)
334
sage: st(G, [range(G.num_verts())])[1] == st(H, [range(H.num_verts())])[1]
335
True
336
337
sage: st(Graph(':Dkw'), [range(5)], lab=False, dig=True)
338
[[4, 1, 2, 3, 0], [0, 2, 1, 3, 4]]
339
340
"""
341
cdef CGraph G
342
cdef int i, j, n
343
cdef Integer I
344
cdef aut_gp_and_can_lab *output
345
cdef PartitionStack *part
346
from sage.graphs.all import Graph, DiGraph
347
from sage.graphs.generic_graph import GenericGraph
348
from copy import copy
349
if isinstance(G_in, GenericGraph):
350
n = G_in.num_verts()
351
if G_in.vertices() != range(n):
352
G_in = copy(G_in)
353
to = G_in.relabel(return_map=True)
354
frm = {}
355
for v in to.iterkeys():
356
frm[to[v]] = v
357
partition = [[to[v] for v in cell] for cell in partition]
358
else:
359
to = dict(enumerate(range(n)))
360
frm = to
361
if sparse:
362
G = SparseGraph(n)
363
else:
364
G = DenseGraph(n)
365
if G_in.is_directed():
366
for i,j in G_in.edge_iterator(labels=False):
367
G.add_arc(i,j)
368
else:
369
for i,j in G_in.edge_iterator(labels=False):
370
G.add_arc(i,j)
371
G.add_arc(j,i)
372
elif isinstance(G_in, CGraph):
373
G = <CGraph> G_in
374
n = G.num_verts
375
to = {}
376
for a in G.verts(): to[a]=a
377
frm = to
378
else:
379
raise TypeError("G must be a Sage graph.")
380
381
cdef GraphStruct GS = GraphStruct()
382
GS.G = G
383
GS.directed = 1 if dig else 0
384
GS.use_indicator = 1 if use_indicator_function else 0
385
386
if n == 0:
387
return_tuple = [[]]
388
if dict_rep:
389
return_tuple.append({})
390
if lab:
391
if isinstance(G_in, GenericGraph):
392
G_C = copy(G_in)
393
else:
394
if isinstance(G, SparseGraph):
395
G_C = SparseGraph(n)
396
else:
397
G_C = DenseGraph(n)
398
return_tuple.append(G_C)
399
if certify:
400
return_tuple.append({})
401
if base:
402
return_tuple.append([])
403
if order:
404
return_tuple.append(Integer(1))
405
if len(return_tuple) == 1:
406
return return_tuple[0]
407
else:
408
return tuple(return_tuple)
409
410
GS.scratch = <int *> sage_malloc( (3*G.num_verts + 1) * sizeof(int) )
411
part = PS_from_list(partition)
412
if GS.scratch is NULL or part is NULL:
413
if part is not NULL: PS_dealloc(part)
414
if GS.scratch is not NULL: sage_free(GS.scratch)
415
raise MemoryError
416
417
lab_new = lab or certify
418
output = get_aut_gp_and_can_lab(<void *>GS, part, G.num_verts, &all_children_are_equivalent, &refine_by_degree, &compare_graphs, lab, NULL)
419
sage_free( GS.scratch )
420
# prepare output
421
list_of_gens = []
422
for i from 0 <= i < output.num_gens:
423
list_of_gens.append([output.generators[j+i*G.num_verts] for j from 0 <= j < G.num_verts])
424
return_tuple = [list_of_gens]
425
if dict_rep:
426
ddd = {}
427
for v in frm.iterkeys():
428
ddd[frm[v]] = v if v != 0 else n
429
return_tuple.append(ddd)
430
if lab:
431
if isinstance(G_in, GenericGraph):
432
G_C = copy(G_in)
433
G_C.relabel([output.relabeling[i] for i from 0 <= i < n])
434
else:
435
if isinstance(G, SparseGraph):
436
G_C = SparseGraph(n)
437
else:
438
G_C = DenseGraph(n)
439
for i from 0 <= i < n:
440
for j in G.out_neighbors(i):
441
G_C.add_arc(output.relabeling[i],output.relabeling[j])
442
return_tuple.append(G_C)
443
if certify:
444
dd = {}
445
for i from 0 <= i < G.num_verts:
446
dd[frm[i]] = output.relabeling[i]
447
return_tuple.append(dd)
448
if base:
449
return_tuple.append([output.group.base_orbits[i][0] for i from 0 <= i < output.group.base_size])
450
if order:
451
I = Integer()
452
SC_order(output.group, 0, I.value)
453
return_tuple.append(I)
454
PS_dealloc(part)
455
sage_free(output.generators)
456
SC_dealloc(output.group)
457
if lab_new:
458
sage_free(output.relabeling)
459
sage_free(output)
460
if len(return_tuple) == 1:
461
return return_tuple[0]
462
else:
463
return tuple(return_tuple)
464
465
cdef int refine_by_degree(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len):
466
r"""
467
Refines the input partition by checking degrees of vertices to the given
468
cells.
469
470
INPUT:
471
PS -- a partition stack, whose finest partition is the partition to be
472
refined.
473
S -- a graph struct object, which contains scratch space, the graph in
474
question, and some flags.
475
cells_to_refine_by -- a list of pointers to cells to check degrees against
476
in refining the other cells (updated in place). Must be allocated to
477
length at least the degree of PS, since the array may grow
478
ctrb_len -- how many cells in cells_to_refine_by
479
480
OUTPUT:
481
482
An integer invariant under the orbits of $S_n$. That is, if $\gamma$ is a
483
permutation of the vertices, then
484
$$ I(G, PS, cells_to_refine_by) = I( \gamma(G), \gamma(PS), \gamma(cells_to_refine_by) ) .$$
485
486
"""
487
cdef GraphStruct GS = <GraphStruct> S
488
cdef CGraph G = GS.G
489
cdef int current_cell_against = 0
490
cdef int current_cell, i, r
491
cdef int first_largest_subcell
492
cdef int invariant = 1
493
cdef int max_degree
494
cdef int *degrees = GS.scratch # length 3n+1
495
cdef bint necessary_to_split_cell
496
cdef int against_index
497
while not PS_is_discrete(PS) and current_cell_against < ctrb_len:
498
invariant += 1
499
current_cell = 0
500
while current_cell < PS.degree:
501
invariant += 50
502
i = current_cell
503
necessary_to_split_cell = 0
504
max_degree = 0
505
while 1:
506
degrees[i-current_cell] = degree(PS, G, i, cells_to_refine_by[current_cell_against], 0)
507
if degrees[i-current_cell] != degrees[0]:
508
necessary_to_split_cell = 1
509
if degrees[i-current_cell] > max_degree:
510
max_degree = degrees[i-current_cell]
511
i += 1
512
if PS.levels[i-1] <= PS.depth:
513
break
514
# now, i points to the next cell (before refinement)
515
if necessary_to_split_cell:
516
invariant += 10
517
first_largest_subcell = sort_by_function(PS, current_cell, degrees)
518
invariant += first_largest_subcell + max_degree
519
against_index = current_cell_against
520
while against_index < ctrb_len:
521
if cells_to_refine_by[against_index] == current_cell:
522
cells_to_refine_by[against_index] = first_largest_subcell
523
break
524
against_index += 1
525
r = current_cell
526
while 1:
527
if r == current_cell or PS.levels[r-1] == PS.depth:
528
if r != first_largest_subcell:
529
cells_to_refine_by[ctrb_len] = r
530
ctrb_len += 1
531
r += 1
532
if r >= i:
533
break
534
invariant += (i - current_cell)
535
current_cell = i
536
if GS.directed:
537
# if we are looking at a digraph, also compute
538
# the reverse degrees and sort by them
539
current_cell = 0
540
while current_cell < PS.degree: # current_cell is still a valid cell
541
invariant += 20
542
i = current_cell
543
necessary_to_split_cell = 0
544
max_degree = 0
545
while 1:
546
degrees[i-current_cell] = degree(PS, G, i, cells_to_refine_by[current_cell_against], 1)
547
if degrees[i-current_cell] != degrees[0]:
548
necessary_to_split_cell = 1
549
if degrees[i-current_cell] > max_degree:
550
max_degree = degrees[i-current_cell]
551
i += 1
552
if PS.levels[i-1] <= PS.depth:
553
break
554
# now, i points to the next cell (before refinement)
555
if necessary_to_split_cell:
556
invariant += 7
557
first_largest_subcell = sort_by_function(PS, current_cell, degrees)
558
invariant += first_largest_subcell + max_degree
559
against_index = current_cell_against
560
while against_index < ctrb_len:
561
if cells_to_refine_by[against_index] == current_cell:
562
cells_to_refine_by[against_index] = first_largest_subcell
563
break
564
against_index += 1
565
against_index = ctrb_len
566
r = current_cell
567
while 1:
568
if r == current_cell or PS.levels[r-1] == PS.depth:
569
if r != first_largest_subcell:
570
cells_to_refine_by[against_index] = r
571
against_index += 1
572
ctrb_len += 1
573
r += 1
574
if r >= i:
575
break
576
invariant += (i - current_cell)
577
current_cell = i
578
current_cell_against += 1
579
if GS.use_indicator:
580
return invariant
581
else:
582
return 0
583
584
cdef int compare_graphs(int *gamma_1, int *gamma_2, void *S1, void *S2):
585
r"""
586
Compare gamma_1(S1) and gamma_2(S2).
587
588
Return return -1 if gamma_1(S1) < gamma_2(S2), 0 if gamma_1(S1) ==
589
gamma_2(S2), 1 if gamma_1(S1) > gamma_2(S2). (Just like the python
590
\code{cmp}) function.
591
592
INPUT:
593
gamma_1, gamma_2 -- list permutations (inverse)
594
S1, S2 -- graph struct objects
595
596
"""
597
cdef int i, j
598
cdef GraphStruct GS1 = <GraphStruct> S1
599
cdef GraphStruct GS2 = <GraphStruct> S2
600
cdef CGraph G1 = GS1.G
601
cdef CGraph G2 = GS2.G
602
for i from 0 <= i < G1.num_verts:
603
for j from 0 <= j < G1.num_verts:
604
if G1.has_arc_unsafe(gamma_1[i], gamma_1[j]):
605
if not G2.has_arc_unsafe(gamma_2[i], gamma_2[j]):
606
return 1
607
elif G2.has_arc_unsafe(gamma_2[i], gamma_2[j]):
608
return -1
609
return 0
610
611
cdef bint all_children_are_equivalent(PartitionStack *PS, void *S):
612
"""
613
Return True if every refinement of the current partition results in the
614
same structure.
615
616
WARNING:
617
Converse does not hold in general! See Lemma 2.25 of [1] for details.
618
619
INPUT:
620
PS -- the partition stack to be checked
621
S -- a graph struct object
622
"""
623
cdef GraphStruct GS = <GraphStruct> S
624
if GS.directed:
625
return 0
626
cdef CGraph G = GS.G
627
cdef int i, n = PS.degree
628
cdef bint in_cell = 0
629
cdef int nontrivial_cells = 0
630
cdef int total_cells = PS_num_cells(PS)
631
if n <= total_cells + 4:
632
return 1
633
for i from 0 <= i < n-1:
634
if PS.levels[i] <= PS.depth:
635
if in_cell:
636
nontrivial_cells += 1
637
in_cell = 0
638
else:
639
in_cell = 1
640
if in_cell:
641
nontrivial_cells += 1
642
if n == total_cells + nontrivial_cells:
643
return 1
644
if n == total_cells + nontrivial_cells + 1:
645
return 1
646
return 0
647
648
cdef inline int degree(PartitionStack *PS, CGraph G, int entry, int cell_index, bint reverse):
649
"""
650
Returns the number of edges from the vertex corresponding to entry to
651
vertices in the cell corresponding to cell_index.
652
653
INPUT:
654
PS -- the partition stack to be checked
655
S -- a graph struct object
656
entry -- the position of the vertex in question in the entries of PS
657
cell_index -- the starting position of the cell in question in the entries
658
of PS
659
reverse -- whether to check for arcs in the other direction
660
"""
661
cdef int num_arcs = 0
662
entry = PS.entries[entry]
663
if not reverse:
664
while 1:
665
if G.has_arc_unsafe(PS.entries[cell_index], entry):
666
num_arcs += 1
667
if PS.levels[cell_index] > PS.depth:
668
cell_index += 1
669
else:
670
break
671
else:
672
while 1:
673
if G.has_arc_unsafe(entry, PS.entries[cell_index]):
674
num_arcs += 1
675
if PS.levels[cell_index] > PS.depth:
676
cell_index += 1
677
else:
678
break
679
return num_arcs
680
681
def all_labeled_graphs(n):
682
"""
683
Returns all labeled graphs on n vertices {0,1,...,n-1}. Used in
684
classifying isomorphism types (naive approach), and more importantly
685
in benchmarking the search algorithm.
686
687
EXAMPLE::
688
689
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import all_labeled_graphs
690
sage: st = sage.groups.perm_gps.partn_ref.refinement_graphs.search_tree
691
sage: Glist = {}
692
sage: Giso = {}
693
sage: for n in [1..5]: # long time (4s on sage.math, 2011)
694
... Glist[n] = all_labeled_graphs(n)
695
... Giso[n] = []
696
... for g in Glist[n]:
697
... a, b = st(g, [range(n)])
698
... inn = False
699
... for gi in Giso[n]:
700
... if b == gi:
701
... inn = True
702
... if not inn:
703
... Giso[n].append(b)
704
sage: for n in Giso: # long time
705
... print n, len(Giso[n])
706
1 1
707
2 2
708
3 4
709
4 11
710
5 34
711
712
"""
713
from sage.graphs.all import Graph
714
TE = []
715
for i in range(n):
716
for j in range(i):
717
TE.append((i, j))
718
m = len(TE)
719
Glist= []
720
for i in range(2**m):
721
G = Graph(n)
722
b = Integer(i).binary()
723
b = '0'*(m-len(b)) + b
724
for i in range(m):
725
if int(b[i]):
726
G.add_edge(TE[i])
727
Glist.append(G)
728
return Glist
729
730
731
def random_tests(num=10, n_max=60, perms_per_graph=5):
732
"""
733
Tests to make sure that C(gamma(G)) == C(G) for random permutations gamma
734
and random graphs G, and that isomorphic returns an isomorphism.
735
736
INPUT:
737
num -- run tests for this many graphs
738
n_max -- test graphs with at most this many vertices
739
perms_per_graph -- test each graph with this many random permutations
740
741
DISCUSSION:
742
743
This code generates num random graphs G on at most n_max vertices. The
744
density of edges is chosen randomly between 0 and 1.
745
746
For each graph G generated, we uniformly generate perms_per_graph random
747
permutations and verify that the canonical labels of G and the image of G
748
under the generated permutation are equal, and that the isomorphic function
749
returns an isomorphism.
750
751
TESTS::
752
753
sage: import sage.groups.perm_gps.partn_ref.refinement_graphs
754
sage: sage.groups.perm_gps.partn_ref.refinement_graphs.random_tests() # long time
755
All passed: 200 random tests on 20 graphs.
756
"""
757
from sage.misc.prandom import random, randint
758
from sage.graphs.graph_generators import GraphGenerators
759
from sage.graphs.digraph_generators import DiGraphGenerators
760
from sage.combinat.permutation import Permutations
761
from copy import copy
762
cdef int i, j, num_tests = 0, num_graphs = 0
763
GG = GraphGenerators()
764
DGG = DiGraphGenerators()
765
for mmm in range(num):
766
p = random()
767
n = randint(1, n_max)
768
S = Permutations(n)
769
770
G = GG.RandomGNP(n, p)
771
H = copy(G)
772
for i from 0 <= i < perms_per_graph:
773
G = copy(H)
774
G1 = search_tree(G, [G.vertices()])[1]
775
perm = list(S.random_element())
776
perm = [perm[j]-1 for j from 0 <= j < n]
777
G.relabel(perm)
778
G2 = search_tree(G, [G.vertices()])[1]
779
if G1 != G2:
780
print "search_tree FAILURE: graph6-"
781
print H.graph6_string()
782
print perm
783
return
784
isom = isomorphic(G, H, [range(n)], range(n), 0, 1)
785
if not isom or G.relabel(isom, inplace=False) != H:
786
print "isom FAILURE: graph6-"
787
print H.graph6_string()
788
print perm
789
return
790
791
D = DGG.RandomDirectedGNP(n, p)
792
D.allow_loops(True)
793
for i from 0 <= i < n:
794
if random() < p:
795
D.add_edge(i,i)
796
E = copy(D)
797
for i from 0 <= i < perms_per_graph:
798
D = copy(E)
799
D1 = search_tree(D, [D.vertices()], dig=True)[1]
800
perm = list(S.random_element())
801
perm = [perm[j]-1 for j from 0 <= j < n]
802
D.relabel(perm)
803
D2 = search_tree(D, [D.vertices()], dig=True)[1]
804
if D1 != D2:
805
print "search_tree FAILURE: dig6-"
806
print E.dig6_string()
807
print perm
808
return
809
isom = isomorphic(D, E, [range(n)], range(n), 1, 1)
810
if not isom or D.relabel(isom, inplace=False) != E:
811
print "isom FAILURE: dig6-"
812
print E.dig6_string()
813
print perm
814
print isom
815
return
816
num_tests += 4*perms_per_graph
817
num_graphs += 2
818
print "All passed: %d random tests on %d graphs."%(num_tests, num_graphs)
819
820
def orbit_partition(gamma, list_perm=False):
821
r"""
822
Assuming that G is a graph on vertices 0,1,...,n-1, and gamma is an
823
element of SymmetricGroup(n), returns the partition of the vertex
824
set determined by the orbits of gamma, considered as action on the
825
set 1,2,...,n where we take 0 = n. In other words, returns the
826
partition determined by a cyclic representation of gamma.
827
828
INPUT:
829
830
831
- ``list_perm`` - if True, assumes
832
``gamma`` is a list representing the map
833
`i \mapsto ``gamma``[i]`.
834
835
836
EXAMPLES::
837
838
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import orbit_partition
839
sage: G = graphs.PetersenGraph()
840
sage: S = SymmetricGroup(10)
841
sage: gamma = S('(10,1,2,3,4)(5,6,7)(8,9)')
842
sage: orbit_partition(gamma)
843
[[1, 2, 3, 4, 0], [5, 6, 7], [8, 9]]
844
sage: gamma = S('(10,5)(1,6)(2,7)(3,8)(4,9)')
845
sage: orbit_partition(gamma)
846
[[1, 6], [2, 7], [3, 8], [4, 9], [5, 0]]
847
"""
848
if list_perm:
849
n = len(gamma)
850
seen = [1] + [0]*(n-1)
851
i = 0
852
p = 0
853
partition = [[0]]
854
while sum(seen) < n:
855
if gamma[i] != partition[p][0]:
856
partition[p].append(gamma[i])
857
i = gamma[i]
858
seen[i] = 1
859
else:
860
for j in range(n):
861
if seen[j]==0:
862
i = j
863
break
864
partition.append([i])
865
p += 1
866
seen[i] = 1
867
return partition
868
else:
869
n = len(gamma.list())
870
l = []
871
for i in range(1,n+1):
872
orb = gamma.orbit(i)
873
if orb not in l: l.append(orb)
874
for i in l:
875
for j in range(len(i)):
876
if i[j] == n:
877
i[j] = 0
878
return l
879
880
def perm_group_elt(lperm):
881
"""
882
Given a list permutation of the set 0, 1, ..., n-1, returns the
883
corresponding PermutationGroupElement where we take 0 = n.
884
885
EXAMPLE::
886
887
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import perm_group_elt
888
sage: perm_group_elt([0,2,1])
889
(1,2)
890
sage: perm_group_elt([1,2,0])
891
(1,2,3)
892
"""
893
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
894
n = len(lperm)
895
S = SymmetricGroup(n)
896
Part = orbit_partition(lperm, list_perm=True)
897
gens = []
898
for z in Part:
899
if len(z) > 1:
900
if 0 in z:
901
zed = z.index(0)
902
generator = z[:zed] + [n] + z[zed+1:]
903
gens.append(tuple(generator))
904
else:
905
gens.append(tuple(z))
906
E = S(gens)
907
return E
908
909
def coarsest_equitable_refinement(CGraph G, list partition, bint directed):
910
"""
911
Returns the coarsest equitable refinement of ``partition`` for ``G``.
912
913
This is a helper function for the graph function of the same name.
914
915
DOCTEST (More thorough testing in ``sage/graphs/graph.py``)::
916
917
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import coarsest_equitable_refinement
918
sage: from sage.graphs.base.sparse_graph import SparseGraph
919
sage: coarsest_equitable_refinement(SparseGraph(7), [[0], [1,2,3,4], [5,6]], 0)
920
[[0], [1, 2, 3, 4], [5, 6]]
921
922
"""
923
cdef int i, j = 0, k = 0, n = G.num_verts
924
925
# set up partition stack and graph struct
926
cdef PartitionStack *nu = PS_new(n, 0)
927
for cell in partition:
928
for i in cell:
929
nu.entries[j] = i
930
nu.levels[j] = n
931
j += 1
932
nu.levels[j-1] = 0
933
PS_move_min_to_front(nu, k, j-1)
934
k = j
935
936
cdef GraphStruct GS = GraphStruct()
937
GS.G = G
938
GS.scratch = <int *> sage_malloc((3*n+1) * sizeof(int))
939
if GS.scratch is NULL:
940
PS_dealloc(nu)
941
raise MemoryError
942
GS.directed = directed
943
GS.use_indicator = 0
944
945
# set up cells to refine by
946
cdef int num_cells = len(partition)
947
cdef int *alpha = <int *>sage_malloc(n * sizeof(int))
948
if alpha is NULL:
949
PS_dealloc(nu)
950
sage_free(GS.scratch)
951
raise MemoryError
952
j = 0
953
for i from 0 <= i < num_cells:
954
alpha[i] = j
955
j += len(partition[i])
956
957
# refine, and get the result
958
refine_by_degree(nu, <void *>GS, alpha, num_cells)
959
960
eq_part = []
961
cell = []
962
for i from 0 <= i < n:
963
cell.append(nu.entries[i])
964
if nu.levels[i] <= 0:
965
eq_part.append(cell)
966
cell = []
967
968
PS_dealloc(nu)
969
sage_free(GS.scratch)
970
sage_free(alpha)
971
972
return eq_part
973
974
def get_orbits(list gens, int n):
975
"""
976
Compute orbits given a list of generators of a permutation group, in list
977
format.
978
979
This is a helper function for automorphism groups of graphs.
980
981
DOCTEST (More thorough testing in ``sage/graphs/graph.py``)::
982
983
sage: from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits
984
sage: get_orbits([[1,2,3,0,4,5], [0,1,2,3,5,4]], 6)
985
[[0, 1, 2, 3], [4, 5]]
986
987
"""
988
cdef int i, j
989
if len(gens) == 0:
990
return [[i] for i from 0 <= i < n]
991
992
cdef OrbitPartition *OP = OP_new(n)
993
cdef int *perm_ints = <int *> sage_malloc(n * sizeof(int))
994
if perm_ints is NULL:
995
OP_dealloc(OP)
996
raise MemoryError
997
998
for gen in gens:
999
for i from 0 <= i < n:
1000
perm_ints[i] = gen[i]
1001
OP_merge_list_perm(OP, perm_ints)
1002
1003
orbit_dict = {}
1004
for i from 0 <= i < n:
1005
j = OP_find(OP, i)
1006
if orbit_dict.has_key(j):
1007
orbit_dict[j].append(i)
1008
else:
1009
orbit_dict[j] = [i]
1010
1011
OP_dealloc(OP)
1012
sage_free(perm_ints)
1013
1014
return orbit_dict.values()
1015
1016