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