Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/genus.pyx
8815 views
1
#*****************************************************************************
2
# Copyright (C) 2010 Tom Boothby <[email protected]>
3
#
4
# Distributed under the terms of the GNU General Public License (GPL v2+)
5
# http://www.gnu.org/licenses/
6
#*****************************************************************************
7
8
"""
9
10
This file contains a moderately-optimized implementation to compute the
11
genus of simple connected graph. It runs about a thousand times faster
12
than the previous version in Sage, not including asymptotic improvements.
13
14
The algorithm works by enumerating combinatorial embeddings of a graph,
15
and computing the genus of these via the Euler characteristic. We view
16
a combinatorial embedding of a graph as a pair of permutations `v,e`
17
which act on a set `B` of `2|E(G)|` "darts". The permutation `e` is an
18
involution, and its orbits correspond to edges in the graph. Similarly,
19
The orbits of `v` correspond to the vertices of the graph, and those of
20
`f = ve` correspond to faces of the embedded graph.
21
22
The requirement that the group `<v,e>` acts transitively on `B` is
23
equivalent to the graph being connected. We can compute the genus of a
24
graph by
25
26
`2 - 2g = V - E + F`
27
28
where `E`, `V`, and `F` denote the number of orbits of `e`, `v`, and
29
`f` respectively.
30
31
We make several optimizations to the naive algorithm, which are
32
described throughout the file.
33
34
"""
35
36
37
cimport sage.combinat.permutation_cython
38
39
from sage.combinat.permutation_cython cimport next_swap, reset_swap
40
41
from sage.graphs.base.dense_graph cimport DenseGraph
42
from sage.graphs.graph import Graph
43
44
45
include "sage/ext/stdsage.pxi"
46
include "sage/ext/cdefs.pxi"
47
include "sage/ext/interrupt.pxi"
48
49
50
cdef inline int edge_map(int i):
51
"""
52
We might as well make the edge map nice, since the vertex map
53
is so slippery. This is the fastest way I could find to
54
establish the correspondence `i <-> i+1` if `i` is even.
55
"""
56
57
return i - 2*(i&1) + 1
58
59
cdef class simple_connected_genus_backtracker:
60
r"""
61
62
A class which computes the genus of a DenseGraph through an
63
extremely slow but relatively optimized algorithm. This is
64
"only" exponential for graphs of bounded degree, and feels
65
pretty snappy for 3-regular graphs. The generic runtime is
66
67
`|V(G)| \prod_{v \in V(G)} (deg(v)-1)!`
68
69
which is `2^{|V(G)|}` for 3-regular graphs, and can achieve
70
`n(n-1)!^{n}` for the complete graph on `n` vertices. We can
71
handily compute the genus of `K_6` in milliseconds on modern
72
hardware, but `K_7` may take a few days. Don't bother with
73
`K_8`, or any graph with more than one vertex of degree
74
10 or worse, unless you can find an a priori lower bound on
75
the genus and expect the graph to have that genus.
76
77
WARNING::
78
79
THIS MAY SEGFAULT OR HANG ON:
80
* DISCONNECTED GRAPHS
81
* DIRECTED GRAPHS
82
* LOOPED GRAPHS
83
* MULTIGRAPHS
84
85
EXAMPLES::
86
87
sage: import sage.graphs.genus
88
sage: G = graphs.CompleteGraph(6)
89
sage: G = Graph(G, implementation='c_graph', sparse=False)
90
sage: bt = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
91
sage: bt.genus() #long time
92
1
93
sage: bt.genus(cutoff=1)
94
1
95
sage: G = graphs.PetersenGraph()
96
sage: G = Graph(G, implementation='c_graph', sparse=False)
97
sage: bt = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
98
sage: bt.genus()
99
1
100
sage: G = graphs.FlowerSnark()
101
sage: G = Graph(G, implementation='c_graph', sparse=False)
102
sage: bt = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
103
sage: bt.genus()
104
2
105
106
"""
107
108
cdef int **vertex_darts
109
cdef int *face_map
110
cdef int *degree
111
cdef int *visited
112
cdef int *face_freeze
113
cdef int **swappers
114
cdef int num_darts, num_verts, num_cycles, record_genus
115
116
def __dealloc__(self):
117
"""
118
119
Deallocate the simple_connected_genus_backtracker object.
120
121
TEST::
122
123
sage: import sage.graphs.genus
124
sage: import gc
125
sage: G = graphs.CompleteGraph(100)
126
sage: G = Graph(G, implementation='c_graph', sparse=False)
127
sage: gc.collect() # random
128
54
129
sage: t = get_memory_usage()
130
sage: for i in xrange(1000):
131
... gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
132
... gb = None #indirect doctest
133
...
134
sage: gc.collect()
135
0
136
sage: get_memory_usage(t) <= 0.0
137
True
138
"""
139
cdef int i
140
141
if self.vertex_darts != NULL:
142
if self.vertex_darts[0] != NULL:
143
sage_free(self.vertex_darts[0])
144
sage_free(self.vertex_darts)
145
146
if self.swappers != NULL:
147
if self.swappers[0] != NULL:
148
sage_free(self.swappers[0])
149
sage_free(self.swappers)
150
151
if self.face_map != NULL:
152
sage_free(self.face_map)
153
154
if self.visited != NULL:
155
sage_free(self.visited)
156
157
if self.face_freeze != NULL:
158
sage_free(self.face_freeze)
159
160
if self.degree != NULL:
161
sage_free(self.degree)
162
163
164
cdef int got_memory(self):
165
"""
166
167
Return 1 if we alloc'd everything ok, or 0 otherwise.
168
169
"""
170
if self.swappers == NULL:
171
return 0
172
if self.vertex_darts == NULL:
173
return 0
174
if self.visited == NULL:
175
return 0
176
if self.face_freeze == NULL:
177
return 0
178
if self.degree == NULL:
179
return 0
180
if self.face_map == NULL:
181
return 0
182
183
return 1
184
185
def __init__(self, DenseGraph G):
186
"""
187
188
Initialize the genus_backtracker object.
189
190
TESTS::
191
sage: import sage.graphs.genus
192
sage: G = Graph(implementation='c_graph', sparse=False) #indirect doctest
193
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
194
sage: G = Graph(graphs.CompleteGraph(4), implementation='c_graph', sparse=False)
195
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
196
sage: gb.genus()
197
0
198
199
"""
200
201
cdef int i,j,du,dv,u,v
202
cdef int *w, *s
203
204
# set this to prevent segfaulting on dealloc in case anything goes wrong.
205
self.visited = NULL
206
self.vertex_darts = NULL
207
self.degree = NULL
208
self.visited = NULL
209
self.swappers = NULL
210
211
self.num_darts = G.num_arcs
212
self.num_verts = G.num_verts
213
214
#bail to avoid an invalid free
215
if self.num_verts <= 1:
216
return
217
218
self.face_map = <int *> sage_malloc(self.num_darts * sizeof(int))
219
self.vertex_darts = <int **>sage_malloc(self.num_verts * sizeof(int *))
220
self.swappers = <int **>sage_malloc(self.num_verts * sizeof(int *))
221
self.degree = <int *> sage_malloc(self.num_verts * sizeof(int))
222
self.visited = <int *> sage_malloc(self.num_darts * sizeof(int))
223
self.face_freeze = <int *> sage_malloc(self.num_darts * sizeof(int))
224
225
if self.got_memory() == 0:
226
# dealloc is NULL-safe and frees everything that did get alloc'd
227
raise MemoryError, "Error allocating memory for graph genus a"
228
229
w = <int *>sage_malloc((self.num_verts + self.num_darts) * sizeof(int))
230
self.vertex_darts[0] = w
231
s = <int *>sage_malloc( 2 * (self.num_darts - self.num_verts) * sizeof(int))
232
self.swappers[0] = s
233
234
if w == NULL or s == NULL:
235
# dealloc is NULL-safe and frees everything that did get alloc'd
236
raise MemoryError, "Error allocating memory for graph genus b"
237
238
for v in range(self.num_verts):
239
if not G.has_vertex(v):
240
raise ValueError, "Please relabel G so vertices are 0, ..., n-1"
241
242
dv = G.in_degrees[v]
243
self.degree[v] = 0
244
self.vertex_darts[v] = w
245
w += dv + 1
246
247
self.swappers[v] = s
248
s += 2*(dv - 1)
249
250
i = 0
251
for v in range(self.num_verts):
252
dv = self.degree[v]
253
254
# we use self.face_map as a temporary int array to hold
255
# neighbors of v since it will be overwritten shortly.
256
G.in_neighbors_unsafe(v, self.face_map, G.in_degrees[v])
257
for j in range(G.in_degrees[v]):
258
u = self.face_map[j]
259
if u < v:
260
#edge hasn't been seen yet
261
self.vertex_darts[u][self.degree[u]] = i
262
self.vertex_darts[v][dv] = i+1
263
self.degree[u] += 1
264
dv += 1
265
i += 2
266
267
self.degree[v] = dv
268
269
for v in range(self.num_verts):
270
dv = self.degree[v]
271
w = self.vertex_darts[v]
272
w[dv] = w[0]
273
for i in range(dv):
274
u = w[i]
275
self.face_map[edge_map(u)] = w[i+1]
276
277
self.freeze_face()
278
279
# good for debugging
280
# def dump(self):
281
# cdef int v, j
282
# print "vertex darts:",
283
# for v in range(self.num_verts):
284
# print '(',
285
# for j in range(self.degree[v] + 1):
286
# print self.vertex_darts[v][j],
287
# print ')',
288
# print "\n"
289
290
# print "face map: [",
291
# for v in range(self.num_darts):
292
# print self.face_map[v],
293
# print ']'
294
295
296
cdef inline void freeze_face(self):
297
"""
298
Quickly store the current face_map so we can recover
299
the embedding it corresponds to later.
300
"""
301
302
memcpy(self.face_freeze, self.face_map, self.num_darts * sizeof(int))
303
304
def get_embedding(self):
305
"""
306
307
Return an embedding for the graph. If min_genus_backtrack
308
has been called with record_embedding = True, then this
309
will return the first minimal embedding that we found.
310
Otherwise, this returns the first embedding considered.
311
312
DOCTESTS::
313
314
sage: import sage.graphs.genus
315
sage: G = Graph(graphs.CompleteGraph(5), implementation='c_graph', sparse=False)
316
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
317
sage: gb.genus(record_embedding = True)
318
1
319
sage: gb.get_embedding()
320
{0: [1, 2, 3, 4], 1: [0, 2, 3, 4], 2: [0, 1, 4, 3], 3: [0, 2, 1, 4], 4: [0, 3, 1, 2]}
321
sage: G = Graph(implementation='c_graph', sparse=False)
322
sage: G.add_edge(0,1)
323
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
324
sage: gb.get_embedding()
325
{0: [1], 1: [0]}
326
sage: G = Graph(implementation='c_graph', sparse=False)
327
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
328
sage: gb.get_embedding()
329
{}
330
331
"""
332
333
cdef int i,j, v, *w
334
cdef int *face_map = self.face_freeze
335
cdef list darts_to_verts
336
337
if self.num_verts == 0:
338
return {}
339
elif self.num_verts == 1:
340
return {0:[]}
341
342
darts_to_verts = [0 for i in range(self.num_darts)]
343
embedding = {}
344
for v in range(self.num_verts):
345
w = self.vertex_darts[v]
346
for i in range(self.degree[v]):
347
darts_to_verts[w[i]] = v
348
349
for v in range(self.num_verts):
350
w = self.vertex_darts[v]
351
i = w[0]
352
orbit_v = [darts_to_verts[edge_map(i)]]
353
354
j = face_map[edge_map(i)]
355
while j != i:
356
orbit_v.append( darts_to_verts[edge_map(j)] )
357
j = face_map[edge_map(j)]
358
359
embedding[v] = orbit_v
360
361
return embedding
362
363
cdef int run_cycle(self, int i):
364
"""
365
366
Mark off the orbit of `i` under face_map. If `i` has
367
been visited recently, bail immediately and return 0.
368
Otherwise, visit each vertex in the orbit of `i` and
369
set `self.visited[j] = k`, where `j` is the `k`-th
370
element in the orbit of `i`. Then, return 1.
371
372
In this manner, we are able to quickly check if a
373
particular element has been visited recently.
374
Moreover, we are able to distinguish what order three
375
elements of a single orbit come in. This is important
376
for `self.flip()`, and discussed in more detail there.
377
378
"""
379
380
cdef int j, counter = 1
381
382
if self.visited[i]:
383
return 0
384
j = self.face_map[i]
385
self.visited[i] = 1
386
counter += 1
387
while i != j:
388
self.visited[j] = 1 + counter
389
counter += 1
390
j = self.face_map[j]
391
return 1
392
393
cdef void flip(self, int v, int i):
394
"""
395
396
This is where the real work happens. Once cycles
397
have been counted for the initial face_map, we
398
make small local changes, and look at their effect
399
on the number of cycles.
400
401
Consider a vertex whose embedding is given by the
402
cycle
403
404
`self.vertex_darts[v] = [..., v0, v1, v2, ... ]`.
405
406
which implies that the vertex map has the cycle
407
408
`... -> v0 -> v1 -> v2 -> ... `
409
410
and say we'd like to exchange a1 and a2. Then,
411
we'll change the vertex map to
412
413
`... -> v0 -> v2 -> v1 -> ...`
414
415
and when this happens, we change the face map orbit
416
of `e0 = e(av)`, `e1 = e(v1)`, and `e2 = e(v2)`,
417
where `e` denotes the edge map.
418
419
In fact, the only orbits that can change are those
420
of `e0`, `e1`, and `e2`. Thus, to determine the
421
effect of the flip on the cycle structure, we need
422
only consider these orbits.
423
424
We find that the set of possibilities for a flip
425
to change the number of orbits among these three
426
elements is very small. In particular,
427
428
* If the three elements belong to distinct orbits,
429
a flip joins them into a single orbit.
430
431
* If the three elements are among exactly two
432
orbits, a flip does not change that fact
433
(though it does break the paired elements and
434
make a new pair, all we care about is the
435
number of cycles)
436
437
* If all three elements are in the same orbit,
438
a flip either disconnects them into three
439
distinct orbits, or maintains status quo.
440
441
To differentiate these situations, we need only
442
look at the order of `v0`, `v1`, and `v2` under
443
the orbit. If `e0 -> ... -> e2 -> ... -> e1`
444
before the flip, the cycle breaks into three.
445
Otherwise, the number of cycles stays the same.
446
447
448
449
"""
450
451
452
cdef int cycles = 0
453
cdef int *w = self.vertex_darts[v]
454
cdef int *face_map = self.face_map
455
456
cdef int v0,v1,v2,e0,e1,e2,f0,f1,f2, j, k
457
458
v0 = w[i-1]
459
v1 = w[i]
460
v2 = w[i+1]
461
462
e0 = edge_map(v0)
463
e1 = edge_map(v1)
464
e2 = edge_map(v2)
465
466
f0 = face_map[e0]
467
f1 = face_map[e1]
468
f2 = face_map[e2]
469
470
face_map[e0] = -1
471
face_map[e1] = -2
472
face_map[e2] = -3
473
474
j = face_map[f0]
475
while j >= 0:
476
j = face_map[j]
477
if j != -2:
478
k = face_map[f1]
479
while k >= 0:
480
k = face_map[k]
481
482
# Magic function follows. There are only four possibilities for j and k
483
# since j != -2. We use magic to avoid branching.
484
# j | k | MF(j,k)
485
# ---+----+--------
486
# -1 | -2 | -2
487
# -1 | -3 | 0
488
# -3 | -1 | 2
489
# -3 | -2 | 0
490
491
self.num_cycles += (2*k + 1 - j)%4
492
493
494
face_map[e0] = v2
495
face_map[e1] = f2
496
face_map[e2] = v1
497
498
w[i] = v2
499
w[i+1] = v1
500
501
502
cdef int count_cycles(self):
503
"""
504
505
Count all cycles.
506
507
"""
508
509
cdef int i, j, c, m
510
self.num_cycles = 0
511
512
for i in range(self.num_darts):
513
self.visited[i] = 0
514
515
for i in range(self.num_darts):
516
self.num_cycles+= self.run_cycle(i)
517
518
def genus(self, int style = 1, int cutoff = 0, int record_embedding = 0):
519
"""
520
521
Compute the minimal or maximal genus of self's graph. Note, this is a
522
remarkably naive algorithm for a very difficult problem. Most
523
interesting cases will take millenia to finish, with the exception of
524
graphs with max degree 3.
525
526
INPUT:
527
528
- ``style`` -- int, find minimum genus if 1, maximum genus if 2
529
530
- ``cutoff`` -- int, stop searching if search style is 1 and
531
genus <= cutoff, or if style is 2 and genus >= cutoff.
532
This is useful where the genus of the graph has a known bound.
533
534
- ``record_embedding`` -- bool, whether or not to remember the best
535
embedding seen. This embedding can be retrieved with
536
self.get_embedding().
537
538
OUTPUT:
539
540
the minimal or maximal genus for self's graph.
541
542
543
EXAMPLES::
544
545
sage: import sage.graphs.genus
546
sage: G = Graph(graphs.CompleteGraph(5), implementation='c_graph', sparse=False)
547
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
548
sage: gb.genus(cutoff = 2, record_embedding = True)
549
2
550
sage: E = gb.get_embedding()
551
sage: gb.genus(record_embedding = False)
552
1
553
sage: gb.get_embedding() == E
554
True
555
sage: gb.genus(style=2, cutoff=5)
556
3
557
sage: G = Graph(implementation='c_graph', sparse=False)
558
sage: gb = sage.graphs.genus.simple_connected_genus_backtracker(G._backend._cg)
559
sage: gb.genus()
560
0
561
562
"""
563
564
cdef int g, i
565
566
# in the original genus implementation, this case resulted in
567
# infinite recursion. oops. Let's skip that.
568
if self.num_verts <= 0:
569
return 0
570
sig_on()
571
if style == 1:
572
g = self.genus_backtrack(cutoff, record_embedding, &min_genus_check)
573
elif style == 2:
574
g = self.genus_backtrack(cutoff, record_embedding, &max_genus_check)
575
sig_off()
576
return g
577
578
cdef void reset_swap(self, int v):
579
"""
580
Reset the swapper associated with vertex ``v``.
581
"""
582
cdef int d = self.degree[v] - 1
583
reset_swap(d, self.swappers[v], self.swappers[v] + d)
584
585
cdef int next_swap(self, int v):
586
"""
587
Compute and return the next swap associated with the vertex ``v``.
588
"""
589
cdef int d = self.degree[v] - 1
590
return next_swap(d, self.swappers[v], self.swappers[v] + d)
591
592
cdef int genus_backtrack(self,
593
int cutoff,
594
int record_embedding,
595
(int (*)(simple_connected_genus_backtracker,int,int,int))check_embedding
596
):
597
"""
598
599
Here's the main backtracking routine. We iterate over all
600
all embeddings of self's graph by considering all cyclic
601
orderings of `self.vertex_darts`. We use the Steinhaus-
602
Johnson-Trotter algorithm to enumerate these by walking
603
over a poly-ary Gray code, and each time the Gray code
604
would flip a bit, we apply the next adjacent transposition
605
from S-J-T at that vertex.
606
607
We start by counting the number of cycles for our initial
608
embedding. From that point forward, we compute the amount
609
that each flip changes the number of cycles.
610
611
"""
612
613
cdef int next_swap, vertex
614
615
for vertex in range(self.num_verts):
616
self.reset_swap(vertex)
617
618
vertex = self.num_verts - 1
619
620
self.count_cycles()
621
622
if check_embedding(self, cutoff, record_embedding, 1):
623
return self.record_genus
624
625
next_swap = self.next_swap(vertex)
626
while 1:
627
while next_swap == -1:
628
self.reset_swap(vertex)
629
vertex -= 1
630
if vertex < 0:
631
return self.record_genus
632
next_swap = self.next_swap(vertex)
633
self.flip(vertex, next_swap + 1)
634
635
if check_embedding(self, cutoff, record_embedding, 0):
636
return self.record_genus
637
638
vertex = self.num_verts-1
639
next_swap = self.next_swap(vertex)
640
641
cdef int min_genus_check(simple_connected_genus_backtracker self,
642
int cutoff,
643
int record_embedding,
644
int initial):
645
"""
646
Search for the minimal genus.
647
If we find a genus <= cutoff, return 1 to quit entirely.
648
If we find a better genus than previously recorded, keep
649
track of that, and if record_embedding is set, record the
650
face map with self.freeze_face()
651
"""
652
653
cdef int g = 1 - (self.num_verts - self.num_darts/2 + self.num_cycles)/2
654
if g < self.record_genus or initial == 1:
655
self.record_genus = g
656
if record_embedding:
657
self.freeze_face()
658
if g <= cutoff:
659
return 1
660
return 0
661
662
cdef int max_genus_check(simple_connected_genus_backtracker self,
663
int cutoff,
664
int record_embedding,
665
int initial):
666
"""
667
Same as min_genus_check, but search for a maximum.
668
"""
669
670
cdef int g = 1 - (self.num_verts - self.num_darts/2 + self.num_cycles)/2
671
if g > self.record_genus or initial == 1:
672
self.record_genus = g
673
if record_embedding:
674
self.freeze_face()
675
if g >= cutoff:
676
return 1
677
return 0
678
679
680
681
682
683
def simple_connected_graph_genus(G, set_embedding = False, check = True, minimal=True):
684
"""
685
Compute the genus of a simple connected graph.
686
687
WARNING::
688
689
THIS MAY SEGFAULT OR HANG ON:
690
* DISCONNECTED GRAPHS
691
* DIRECTED GRAPHS
692
* LOOPED GRAPHS
693
* MULTIGRAPHS
694
695
DO NOT CALL WITH ``check = False`` UNLESS YOU ARE CERTAIN.
696
697
EXAMPLES::
698
699
sage: import sage.graphs.genus
700
sage: from sage.graphs.genus import simple_connected_graph_genus as genus
701
sage: [genus(g) for g in graphs(6) if g.is_connected()].count(1)
702
13
703
sage: G = graphs.FlowerSnark()
704
sage: genus(G) # see [1]
705
2
706
sage: G = graphs.BubbleSortGraph(4)
707
sage: genus(G)
708
0
709
sage: G = graphs.OddGraph(3)
710
sage: genus(G)
711
1
712
713
REFERENCS::
714
715
[1] http://www.springerlink.com/content/0776127h0r7548v7/
716
717
"""
718
cdef int style, cutoff
719
oG = G #original graph
720
721
if minimal and G.is_planar(set_embedding = set_embedding):
722
return 0
723
else:
724
if check:
725
if not G.is_connected():
726
raise ValueError, "Cannot compute the genus of a disconnected graph"
727
728
if G.is_directed() or G.has_multiple_edges() or G.has_loops():
729
G = G.to_simple()
730
731
G, vmap = G.relabel(inplace=False,return_map=True)
732
backmap = dict([(u,v) for (v,u) in vmap.items()])
733
G = Graph(G, implementation = 'c_graph', sparse=False)
734
GG = simple_connected_genus_backtracker(G._backend._cg)
735
736
if minimal:
737
style = 1
738
cutoff = 1
739
else:
740
style = 2
741
cutoff = 1 + (G.num_edges() - G.num_verts())/2 #rounding here is ok
742
743
g = GG.genus(style=style,cutoff=cutoff,record_embedding = set_embedding)
744
if set_embedding:
745
oE = {}
746
E = GG.get_embedding()
747
for v in E:
748
oE[backmap[v]] = [backmap[x] for x in E[v]]
749
oG.set_embedding(oE)
750
return g
751
752
753