Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/generic_graph_pyx.pyx
8815 views
1
"""
2
GenericGraph Cython functions
3
4
AUTHORS:
5
-- Robert L. Miller (2007-02-13): initial version
6
-- Robert W. Bradshaw (2007-03-31): fast spring layout algorithms
7
-- Nathann Cohen : exhaustive search
8
"""
9
10
#*****************************************************************************
11
# Copyright (C) 2007 Robert L. Miller <[email protected]>
12
# 2007 Robert W. Bradshaw <[email protected]>
13
#
14
# Distributed under the terms of the GNU General Public License (GPL)
15
# http://www.gnu.org/licenses/
16
#*****************************************************************************
17
18
include "sage/ext/interrupt.pxi"
19
include 'sage/ext/cdefs.pxi'
20
include 'sage/ext/stdsage.pxi'
21
22
# import from Python standard library
23
from sage.misc.prandom import random
24
25
# import from third-party library
26
from sage.graphs.base.sparse_graph cimport SparseGraph
27
28
29
cdef class GenericGraph_pyx(SageObject):
30
pass
31
32
def spring_layout_fast_split(G, **options):
33
"""
34
Graphs each component of G separately, placing them adjacent to
35
each other. This is done because on a disconnected graph, the
36
spring layout will push components further and further from each
37
other without bound, resulting in very tight clumps for each
38
component.
39
40
NOTE:
41
If the axis are scaled to fit the plot in a square, the
42
horizontal distance may end up being "squished" due to
43
the several adjacent components.
44
45
EXAMPLES:
46
47
sage: G = graphs.DodecahedralGraph()
48
sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))
49
sage: from sage.graphs.generic_graph_pyx import spring_layout_fast_split
50
sage: spring_layout_fast_split(G)
51
{0: [0.452..., 0.247...], ..., 502: [25.7..., 0.505...]}
52
53
AUTHOR:
54
Robert Bradshaw
55
"""
56
Gs = G.connected_components_subgraphs()
57
pos = {}
58
left = 0
59
buffer = 1/sqrt(len(G))
60
for g in Gs:
61
cur_pos = spring_layout_fast(g, **options)
62
xmin = min([x[0] for x in cur_pos.values()])
63
xmax = max([x[0] for x in cur_pos.values()])
64
if len(g) > 1:
65
buffer = (xmax - xmin)/sqrt(len(g))
66
for v, loc in cur_pos.iteritems():
67
loc[0] += left - xmin + buffer
68
pos[v] = loc
69
left += xmax - xmin + buffer
70
return pos
71
72
def spring_layout_fast(G, iterations=50, int dim=2, vpos=None, bint rescale=True, bint height=False, by_component = False, **options):
73
"""
74
Spring force model layout
75
76
This function primarily acts as a wrapper around run_spring,
77
converting to and from raw c types.
78
79
This kind of speed cannot be achieved by naive Cythonification of the
80
function alone, especially if we require a function call (let alone
81
an object creation) every time we want to add a pair of doubles.
82
83
INPUT:
84
85
- ``by_component`` - a boolean
86
87
EXAMPLES::
88
89
sage: G = graphs.DodecahedralGraph()
90
sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))
91
sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
92
sage: spring_layout_fast(G)
93
{0: [-0.0733..., 0.157...], ..., 502: [-0.551..., 0.682...]}
94
95
With ``split=True``, each component of G is layed out separately,
96
placing them adjacent to each other. This is done because on a
97
disconnected graph, the spring layout will push components further
98
and further from each other without bound, resulting in very tight
99
clumps for each component.
100
101
NOTE:
102
103
If the axis are scaled to fit the plot in a square, the
104
horizontal distance may end up being "squished" due to
105
the several adjacent components.
106
107
sage: G = graphs.DodecahedralGraph()
108
sage: for i in range(10): G.add_cycle(range(100*i, 100*i+3))
109
sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
110
sage: spring_layout_fast(G, by_component = True)
111
{0: [2.12..., -0.321...], ..., 502: [26.0..., -0.812...]}
112
"""
113
114
if by_component:
115
return spring_layout_fast_split(G, iterations=iterations, dim = dim,
116
vpos = vpos, rescale = rescale, height = height,
117
**options)
118
119
G = G.to_undirected()
120
vlist = G.vertices() # this defines a consistent order
121
122
cdef int i, j, x
123
cdef int n = G.order()
124
if n == 0:
125
return {}
126
127
cdef double* pos = <double*>sage_malloc(n * dim * sizeof(double))
128
if pos is NULL:
129
raise MemoryError, "error allocating scratch space for spring layout"
130
131
# convert or create the starting positions as a flat list of doubles
132
if vpos is None: # set the initial positions randomly in 1x1 box
133
for i from 0 <= i < n*dim:
134
pos[i] = random()
135
else:
136
for i from 0 <= i < n:
137
loc = vpos[vlist[i]]
138
for x from 0 <= x <dim:
139
pos[i*dim + x] = loc[x]
140
141
142
# here we construct a lexicographically ordered list of all edges
143
# where elist[2*i], elist[2*i+1] represents the i-th edge
144
cdef int* elist = <int*>sage_malloc( (2 * len(G.edges()) + 2) * sizeof(int) )
145
if elist is NULL:
146
sage_free(pos)
147
raise MemoryError, "error allocating scratch space for spring layout"
148
149
cdef int cur_edge = 0
150
151
for i from 0 <= i < n:
152
for j from i < j < n:
153
if G.has_edge(vlist[i], vlist[j]):
154
elist[cur_edge] = i
155
elist[cur_edge+1] = j
156
cur_edge += 2
157
158
# finish the list with -1, -1 which never gets matched
159
# but does get compared against when looking for the "next" edge
160
elist[cur_edge] = -1
161
elist[cur_edge+1] = -1
162
163
run_spring(iterations, dim, pos, elist, n, height)
164
165
# recenter
166
cdef double* cen
167
cdef double r, r2, max_r2 = 0
168
if rescale:
169
cen = <double *>sage_malloc(sizeof(double) * dim)
170
if cen is NULL:
171
sage_free(elist)
172
sage_free(pos)
173
raise MemoryError, "error allocating scratch space for spring layout"
174
for x from 0 <= x < dim: cen[x] = 0
175
for i from 0 <= i < n:
176
for x from 0 <= x < dim:
177
cen[x] += pos[i*dim + x]
178
for x from 0 <= x < dim: cen[x] /= n
179
for i from 0 <= i < n:
180
r2 = 0
181
for x from 0 <= x < dim:
182
pos[i*dim + x] -= cen[x]
183
r2 += pos[i*dim + x] * pos[i*dim + x]
184
if r2 > max_r2:
185
max_r2 = r2
186
r = 1 if max_r2 == 0 else sqrt(max_r2)
187
for i from 0 <= i < n:
188
for x from 0 <= x < dim:
189
pos[i*dim + x] /= r
190
sage_free(cen)
191
192
# put the data back into a position dictionary
193
vpos = {}
194
for i from 0 <= i < n:
195
vpos[vlist[i]] = [pos[i*dim+x] for x from 0 <= x < dim]
196
197
sage_free(pos)
198
sage_free(elist)
199
200
return vpos
201
202
203
cdef run_spring(int iterations, int dim, double* pos, int* edges, int n, bint height):
204
"""
205
Find a locally optimal layout for this graph, according to the
206
constraints that neighboring nodes want to be a fixed distance
207
from each other, and non-neighboring nodes always repel.
208
209
This is not a true physical model of mutually-repulsive particles
210
with springs, rather it is more a model of such things traveling,
211
without any inertia, through an (ever thickening) fluid.
212
213
TODO: The inertial model could be incorporated (with F=ma)
214
TODO: Are the hard-coded constants here optimal?
215
216
INPUT:
217
iterations -- number of steps to take
218
dim -- number of dimensions of freedom
219
pos -- already initialized initial positions
220
Each vertex is stored as [dim] consecutive doubles.
221
These doubles are then placed consecutively in the array.
222
For example, if dim=3, we would have
223
pos = [x_1, y_1, z_1, x_2, y_2, z_2, ... , x_n, y_n, z_n]
224
edges -- List of edges, sorted lexicographically by the first
225
(smallest) vertex, terminated by -1, -1.
226
The first two entries represent the first edge, and so on.
227
n -- number of vertices in the graph
228
height -- if True, do not update the last coordinate ever
229
230
OUTPUT:
231
Modifies contents of pos.
232
233
AUTHOR:
234
Robert Bradshaw
235
"""
236
237
cdef int cur_iter, cur_edge
238
cdef int i, j, x
239
240
# k -- the equilibrium distance between two adjacent nodes
241
cdef double t = 1, dt = t/(1e-20 + iterations), k = sqrt(1.0/n)
242
cdef double square_dist, force, scale
243
cdef double* disp_i
244
cdef double* disp_j
245
cdef double* delta
246
247
cdef double* disp = <double*>sage_malloc((n+1) * dim * sizeof(double))
248
if disp is NULL:
249
raise MemoryError, "error allocating scratch space for spring layout"
250
delta = &disp[n*dim]
251
252
if height:
253
update_dim = dim-1
254
else:
255
update_dim = dim
256
257
sig_on()
258
259
for cur_iter from 0 <= cur_iter < iterations:
260
cur_edge = 1 # offset by one for fast checking against 2nd element first
261
# zero out the disp vectors
262
memset(disp, 0, n * dim * sizeof(double))
263
for i from 0 <= i < n:
264
disp_i = disp + (i*dim)
265
for j from i < j < n:
266
disp_j = disp + (j*dim)
267
268
for x from 0 <= x < dim:
269
delta[x] = pos[i*dim+x] - pos[j*dim+x]
270
271
square_dist = delta[0] * delta[0]
272
for x from 1 <= x < dim:
273
square_dist += delta[x] * delta[x]
274
275
if square_dist < 0.01:
276
square_dist = 0.01
277
278
# they repel according to the (capped) inverse square law
279
force = k*k/square_dist
280
281
# and if they are neighbors, attract according Hooke's law
282
if edges[cur_edge] == j and edges[cur_edge-1] == i:
283
force -= sqrt(square_dist)/k
284
cur_edge += 2
285
286
# add this factor into each of the involved points
287
for x from 0 <= x < dim:
288
disp_i[x] += delta[x] * force
289
disp_j[x] -= delta[x] * force
290
291
# now update the positions
292
for i from 0 <= i < n:
293
disp_i = disp + (i*dim)
294
295
square_dist = disp_i[0] * disp_i[0]
296
for x from 1 <= x < dim:
297
square_dist += disp_i[x] * disp_i[x]
298
299
scale = t / (1 if square_dist < 0.01 else sqrt(square_dist))
300
301
for x from 0 <= x < update_dim:
302
pos[i*dim+x] += disp_i[x] * scale
303
304
t -= dt
305
306
sig_off()
307
308
sage_free(disp)
309
310
def binary(n, length=None):
311
"""
312
A quick python int to binary string conversion.
313
314
EXAMPLE:
315
sage: sage.graphs.generic_graph_pyx.binary(389)
316
'110000101'
317
sage: Integer(389).binary()
318
'110000101'
319
sage: sage.graphs.generic_graph_pyx.binary(2007)
320
'11111010111'
321
"""
322
cdef mpz_t i
323
mpz_init(i)
324
mpz_set_ui(i,n)
325
cdef char* s=mpz_get_str(NULL, 2, i)
326
t=str(s)
327
sage_free(s)
328
mpz_clear(i)
329
return t
330
331
def R(x):
332
"""
333
A helper function for the graph6 format. Described in [McK]
334
335
EXAMPLE:
336
sage: from sage.graphs.generic_graph_pyx import R
337
sage: R('110111010110110010111000001100000001000000001')
338
'vUqwK@?G'
339
340
REFERENCES:
341
McKay, Brendan. 'Description of graph6 and sparse6 encodings.'
342
http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)
343
"""
344
# pad on the right to make a multiple of 6
345
x += '0' * ( (6 - (len(x) % 6)) % 6)
346
347
# split into groups of 6, and convert numbers to decimal, adding 63
348
six_bits = ''
349
cdef int i
350
for i from 0 <= i < len(x)/6:
351
six_bits += chr( int( x[6*i:6*(i+1)], 2) + 63 )
352
return six_bits
353
354
def N(n):
355
"""
356
A helper function for the graph6 format. Described in [McK]
357
358
EXAMPLE:
359
sage: from sage.graphs.generic_graph_pyx import N
360
sage: N(13)
361
'L'
362
sage: N(136)
363
'~?AG'
364
365
REFERENCES:
366
McKay, Brendan. 'Description of graph6 and sparse6 encodings.'
367
http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)
368
"""
369
if n < 63:
370
return chr(n + 63)
371
else:
372
# get 18-bit rep of n
373
n = binary(n)
374
n = '0'*(18-len(n)) + n
375
return chr(126) + R(n)
376
377
def N_inverse(s):
378
"""
379
A helper function for the graph6 format. Described in [McK]
380
381
EXAMPLE:
382
sage: from sage.graphs.generic_graph_pyx import N_inverse
383
sage: N_inverse('~??~?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?')
384
(63, '?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?')
385
sage: N_inverse('_???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????')
386
(32, '???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????')
387
388
REFERENCES:
389
McKay, Brendan. 'Description of graph6 and sparse6 encodings.'
390
http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)
391
"""
392
if s[0] == chr(126): # first four bytes are N
393
a = binary(ord(s[1]) - 63).zfill(6)
394
b = binary(ord(s[2]) - 63).zfill(6)
395
c = binary(ord(s[3]) - 63).zfill(6)
396
n = int(a + b + c,2)
397
s = s[4:]
398
else: # only first byte is N
399
o = ord(s[0])
400
if o > 126 or o < 63:
401
raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
402
n = o - 63
403
s = s[1:]
404
return n, s
405
406
def R_inverse(s, n):
407
"""
408
A helper function for the graph6 format. Described in [McK]
409
410
REFERENCES:
411
McKay, Brendan. 'Description of graph6 and sparse6 encodings.'
412
http://cs.anu.edu.au/~bdm/data/formats.txt (2007-02-13)
413
414
EXAMPLE:
415
sage: from sage.graphs.generic_graph_pyx import R_inverse
416
sage: R_inverse('?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?', 63)
417
'0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'
418
sage: R_inverse('???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????', 32)
419
'0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'
420
421
"""
422
l = []
423
cdef int i
424
for i from 0 <= i < len(s):
425
o = ord(s[i])
426
if o > 126 or o < 63:
427
raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
428
a = binary(o-63)
429
l.append( '0'*(6-len(a)) + a )
430
m = "".join(l)
431
return m
432
433
def D_inverse(s, n):
434
"""
435
A helper function for the dig6 format.
436
437
EXAMPLE:
438
sage: from sage.graphs.generic_graph_pyx import D_inverse
439
sage: D_inverse('?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?', 63)
440
'0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'
441
sage: D_inverse('???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????', 32)
442
'0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'
443
444
"""
445
l = []
446
cdef int i
447
for i from 0 <= i < len(s):
448
o = ord(s[i])
449
if o > 126 or o < 63:
450
raise RuntimeError("The string seems corrupt: valid characters are \n" + ''.join([chr(i) for i in xrange(63,127)]))
451
a = binary(o-63)
452
l.append( '0'*(6-len(a)) + a )
453
m = "".join(l)
454
return m[:n*n]
455
456
# Exhaustive search in graphs
457
458
cdef class SubgraphSearch:
459
r"""
460
This class implements methods to exhaustively search for labelled
461
copies of a graph `H` in a larger graph `G`.
462
463
It is possible to look for induced subgraphs instead, and to
464
iterate or count the number of their occurrences.
465
466
ALGORITHM:
467
468
The algorithm is a brute-force search. Let `V(H) =
469
\{h_1,\dots,h_k\}`. It first tries to find in `G` a possible
470
representant of `h_1`, then a representant of `h_2` compatible
471
with `h_1`, then a representant of `h_3` compatible with the first
472
two, etc.
473
474
This way, most of the time we need to test far less than `k!
475
\binom{|V(G)|}{k}` subsets, and hope this brute-force technique
476
can sometimes be useful.
477
"""
478
def __init__(self, G, H, induced = False):
479
r"""
480
Constructor
481
482
This constructor only checks there is no inconsistency in the
483
input : `G` and `H` are both graphs or both digraphs and that `H`
484
has order at least 2.
485
486
EXAMPLE::
487
488
sage: g = graphs.PetersenGraph()
489
sage: g.subgraph_search(graphs.CycleGraph(5))
490
Subgraph of (Petersen graph): Graph on 5 vertices
491
492
TESTS:
493
494
Test proper initialization and deallocation, see :trac:`14067`.
495
We intentionally only create the class without doing any
496
computations with it::
497
498
sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
499
sage: SubgraphSearch(Graph(5), Graph(1))
500
Traceback (most recent call last):
501
...
502
ValueError: Searched graph should have at least 2 vertices.
503
sage: SubgraphSearch(Graph(5), Graph(2))
504
<sage.graphs.generic_graph_pyx.SubgraphSearch ...>
505
"""
506
if H.order() <= 1:
507
raise ValueError("Searched graph should have at least 2 vertices.")
508
509
if sum([G.is_directed(), H.is_directed()]) == 1:
510
raise ValueError("One graph can not be directed while the other is not.")
511
512
G._scream_if_not_simple(allow_loops=True)
513
H._scream_if_not_simple(allow_loops=True)
514
515
self._initialization()
516
517
def __iter__(self):
518
r"""
519
Returns an iterator over all the labeleld subgraphs of `G`
520
isomorphic to `H`.
521
522
EXAMPLE:
523
524
Iterating through all the `P_3` of `P_5`::
525
526
sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
527
sage: g = graphs.PathGraph(5)
528
sage: h = graphs.PathGraph(3)
529
sage: S = SubgraphSearch(g, h)
530
sage: for p in S:
531
... print p
532
[0, 1, 2]
533
[1, 2, 3]
534
[2, 1, 0]
535
[2, 3, 4]
536
[3, 2, 1]
537
[4, 3, 2]
538
"""
539
self._initialization()
540
return self
541
542
def cardinality(self):
543
r"""
544
Returns the number of labelled subgraphs of `G` isomorphic to
545
`H`.
546
547
.. NOTE::
548
549
This method counts the subgraphs by enumerating them all !
550
Hence it probably is not a good idea to count their number
551
before enumerating them :-)
552
553
EXAMPLE:
554
555
Counting the number of labelled `P_3` in `P_5`::
556
557
sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
558
sage: g = graphs.PathGraph(5)
559
sage: h = graphs.PathGraph(3)
560
sage: S = SubgraphSearch(g, h)
561
sage: S.cardinality()
562
6
563
"""
564
if self.nh > self.ng:
565
return 0
566
567
self._initialization()
568
cdef int i
569
570
i=0
571
for _ in self:
572
i+=1
573
574
return i
575
576
def _initialization(self):
577
r"""
578
Initialization of the variables.
579
580
Once the memory allocation is done in :meth:`__cinit__`,
581
several variables need to be set to a default value. As this
582
operation needs to be performed before any call to
583
:meth:`__iter__` or to :meth:`cardinality`, it is cleaner to
584
create a dedicated method.
585
586
EXAMPLE:
587
588
Finding two times the first occurrence through the
589
re-initialization of the instance ::
590
591
sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
592
sage: g = graphs.PathGraph(5)
593
sage: h = graphs.PathGraph(3)
594
sage: S = SubgraphSearch(g, h)
595
sage: S.__next__()
596
[0, 1, 2]
597
sage: S._initialization()
598
sage: S.__next__()
599
[0, 1, 2]
600
"""
601
602
memset(self.busy, 0, self.ng * sizeof(int))
603
# 0 is the first vertex we use, so it is at first busy
604
self.busy[0] = 1
605
# stack -- list of the vertices which are part of the partial copy of H
606
# in G.
607
#
608
# stack[i] -- the integer corresponding to the vertex of G representing
609
# the i-th vertex of H.
610
#
611
# stack[i] = -1 means that i is not represented
612
# ... yet!
613
614
self.stack[0] = 0
615
self.stack[1] = -1
616
617
# Number of representants we have already found. Set to 1 as vertex 0
618
# is already part of the partial copy of H in G.
619
self.active = 1
620
621
def __cinit__(self, G, H, induced = False):
622
r"""
623
Cython constructor
624
625
This method initializes all the C values.
626
627
EXAMPLE::
628
629
sage: g = graphs.PetersenGraph()
630
sage: g.subgraph_search(graphs.CycleGraph(5))
631
Subgraph of (Petersen graph): Graph on 5 vertices
632
"""
633
634
# Storing the number of vertices
635
self.ng = G.order()
636
self.nh = H.order()
637
638
# Storing the list of vertices
639
self.g_vertices = G.vertices()
640
641
# Are the graphs directed (in __init__(), we check
642
# whether both are of the same type)
643
self.directed = G.is_directed()
644
645
cdef int i, j, k
646
647
self.tmp_array = <int *>sage_malloc(self.ng * sizeof(int))
648
if self.tmp_array == NULL:
649
raise MemoryError()
650
651
# Should we look for induced subgraphs ?
652
if induced:
653
self.is_admissible = vectors_equal
654
else:
655
self.is_admissible = vectors_inferior
656
657
# static copies of the two graphs for more efficient operations
658
self.g = DenseGraph(self.ng)
659
self.h = DenseGraph(self.nh)
660
661
# copying the adjacency relations in both G and H
662
i = 0
663
for row in G.adjacency_matrix():
664
j = 0
665
for k in row:
666
if k:
667
self.g.add_arc(i, j)
668
j += 1
669
i += 1
670
i = 0
671
for row in H.adjacency_matrix():
672
j = 0
673
for k in row:
674
if k:
675
self.h.add_arc(i, j)
676
j += 1
677
i += 1
678
679
# A vertex is said to be busy if it is already part of the partial copy
680
# of H in G.
681
self.busy = <int *>sage_malloc(self.ng * sizeof(int))
682
self.stack = <int *>sage_malloc(self.nh * sizeof(int))
683
684
# vertices is equal to range(nh), as an int *variable
685
self.vertices = <int *>sage_malloc(self.nh * sizeof(int))
686
for 0 <= i < self.nh:
687
self.vertices[i] = i
688
689
# line_h_out[i] represents the adjacency sequence of vertex i
690
# in h relative to vertices 0, 1, ..., i-1
691
self.line_h_out = <int **>sage_malloc(self.nh * sizeof(int *))
692
for 0 <= i < self.nh:
693
self.line_h_out[i] = <int *> sage_malloc(self.nh * sizeof(int *))
694
if self.line_h_out[i] is NULL:
695
raise MemoryError()
696
self.h.adjacency_sequence_out(i, self.vertices, i, self.line_h_out[i])
697
698
# Similarly in the opposite direction (only useful if the
699
# graphs are directed)
700
if self.directed:
701
self.line_h_in = <int **>sage_malloc(self.nh * sizeof(int *))
702
for 0 <= i < self.nh:
703
self.line_h_in[i] = <int *> sage_malloc(self.nh * sizeof(int *))
704
if self.line_h_in[i] is NULL:
705
raise MemoryError()
706
707
self.h.adjacency_sequence_in(i, self.vertices, i, self.line_h_in[i])
708
709
def __next__(self):
710
r"""
711
Returns the next isomorphic subgraph if any, and raises a
712
``StopIteration`` otherwise.
713
714
EXAMPLE::
715
716
sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
717
sage: g = graphs.PathGraph(5)
718
sage: h = graphs.PathGraph(3)
719
sage: S = SubgraphSearch(g, h)
720
sage: S.__next__()
721
[0, 1, 2]
722
"""
723
sig_on()
724
cdef bint is_admissible
725
cdef int * tmp_array = self.tmp_array
726
727
# as long as there is a non-void partial copy of H in G
728
while self.active >= 0:
729
# If we are here and found nothing yet, we try the next possible
730
# vertex as a representant of the active i-th vertex of H.
731
self.i = self.stack[self.active] + 1
732
# Looking for a vertex that is not busy and compatible with the
733
# partial copy we have of H.
734
while self.i < self.ng:
735
if self.busy[self.i]:
736
self.i += 1
737
else:
738
# Testing whether the vertex we picked is a
739
# correct extension by checking the edges from the
740
# vertices already selected to self.i satisfy the
741
# constraints
742
self.g.adjacency_sequence_out(self.active, self.stack, self.i, tmp_array)
743
is_admissible = self.is_admissible(self.active, tmp_array, self.line_h_out[self.active])
744
745
# If G and H are digraphs, we also need to ensure
746
# the edges going in the opposite direction
747
# satisfy the constraints
748
if is_admissible and self.directed:
749
self.g.adjacency_sequence_in(self.active, self.stack, self.i, tmp_array)
750
is_admissible = is_admissible and self.is_admissible(self.active, tmp_array, self.line_h_in[self.active])
751
752
if is_admissible:
753
break
754
else:
755
self.i += 1
756
757
# If we have found a good representant of H's i-th vertex in G
758
if self.i < self.ng:
759
760
# updating the last vertex of the stack
761
if self.stack[self.active] != -1:
762
self.busy[self.stack[self.active]] = 0
763
self.stack[self.active] = self.i
764
765
# We have found our copy !!!
766
if self.active == self.nh-1:
767
sig_off()
768
return [self.g_vertices[self.stack[l]] for l in xrange(self.nh)]
769
770
# We are still missing several vertices ...
771
else:
772
self.busy[self.stack[self.active]] = 1
773
self.active += 1
774
775
# we begin the search of the next vertex at 0
776
self.stack[self.active] = -1
777
778
# If we found no representant for the i-th vertex, it
779
# means that we cannot extend the current copy of H so we
780
# update the status of stack[active] and prepare to change
781
# the previous vertex.
782
783
else:
784
if self.stack[self.active] != -1:
785
self.busy[self.stack[self.active]] = 0
786
self.stack[self.active] = -1
787
self.active -= 1
788
789
sig_off()
790
raise StopIteration
791
792
def __dealloc__(self):
793
r"""
794
Freeing the allocated memory.
795
"""
796
797
# Free the memory
798
sage_free(self.busy)
799
sage_free(self.stack)
800
sage_free(self.vertices)
801
for 0 <= i < self.nh:
802
sage_free(self.line_h_out[i])
803
sage_free(self.line_h_out)
804
805
if self.directed:
806
for 0 <= i < self.nh:
807
sage_free(self.line_h_in[i])
808
sage_free(self.line_h_in)
809
810
if self.tmp_array != NULL:
811
sage_free(self.tmp_array)
812
813
cdef inline bint vectors_equal(int n, int *a, int *b):
814
r"""
815
Tests whether the two given vectors are equal. Two integer vectors
816
`a = (a_1, a_2, \dots, a_n)` and `b = (b_1, b_2, \dots, b_n)` are equal
817
iff `a_i = b_i` for all `i = 1, 2, \dots, n`. See the function
818
``_test_vectors_equal_inferior()`` for unit tests.
819
820
INPUT:
821
822
- ``n`` -- positive integer; length of the vectors.
823
824
- ``a``, ``b`` -- two vectors of integers.
825
826
OUTPUT:
827
828
- ``True`` if ``a`` and ``b`` are the same vector; ``False`` otherwise.
829
"""
830
cdef int i = 0
831
for 0 <= i < n:
832
if a[i] != b[i]:
833
return False
834
return True
835
836
cdef inline bint vectors_inferior(int n, int *a, int *b):
837
r"""
838
Tests whether the second vector of integers is inferior to the first. Let
839
`u = (u_1, u_2, \dots, u_k)` and `v = (v_1, v_2, \dots, v_k)` be two
840
integer vectors of equal length. Then `u` is said to be less than
841
(or inferior to) `v` if `u_i \leq v_i` for all `i = 1, 2, \dots, k`. See
842
the function ``_test_vectors_equal_inferior()`` for unit tests. Given two
843
equal integer vectors `u` and `v`, `u` is inferior to `v` and vice versa.
844
We could also define two vectors `a` and `b` to be equal if `a` is
845
inferior to `b` and `b` is inferior to `a`.
846
847
INPUT:
848
849
- ``n`` -- positive integer; length of the vectors.
850
851
- ``a``, ``b`` -- two vectors of integers.
852
853
OUTPUT:
854
855
- ``True`` if ``b`` is inferior to (or less than) ``a``; ``False``
856
otherwise.
857
"""
858
cdef int i = 0
859
for 0 <= i < n:
860
if a[i] < b[i]:
861
return False
862
return True
863
864
##############################
865
# Further tests. Unit tests for methods, functions, classes defined with cdef.
866
##############################
867
868
def _test_vectors_equal_inferior():
869
"""
870
Unit testing the function ``vectors_equal()``. No output means that no
871
errors were found in the random tests.
872
873
TESTS::
874
875
sage: from sage.graphs.generic_graph_pyx import _test_vectors_equal_inferior
876
sage: _test_vectors_equal_inferior()
877
"""
878
from sage.misc.prandom import randint
879
n = randint(500, 10**3)
880
cdef int *u = <int *>sage_malloc(n * sizeof(int))
881
cdef int *v = <int *>sage_malloc(n * sizeof(int))
882
cdef int i
883
# equal vectors: u = v
884
for 0 <= i < n:
885
u[i] = randint(-10**6, 10**6)
886
v[i] = u[i]
887
try:
888
assert vectors_equal(n, u, v)
889
assert vectors_equal(n, v, u)
890
# Since u and v are equal vectors, then u is inferior to v and v is
891
# inferior to u. One could also define u and v as being equal if
892
# u is inferior to v and vice versa.
893
assert vectors_inferior(n, u, v)
894
assert vectors_inferior(n, v, u)
895
except AssertionError:
896
sage_free(u)
897
sage_free(v)
898
raise AssertionError("Vectors u and v should be equal.")
899
# Different vectors: u != v because we have u_j > v_j for some j. Thus,
900
# u_i = v_i for 0 <= i < j and u_j > v_j. For j < k < n - 2, we could have:
901
# (1) u_k = v_k,
902
# (2) u_k < v_k, or
903
# (3) u_k > v_k.
904
# And finally, u_{n-1} < v_{n-1}.
905
cdef int j = randint(1, n//2)
906
cdef int k
907
for 0 <= i < j:
908
u[i] = randint(-10**6, 10**6)
909
v[i] = u[i]
910
u[j] = randint(-10**6, 10**6)
911
v[j] = u[j] - randint(1, 10**6)
912
for j < k < n:
913
u[k] = randint(-10**6, 10**6)
914
v[k] = randint(-10**6, 10**6)
915
u[n - 1] = v[n - 1] - randint(1, 10**6)
916
try:
917
assert not vectors_equal(n, u, v)
918
assert not vectors_equal(n, v, u)
919
# u is not inferior to v because at least u_j > v_j
920
assert u[j] > v[j]
921
assert not vectors_inferior(n, v, u)
922
# v is not inferior to u because at least v_{n-1} > u_{n-1}
923
assert v[n - 1] > u[n - 1]
924
assert not vectors_inferior(n, u, v)
925
except AssertionError:
926
sage_free(u)
927
sage_free(v)
928
raise AssertionError("".join([
929
"Vectors u and v should not be equal. ",
930
"u should not be inferior to v, and vice versa."]))
931
# Different vectors: u != v because we have u_j < v_j for some j. Thus,
932
# u_i = v_i for 0 <= i < j and u_j < v_j. For j < k < n - 2, we could have:
933
# (1) u_k = v_k,
934
# (2) u_k < v_k, or
935
# (3) u_k > v_k.
936
# And finally, u_{n-1} > v_{n-1}.
937
j = randint(1, n//2)
938
for 0 <= i < j:
939
u[i] = randint(-10**6, 10**6)
940
v[i] = u[i]
941
u[j] = randint(-10**6, 10**6)
942
v[j] = u[j] + randint(1, 10**6)
943
for j < k < n:
944
u[k] = randint(-10**6, 10**6)
945
v[k] = randint(-10**6, 10**6)
946
u[n - 1] = v[n - 1] + randint(1, 10**6)
947
try:
948
assert not vectors_equal(n, u, v)
949
assert not vectors_equal(n, v, u)
950
# u is not inferior to v because at least u_{n-1} > v_{n-1}
951
assert u[n - 1] > v[n - 1]
952
assert not vectors_inferior(n, v, u)
953
# v is not inferior to u because at least u_j < v_j
954
assert u[j] < v[j]
955
assert not vectors_inferior(n, u, v)
956
except AssertionError:
957
sage_free(u)
958
sage_free(v)
959
raise AssertionError("".join([
960
"Vectors u and v should not be equal. ",
961
"u should not be inferior to v, and vice versa."]))
962
# different vectors u != v
963
# What's the probability of two random vectors being equal?
964
for 0 <= i < n:
965
u[i] = randint(-10**6, 10**6)
966
v[i] = randint(-10**6, 10**6)
967
try:
968
assert not vectors_equal(n, u, v)
969
assert not vectors_equal(n, v, u)
970
except AssertionError:
971
sage_free(u)
972
sage_free(v)
973
raise AssertionError("Vectors u and v should not be equal.")
974
# u is inferior to v, but v is not inferior to u
975
for 0 <= i < n:
976
v[i] = randint(-10**6, 10**6)
977
u[i] = randint(-10**6, 10**6)
978
while u[i] > v[i]:
979
u[i] = randint(-10**6, 10**6)
980
try:
981
assert not vectors_equal(n, u, v)
982
assert not vectors_equal(n, v, u)
983
assert vectors_inferior(n, v, u)
984
assert not vectors_inferior(n, u, v)
985
except AssertionError:
986
raise AssertionError(
987
"u should be inferior to v, but v is not inferior to u.")
988
finally:
989
sage_free(u)
990
sage_free(v)
991
992
cpdef tuple find_hamiltonian( G, long max_iter=100000, long reset_bound=30000, long backtrack_bound=1000, find_path=False ):
993
r"""
994
Randomized backtracking for finding hamiltonian cycles and paths.
995
996
ALGORITHM:
997
998
A path ``P`` is maintained during the execution of the algorithm. Initially
999
the path will contain an edge of the graph. Every 10 iterations the path
1000
is reversed. Every ``reset_bound`` iterations the path will be cleared
1001
and the procedure is restarted. Every ``backtrack_bound`` steps we discard
1002
the last five vertices and continue with the procedure. The total number
1003
of steps in the algorithm is controlled by ``max_iter``. If a hamiltonian
1004
cycle or hamiltonian path is found it is returned. If the number of steps reaches
1005
``max_iter`` then a longest path is returned. See OUTPUT for more details.
1006
1007
1008
INPUT:
1009
1010
- ``G`` - Graph.
1011
1012
- ``max_iter`` - Maximum number of iterations.
1013
1014
- ``reset_bound`` - Number of iterations before restarting the
1015
procedure.
1016
1017
- ``backtrack_bound`` - Number of iterations to elapse before
1018
discarding the last 5 vertices of the path.
1019
1020
- ``find_path`` - If set to ``True``, will search a hamiltonian
1021
path. If ``False``, will search for a hamiltonian
1022
cycle. Default value is ``False``.
1023
1024
OUTPUT:
1025
1026
A pair ``(B,P)``, where ``B`` is a Boolean and ``P`` is a list of vertices.
1027
1028
* If ``B`` is ``True`` and ``find_path`` is ``False``, ``P``
1029
represents a hamiltonian cycle.
1030
1031
* If ``B`` is ``True`` and ``find_path`` is ``True``, ``P``
1032
represents a hamiltonian path.
1033
1034
* If ``B`` is false, then ``P`` represents the longest path
1035
found during the execution of the algorithm.
1036
1037
.. WARNING::
1038
1039
May loop endlessly when run on a graph with vertices of degree
1040
1.
1041
1042
EXAMPLES:
1043
1044
First we try the algorithm in the Dodecahedral graph, which is
1045
hamiltonian, so we are able to find a hamiltonian cycle and a
1046
hamiltonian path ::
1047
1048
sage: from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
1049
sage: G=graphs.DodecahedralGraph()
1050
sage: fh(G)
1051
(True, [9, 10, 0, 19, 3, 2, 1, 8, 7, 6, 5, 4, 17, 18, 11, 12, 16, 15, 14, 13])
1052
sage: fh(G,find_path=True)
1053
(True, [8, 9, 10, 11, 18, 17, 4, 3, 19, 0, 1, 2, 6, 7, 14, 13, 12, 16, 15, 5])
1054
1055
Another test, now in the Moebius-Kantor graph which is also
1056
hamiltonian, as in our previous example, we are able to find a
1057
hamiltonian cycle and path ::
1058
1059
sage: G=graphs.MoebiusKantorGraph()
1060
sage: fh(G)
1061
(True, [5, 4, 3, 2, 10, 15, 12, 9, 1, 0, 7, 6, 14, 11, 8, 13])
1062
sage: fh(G,find_path=True)
1063
(True, [4, 5, 6, 7, 15, 12, 9, 1, 0, 8, 13, 10, 2, 3, 11, 14])
1064
1065
Now, we try the algorithm on a non hamiltonian graph, the Petersen
1066
graph. This graph is known to be hypohamiltonian, so a
1067
hamiltonian path can be found ::
1068
1069
sage: G=graphs.PetersenGraph()
1070
sage: fh(G)
1071
(False, [7, 9, 4, 3, 2, 1, 0, 5, 8, 6])
1072
sage: fh(G,find_path=True)
1073
(True, [3, 8, 6, 1, 2, 7, 9, 4, 0, 5])
1074
1075
We now show the algorithm working on another known hypohamiltonian
1076
graph, the generalized Petersen graph with parameters 11 and 2 ::
1077
1078
sage: G=graphs.GeneralizedPetersenGraph(11,2)
1079
sage: fh(G)
1080
(False, [13, 11, 0, 10, 9, 20, 18, 16, 14, 3, 2, 1, 12, 21, 19, 8, 7, 6, 17, 15, 4, 5])
1081
sage: fh(G,find_path=True)
1082
(True, [7, 18, 20, 9, 8, 19, 17, 6, 5, 16, 14, 3, 4, 15, 13, 11, 0, 10, 21, 12, 1, 2])
1083
1084
Finally, an example on a graph which does not have a hamiltonian
1085
path ::
1086
1087
sage: G=graphs.HyperStarGraph(5,2)
1088
sage: fh(G,find_path=False)
1089
(False, ['00011', '10001', '01001', '11000', '01010', '10010', '00110', '10100', '01100'])
1090
sage: fh(G,find_path=True)
1091
(False, ['00101', '10001', '01001', '11000', '01010', '10010', '00110', '10100', '01100'])
1092
"""
1093
1094
from sage.misc.prandom import randint
1095
cdef int n = G.order()
1096
cdef int m = G.num_edges()
1097
1098
#Initialize the path.
1099
cdef int *path = <int *>sage_malloc(n * sizeof(int))
1100
memset(path, -1, n * sizeof(int))
1101
1102
#Initialize the membership array
1103
cdef bint *member = <bint *>sage_malloc(n * sizeof(int))
1104
memset(member, 0, n * sizeof(int))
1105
1106
# static copy of the graph for more efficient operations
1107
cdef SparseGraph g = SparseGraph(n)
1108
# copying the adjacency relations in G
1109
cdef int i
1110
cdef int j
1111
i = 0
1112
for row in G.adjacency_matrix():
1113
j = 0
1114
for k in row:
1115
if k:
1116
g.add_arc(i, j)
1117
j += 1
1118
i += 1
1119
# Cache copy of the vertices
1120
cdef list vertices = g.verts()
1121
1122
# A list to store the available vertices at each step
1123
cdef list available_vertices=[]
1124
1125
#We now work towards picking a random edge
1126
# First we pick a random vertex u
1127
cdef int x = randint( 0, n-1 )
1128
cdef int u = vertices[x]
1129
# Then we pick at random a neighbor of u
1130
x = randint( 0, len(g.out_neighbors( u ))-1 )
1131
cdef int v = g.out_neighbors( u )[x]
1132
# This will be the first edge in the path
1133
cdef int length=2
1134
path[ 0 ] = u
1135
path[ 1 ] = v
1136
member[ u ] = True
1137
member[ v ] = True
1138
1139
#Initialize all the variables neccesary to start iterating
1140
cdef bint done = False
1141
cdef long counter = 0
1142
cdef long bigcount = 0
1143
cdef int longest = length
1144
1145
#Initialize a path to contain the longest path
1146
cdef int *longest_path = <int *>sage_malloc(n * sizeof(int))
1147
memset(longest_path, -1, n * sizeof(int))
1148
i = 0
1149
for 0 <= i < length:
1150
longest_path[ i ] = path[ i ]
1151
1152
#Initialize a temporary path for flipping
1153
cdef int *temp_path = <int *>sage_malloc(n * sizeof(int))
1154
memset(temp_path, -1, n * sizeof(int))
1155
1156
cdef bint longer = False
1157
cdef bint good = True
1158
1159
while not done:
1160
counter = counter + 1
1161
if counter%10 == 0:
1162
#Reverse the path
1163
1164
i=0
1165
for 0<= i < length/2:
1166
t=path[ i ]
1167
path[ i ] = path[ length - i - 1]
1168
path[ length -i -1 ] = t
1169
1170
if counter > reset_bound:
1171
bigcount = bigcount + 1
1172
counter = 1
1173
1174
#Time to reset the procedure
1175
for 0 <= i < n:
1176
member[ i ]=False
1177
# First we pick a random vertex u
1178
x = randint( 0, n-1 )
1179
u = vertices[x]
1180
# Then we pick at random a neighbor of u
1181
degree = len(g.out_neighbors( u ))
1182
x = randint( 0, degree-1 )
1183
v = g.out_neighbors( u )[x]
1184
# This will be the first edge in the path
1185
length=2
1186
path[ 0 ] = u
1187
path[ 1 ] = v
1188
member[ u ] = True
1189
member[ v ] = True
1190
1191
if counter%backtrack_bound == 0:
1192
for 0 <= i < 5:
1193
member[ path[length - i - 1] ] = False
1194
length = length - 5
1195
longer = False
1196
1197
available_vertices = []
1198
for u in g.out_neighbors( path[ length-1 ] ):
1199
if not member[ u ]:
1200
available_vertices.append( u )
1201
1202
n_available=len( available_vertices )
1203
if n_available > 0:
1204
longer = True
1205
x=randint( 0, n_available-1 )
1206
path[ length ] = available_vertices[ x ]
1207
length = length + 1
1208
member [ available_vertices[ x ] ] = True
1209
1210
if not longer and length > longest:
1211
1212
for 0 <= i < length:
1213
longest_path[ i ] = path[ i ]
1214
1215
longest = length
1216
if not longer:
1217
1218
memset(temp_path, -1, n * sizeof(int))
1219
degree = len(g.out_neighbors( path[ length-1 ] ))
1220
while True:
1221
x = randint( 0, degree-1 )
1222
u = g.out_neighbors(path[length - 1])[ x ]
1223
if u != path[length - 2]:
1224
break
1225
1226
flag = False
1227
i=0
1228
j=0
1229
for 0 <= i < length:
1230
if i > length-j-1:
1231
break
1232
if flag:
1233
t=path[ i ]
1234
path[ i ] = path[ length - j - 1]
1235
path[ length - j - 1 ] = t
1236
j=j+1
1237
if path[ i ] == u:
1238
flag = True
1239
if length == n:
1240
if find_path:
1241
done=True
1242
else:
1243
done = g.has_arc( path[n-1], path[0] )
1244
1245
if bigcount*reset_bound > max_iter:
1246
verts=G.vertices()
1247
output=[ verts[ longest_path[i] ] for i from 0<= i < longest ]
1248
sage_free( member )
1249
sage_free( path )
1250
sage_free( longest_path )
1251
sage_free( temp_path )
1252
return (False, output)
1253
# #
1254
# # Output test
1255
# #
1256
1257
# Test adjacencies
1258
for 0 <=i < n-1:
1259
u = path[i]
1260
v = path[i + 1]
1261
#Graph is simple, so both arcs are present
1262
if not g.has_arc( u, v ):
1263
good = False
1264
break
1265
if good == False:
1266
raise RuntimeError( 'Vertices %d and %d are consecutive in the cycle but are not ajacent.'%(u,v) )
1267
if not find_path and not g.has_arc( path[0], path[n-1] ):
1268
raise RuntimeError( 'Vertices %d and %d are not ajacent.'%(path[0],path[n-1]) )
1269
for 0 <= u < n:
1270
member[ u ]=False
1271
1272
for 0 <= u < n:
1273
if member[ u ]:
1274
good = False
1275
break
1276
member[ u ] = True
1277
if good == False:
1278
raise RuntimeError( 'Vertex %d appears twice in the cycle.'%(u) )
1279
verts=G.vertices()
1280
output=[ verts[path[i]] for i from 0<= i < length ]
1281
sage_free( member )
1282
sage_free( path )
1283
sage_free( longest_path )
1284
sage_free( temp_path )
1285
1286
return (True,output)
1287
1288
1289