Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/generators/families.py
8815 views
1
# -*- coding: utf-8 -*-
2
r"""
3
Families of graphs
4
5
The methods defined here appear in :mod:`sage.graphs.graph_generators`.
6
7
AUTHORS:
8
9
- David Coudert (2012) Ringed Trees
10
11
"""
12
13
###########################################################################
14
#
15
# Copyright (C) 2006 Robert L. Miller <[email protected]>
16
# and Emily A. Kirkman
17
# Copyright (C) 2009 Michael C. Yurko <[email protected]>
18
#
19
# Distributed under the terms of the GNU General Public License (GPL)
20
# http://www.gnu.org/licenses/
21
###########################################################################
22
23
# import from Sage library
24
from sage.graphs.graph import Graph
25
from sage.graphs import graph
26
from math import sin, cos, pi
27
28
def JohnsonGraph(n, k):
29
r"""
30
Returns the Johnson graph with parameters `n, k`.
31
32
Johnson graphs are a special class of undirected graphs defined from systems
33
of sets. The vertices of the Johnson graph `J(n,k)` are the `k`-element
34
subsets of an `n`-element set; two vertices are adjacent when they meet in a
35
`(k-1)`-element set. For more information about Johnson graphs, see the
36
corresponding :wikipedia:`Wikipedia page <Johnson_graph>`.
37
38
EXAMPLES:
39
40
The Johnson graph is a Hamiltonian graph. ::
41
42
sage: g = graphs.JohnsonGraph(7, 3)
43
sage: g.is_hamiltonian()
44
True
45
46
Every Johnson graph is vertex transitive. ::
47
48
sage: g = graphs.JohnsonGraph(6, 4)
49
sage: g.is_vertex_transitive()
50
True
51
52
The complement of the Johnson graph `J(n,2)` is isomorphic to the Knesser
53
Graph `K(n,2)`. In paritcular the complement of `J(5,2)` is isomorphic to
54
the Petersen graph. ::
55
56
sage: g = graphs.JohnsonGraph(5,2)
57
sage: g.complement().is_isomorphic(graphs.PetersenGraph())
58
True
59
"""
60
61
g = Graph(name="Johnson graph with parameters "+str(n)+","+str(k))
62
from sage.combinat.subset import Set, Subsets
63
64
S = Set(range(n))
65
g.add_vertices(Subsets(S, k))
66
67
for sub in Subsets(S, k-1):
68
elem_left = S - sub
69
for i in elem_left:
70
for j in elem_left:
71
if j <= i:
72
continue
73
g.add_edge(sub+Set([i]),sub+Set([j]))
74
75
return g
76
77
78
def KneserGraph(n,k):
79
r"""
80
Returns the Kneser Graph with parameters `n, k`.
81
82
The Kneser Graph with parameters `n,k` is the graph
83
whose vertices are the `k`-subsets of `[0,1,\dots,n-1]`, and such
84
that two vertices are adjacent if their corresponding sets
85
are disjoint.
86
87
For example, the Petersen Graph can be defined
88
as the Kneser Graph with parameters `5,2`.
89
90
EXAMPLE::
91
92
sage: KG=graphs.KneserGraph(5,2)
93
sage: print KG.vertices()
94
[{4, 5}, {1, 3}, {2, 5}, {2, 3}, {3, 4}, {3, 5}, {1, 4}, {1, 5}, {1, 2}, {2, 4}]
95
sage: P=graphs.PetersenGraph()
96
sage: P.is_isomorphic(KG)
97
True
98
99
TESTS::
100
101
sage: KG=graphs.KneserGraph(0,0)
102
Traceback (most recent call last):
103
...
104
ValueError: Parameter n should be a strictly positive integer
105
sage: KG=graphs.KneserGraph(5,6)
106
Traceback (most recent call last):
107
...
108
ValueError: Parameter k should be a strictly positive integer inferior to n
109
"""
110
111
if not n>0:
112
raise ValueError, "Parameter n should be a strictly positive integer"
113
if not (k>0 and k<=n):
114
raise ValueError, "Parameter k should be a strictly positive integer inferior to n"
115
116
g = Graph(name="Kneser graph with parameters "+str(n)+","+str(k))
117
from sage.combinat.subset import Subsets
118
119
if k>n/2:
120
g.add_vertices(Subsets(n,k).list())
121
122
S = Subsets(n,k)
123
for s in S:
124
for t in Subsets(S.s.difference(s),k):
125
g.add_edge(s,t)
126
127
return g
128
129
def BalancedTree(r, h):
130
r"""
131
Returns the perfectly balanced tree of height `h \geq 1`,
132
whose root has degree `r \geq 2`.
133
134
The number of vertices of this graph is
135
`1 + r + r^2 + \cdots + r^h`, that is,
136
`\frac{r^{h+1} - 1}{r - 1}`. The number of edges is one
137
less than the number of vertices.
138
139
INPUT:
140
141
- ``r`` -- positive integer `\geq 2`. The degree of the root node.
142
143
- ``h`` -- positive integer `\geq 1`. The height of the balanced tree.
144
145
OUTPUT:
146
147
The perfectly balanced tree of height `h \geq 1` and whose root has
148
degree `r \geq 2`. A ``NetworkXError`` is returned if `r < 2` or
149
`h < 1`.
150
151
ALGORITHM:
152
153
Uses `NetworkX <http://networkx.lanl.gov>`_.
154
155
EXAMPLES:
156
157
A balanced tree whose root node has degree `r = 2`, and of height
158
`h = 1`, has order 3 and size 2::
159
160
sage: G = graphs.BalancedTree(2, 1); G
161
Balanced tree: Graph on 3 vertices
162
sage: G.order(); G.size()
163
3
164
2
165
sage: r = 2; h = 1
166
sage: v = 1 + r
167
sage: v; v - 1
168
3
169
2
170
171
Plot a balanced tree of height 5, whose root node has degree `r = 3`::
172
173
sage: G = graphs.BalancedTree(3, 5)
174
sage: G.show() # long time
175
176
A tree is bipartite. If its vertex set is finite, then it is planar. ::
177
178
sage: r = randint(2, 5); h = randint(1, 7)
179
sage: T = graphs.BalancedTree(r, h)
180
sage: T.is_bipartite()
181
True
182
sage: T.is_planar()
183
True
184
sage: v = (r^(h + 1) - 1) / (r - 1)
185
sage: T.order() == v
186
True
187
sage: T.size() == v - 1
188
True
189
190
TESTS:
191
192
Normally we would only consider balanced trees whose root node
193
has degree `r \geq 2`, but the construction degenerates
194
gracefully::
195
196
sage: graphs.BalancedTree(1, 10)
197
Balanced tree: Graph on 2 vertices
198
199
sage: graphs.BalancedTree(-1, 10)
200
Balanced tree: Graph on 1 vertex
201
202
Similarly, we usually want the tree must have height `h \geq 1`
203
but the algorithm also degenerates gracefully here::
204
205
sage: graphs.BalancedTree(3, 0)
206
Balanced tree: Graph on 1 vertex
207
208
sage: graphs.BalancedTree(5, -2)
209
Balanced tree: Graph on 0 vertices
210
211
sage: graphs.BalancedTree(-2,-2)
212
Balanced tree: Graph on 0 vertices
213
"""
214
import networkx
215
return Graph(networkx.balanced_tree(r, h), name="Balanced tree")
216
217
def BarbellGraph(n1, n2):
218
r"""
219
Returns a barbell graph with ``2*n1 + n2`` nodes. The argument ``n1``
220
must be greater than or equal to 2.
221
222
A barbell graph is a basic structure that consists of a path graph
223
of order ``n2`` connecting two complete graphs of order ``n1`` each.
224
225
This constructor depends on `NetworkX <http://networkx.lanl.gov>`_
226
numeric labels. In this case, the ``n1``-th node connects to the
227
path graph from one complete graph and the ``n1 + n2 + 1``-th node
228
connects to the path graph from the other complete graph.
229
230
INPUT:
231
232
- ``n1`` -- integer `\geq 2`. The order of each of the two
233
complete graphs.
234
235
- ``n2`` -- nonnegative integer. The order of the path graph
236
connecting the two complete graphs.
237
238
OUTPUT:
239
240
A barbell graph of order ``2*n1 + n2``. A ``ValueError`` is
241
returned if ``n1 < 2`` or ``n2 < 0``.
242
243
ALGORITHM:
244
245
Uses `NetworkX <http://networkx.lanl.gov>`_.
246
247
PLOTTING:
248
249
Upon construction, the position dictionary is filled to
250
override the spring-layout algorithm. By convention, each barbell
251
graph will be displayed with the two complete graphs in the
252
lower-left and upper-right corners, with the path graph connecting
253
diagonally between the two. Thus the ``n1``-th node will be drawn at a
254
45 degree angle from the horizontal right center of the first
255
complete graph, and the ``n1 + n2 + 1``-th node will be drawn 45
256
degrees below the left horizontal center of the second complete graph.
257
258
EXAMPLES:
259
260
Construct and show a barbell graph ``Bar = 4``, ``Bells = 9``::
261
262
sage: g = graphs.BarbellGraph(9, 4); g
263
Barbell graph: Graph on 22 vertices
264
sage: g.show() # long time
265
266
An ``n1 >= 2``, ``n2 >= 0`` barbell graph has order ``2*n1 + n2``. It
267
has the complete graph on ``n1`` vertices as a subgraph. It also has
268
the path graph on ``n2`` vertices as a subgraph. ::
269
270
sage: n1 = randint(2, 2*10^2)
271
sage: n2 = randint(0, 2*10^2)
272
sage: g = graphs.BarbellGraph(n1, n2)
273
sage: v = 2*n1 + n2
274
sage: g.order() == v
275
True
276
sage: K_n1 = graphs.CompleteGraph(n1)
277
sage: P_n2 = graphs.PathGraph(n2)
278
sage: s_K = g.subgraph_search(K_n1, induced=True)
279
sage: s_P = g.subgraph_search(P_n2, induced=True)
280
sage: K_n1.is_isomorphic(s_K)
281
True
282
sage: P_n2.is_isomorphic(s_P)
283
True
284
285
Create several barbell graphs in a Sage graphics array::
286
287
sage: g = []
288
sage: j = []
289
sage: for i in range(6):
290
... k = graphs.BarbellGraph(i + 2, 4)
291
... g.append(k)
292
...
293
sage: for i in range(2):
294
... n = []
295
... for m in range(3):
296
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
297
... j.append(n)
298
...
299
sage: G = sage.plot.graphics.GraphicsArray(j)
300
sage: G.show() # long time
301
302
TESTS:
303
304
The input ``n1`` must be `\geq 2`::
305
306
sage: graphs.BarbellGraph(1, randint(0, 10^6))
307
Traceback (most recent call last):
308
...
309
ValueError: Invalid graph description, n1 should be >= 2
310
sage: graphs.BarbellGraph(randint(-10^6, 1), randint(0, 10^6))
311
Traceback (most recent call last):
312
...
313
ValueError: Invalid graph description, n1 should be >= 2
314
315
The input ``n2`` must be `\geq 0`::
316
317
sage: graphs.BarbellGraph(randint(2, 10^6), -1)
318
Traceback (most recent call last):
319
...
320
ValueError: Invalid graph description, n2 should be >= 0
321
sage: graphs.BarbellGraph(randint(2, 10^6), randint(-10^6, -1))
322
Traceback (most recent call last):
323
...
324
ValueError: Invalid graph description, n2 should be >= 0
325
sage: graphs.BarbellGraph(randint(-10^6, 1), randint(-10^6, -1))
326
Traceback (most recent call last):
327
...
328
ValueError: Invalid graph description, n1 should be >= 2
329
"""
330
# sanity checks
331
if n1 < 2:
332
raise ValueError("Invalid graph description, n1 should be >= 2")
333
if n2 < 0:
334
raise ValueError("Invalid graph description, n2 should be >= 0")
335
336
pos_dict = {}
337
338
for i in range(n1):
339
x = float(cos((pi / 4) - ((2 * pi) / n1) * i) - (n2 / 2) - 1)
340
y = float(sin((pi / 4) - ((2 * pi) / n1) * i) - (n2 / 2) - 1)
341
j = n1 - 1 - i
342
pos_dict[j] = (x, y)
343
for i in range(n1, n1 + n2):
344
x = float(i - n1 - (n2 / 2) + 1)
345
y = float(i - n1 - (n2 / 2) + 1)
346
pos_dict[i] = (x, y)
347
for i in range(n1 + n2, (2 * n1) + n2):
348
x = float(
349
cos((5 * (pi / 4)) + ((2 * pi) / n1) * (i - n1 - n2))
350
+ (n2 / 2) + 2)
351
y = float(
352
sin((5 * (pi / 4)) + ((2 * pi) / n1) * (i - n1 - n2))
353
+ (n2 / 2) + 2)
354
pos_dict[i] = (x, y)
355
356
import networkx
357
G = networkx.barbell_graph(n1, n2)
358
return Graph(G, pos=pos_dict, name="Barbell graph")
359
360
def BubbleSortGraph(n):
361
r"""
362
Returns the bubble sort graph `B(n)`.
363
364
The vertices of the bubble sort graph are the set of permutations on
365
`n` symbols. Two vertices are adjacent if one can be obtained from the
366
other by swapping the labels in the `i`-th and `(i+1)`-th positions for
367
`1 \leq i \leq n-1`. In total, `B(n)` has order `n!`. Thus, the order
368
of `B(n)` increases according to `f(n) = n!`.
369
370
INPUT:
371
372
- ``n`` -- positive integer. The number of symbols to permute.
373
374
OUTPUT:
375
376
The bubble sort graph `B(n)` on `n` symbols. If `n < 1`, a
377
``ValueError`` is returned.
378
379
EXAMPLES::
380
381
sage: g = graphs.BubbleSortGraph(4); g
382
Bubble sort: Graph on 24 vertices
383
sage: g.plot() # long time
384
385
The bubble sort graph on `n = 1` symbol is the trivial graph `K_1`::
386
387
sage: graphs.BubbleSortGraph(1)
388
Bubble sort: Graph on 1 vertex
389
390
If `n \geq 1`, then the order of `B(n)` is `n!`::
391
392
sage: n = randint(1, 8)
393
sage: g = graphs.BubbleSortGraph(n)
394
sage: g.order() == factorial(n)
395
True
396
397
TESTS:
398
399
Input ``n`` must be positive::
400
401
sage: graphs.BubbleSortGraph(0)
402
Traceback (most recent call last):
403
...
404
ValueError: Invalid number of symbols to permute, n should be >= 1
405
sage: graphs.BubbleSortGraph(randint(-10^6, 0))
406
Traceback (most recent call last):
407
...
408
ValueError: Invalid number of symbols to permute, n should be >= 1
409
410
AUTHORS:
411
412
- Michael Yurko (2009-09-01)
413
"""
414
# sanity checks
415
if n < 1:
416
raise ValueError(
417
"Invalid number of symbols to permute, n should be >= 1")
418
if n == 1:
419
from sage.graphs.generators.basic import CompleteGraph
420
return Graph(CompleteGraph(n), name="Bubble sort")
421
from sage.combinat.permutation import Permutations
422
#create set from which to permute
423
label_set = [str(i) for i in xrange(1, n + 1)]
424
d = {}
425
#iterate through all vertices
426
for v in Permutations(label_set):
427
v = list(v) # So we can easily mutate it
428
tmp_dict = {}
429
#add all adjacencies
430
for i in xrange(n - 1):
431
#swap entries
432
v[i], v[i + 1] = v[i + 1], v[i]
433
#add new vertex
434
new_vert = ''.join(v)
435
tmp_dict[new_vert] = None
436
#swap back
437
v[i], v[i + 1] = v[i + 1], v[i]
438
#add adjacency dict
439
d[''.join(v)] = tmp_dict
440
return Graph(d, name="Bubble sort")
441
442
def CirculantGraph(n, adjacency):
443
r"""
444
Returns a circulant graph with n nodes.
445
446
A circulant graph has the property that the vertex `i` is connected
447
with the vertices `i+j` and `i-j` for each j in adj.
448
449
INPUT:
450
451
452
- ``n`` - number of vertices in the graph
453
454
- ``adjacency`` - the list of j values
455
456
457
PLOTTING: Upon construction, the position dictionary is filled to
458
override the spring-layout algorithm. By convention, each circulant
459
graph will be displayed with the first (0) node at the top, with
460
the rest following in a counterclockwise manner.
461
462
Filling the position dictionary in advance adds O(n) to the
463
constructor.
464
465
.. SEEALSO::
466
467
* :meth:`sage.graphs.generic_graph.GenericGraph.is_circulant`
468
-- checks whether a (di)graph is circulant, and/or returns
469
all possible sets of parameters.
470
471
EXAMPLES: Compare plotting using the predefined layout and
472
networkx::
473
474
sage: import networkx
475
sage: n = networkx.cycle_graph(23)
476
sage: spring23 = Graph(n)
477
sage: posdict23 = graphs.CirculantGraph(23,2)
478
sage: spring23.show() # long time
479
sage: posdict23.show() # long time
480
481
We next view many cycle graphs as a Sage graphics array. First we
482
use the ``CirculantGraph`` constructor, which fills in
483
the position dictionary::
484
485
sage: g = []
486
sage: j = []
487
sage: for i in range(9):
488
... k = graphs.CirculantGraph(i+3,i)
489
... g.append(k)
490
...
491
sage: for i in range(3):
492
... n = []
493
... for m in range(3):
494
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
495
... j.append(n)
496
...
497
sage: G = sage.plot.graphics.GraphicsArray(j)
498
sage: G.show() # long time
499
500
Compare to plotting with the spring-layout algorithm::
501
502
sage: g = []
503
sage: j = []
504
sage: for i in range(9):
505
... spr = networkx.cycle_graph(i+3)
506
... k = Graph(spr)
507
... g.append(k)
508
...
509
sage: for i in range(3):
510
... n = []
511
... for m in range(3):
512
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
513
... j.append(n)
514
...
515
sage: G = sage.plot.graphics.GraphicsArray(j)
516
sage: G.show() # long time
517
518
Passing a 1 into adjacency should give the cycle.
519
520
::
521
522
sage: graphs.CirculantGraph(6,1)==graphs.CycleGraph(6)
523
True
524
sage: graphs.CirculantGraph(7,[1,3]).edges(labels=false)
525
[(0, 1),
526
(0, 3),
527
(0, 4),
528
(0, 6),
529
(1, 2),
530
(1, 4),
531
(1, 5),
532
(2, 3),
533
(2, 5),
534
(2, 6),
535
(3, 4),
536
(3, 6),
537
(4, 5),
538
(5, 6)]
539
"""
540
from sage.graphs.graph_plot import _circle_embedding
541
542
if not isinstance(adjacency,list):
543
adjacency=[adjacency]
544
545
G = Graph(n, name="Circulant graph ("+str(adjacency)+")")
546
_circle_embedding(G, range(n))
547
548
for v in G:
549
G.add_edges([(v,(v+j)%n) for j in adjacency])
550
551
return G
552
553
def CubeGraph(n):
554
r"""
555
Returns the hypercube in `n` dimensions.
556
557
The hypercube in `n` dimension is build upon the binary
558
strings on `n` bits, two of them being adjacent if
559
they differ in exactly one bit. Hence, the distance
560
between two vertices in the hypercube is the Hamming
561
distance.
562
563
EXAMPLES:
564
565
The distance between `0100110` and `1011010` is
566
`5`, as expected ::
567
568
sage: g = graphs.CubeGraph(7)
569
sage: g.distance('0100110','1011010')
570
5
571
572
Plot several `n`-cubes in a Sage Graphics Array ::
573
574
sage: g = []
575
sage: j = []
576
sage: for i in range(6):
577
... k = graphs.CubeGraph(i+1)
578
... g.append(k)
579
...
580
sage: for i in range(2):
581
... n = []
582
... for m in range(3):
583
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
584
... j.append(n)
585
...
586
sage: G = sage.plot.graphics.GraphicsArray(j)
587
sage: G.show(figsize=[6,4]) # long time
588
589
Use the plot options to display larger `n`-cubes
590
591
::
592
593
sage: g = graphs.CubeGraph(9)
594
sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time
595
596
AUTHORS:
597
598
- Robert Miller
599
"""
600
theta = float(pi/n)
601
602
d = {'':[]}
603
dn={}
604
p = {'':(float(0),float(0))}
605
pn={}
606
607
# construct recursively the adjacency dict and the positions
608
for i in xrange(n):
609
ci = float(cos(i*theta))
610
si = float(sin(i*theta))
611
for v,e in d.iteritems():
612
v0 = v+'0'
613
v1 = v+'1'
614
l0 = [v1]
615
l1 = [v0]
616
for m in e:
617
l0.append(m+'0')
618
l1.append(m+'1')
619
dn[v0] = l0
620
dn[v1] = l1
621
x,y = p[v]
622
pn[v0] = (x, y)
623
pn[v1] = (x+ci, y+si)
624
d,dn = dn,{}
625
p,pn = pn,{}
626
627
# construct the graph
628
r = Graph(name="%d-Cube"%n)
629
r.add_vertices(d.keys())
630
for u,L in d.iteritems():
631
for v in L:
632
r.add_edge(u,v)
633
r.set_pos(p)
634
635
return r
636
637
def DorogovtsevGoltsevMendesGraph(n):
638
"""
639
Construct the n-th generation of the Dorogovtsev-Goltsev-Mendes
640
graph.
641
642
EXAMPLE::
643
644
sage: G = graphs.DorogovtsevGoltsevMendesGraph(8)
645
sage: G.size()
646
6561
647
648
REFERENCE:
649
650
- [1] Dorogovtsev, S. N., Goltsev, A. V., and Mendes, J.
651
F. F., Pseudofractal scale-free web, Phys. Rev. E 066122
652
(2002).
653
"""
654
import networkx
655
return Graph(networkx.dorogovtsev_goltsev_mendes_graph(n),\
656
name="Dorogovtsev-Goltsev-Mendes Graph, %d-th generation"%n)
657
658
def FoldedCubeGraph(n):
659
r"""
660
Returns the folded cube graph of order `2^{n-1}`.
661
662
The folded cube graph on `2^{n-1}` vertices can be obtained from a cube
663
graph on `2^n` vertices by merging together opposed
664
vertices. Alternatively, it can be obtained from a cube graph on
665
`2^{n-1}` vertices by adding an edge between opposed vertices. This
666
second construction is the one produced by this method.
667
668
For more information on folded cube graphs, see the corresponding
669
:wikipedia:`Wikipedia page <Folded_cube_graph>`.
670
671
EXAMPLES:
672
673
The folded cube graph of order five is the Clebsch graph::
674
675
sage: fc = graphs.FoldedCubeGraph(5)
676
sage: clebsch = graphs.ClebschGraph()
677
sage: fc.is_isomorphic(clebsch)
678
True
679
"""
680
681
if n < 1:
682
raise ValueError("The value of n must be at least 2")
683
684
g = CubeGraph(n-1)
685
g.name("Folded Cube Graph")
686
687
# Complementing the binary word
688
def complement(x):
689
x = x.replace('0','a')
690
x = x.replace('1','0')
691
x = x.replace('a','1')
692
return x
693
694
for x in g:
695
if x[0] == '0':
696
g.add_edge(x,complement(x))
697
698
return g
699
700
701
def FriendshipGraph(n):
702
r"""
703
Returns the friendship graph `F_n`.
704
705
The friendship graph is also known as the Dutch windmill graph. Let
706
`C_3` be the cycle graph on 3 vertices. Then `F_n` is constructed by
707
joining `n \geq 1` copies of `C_3` at a common vertex. If `n = 1`,
708
then `F_1` is isomorphic to `C_3` (the triangle graph). If `n = 2`,
709
then `F_2` is the butterfly graph, otherwise known as the bowtie
710
graph. For more information, see this
711
`Wikipedia article on the friendship graph <http://en.wikipedia.org/wiki/Friendship_graph>`_.
712
713
INPUT:
714
715
- ``n`` -- positive integer; the number of copies of `C_3` to use in
716
constructing `F_n`.
717
718
OUTPUT:
719
720
- The friendship graph `F_n` obtained from `n` copies of the cycle
721
graph `C_3`.
722
723
.. seealso::
724
725
- :meth:`GraphGenerators.ButterflyGraph`
726
727
EXAMPLES:
728
729
The first few friendship graphs. ::
730
731
sage: A = []; B = []
732
sage: for i in range(9):
733
... g = graphs.FriendshipGraph(i + 1)
734
... A.append(g)
735
sage: for i in range(3):
736
... n = []
737
... for j in range(3):
738
... n.append(A[3*i + j].plot(vertex_size=20, vertex_labels=False))
739
... B.append(n)
740
sage: G = sage.plot.graphics.GraphicsArray(B)
741
sage: G.show() # long time
742
743
For `n = 1`, the friendship graph `F_1` is isomorphic to the cycle
744
graph `C_3`, whose visual representation is a triangle. ::
745
746
sage: G = graphs.FriendshipGraph(1); G
747
Friendship graph: Graph on 3 vertices
748
sage: G.show() # long time
749
sage: G.is_isomorphic(graphs.CycleGraph(3))
750
True
751
752
For `n = 2`, the friendship graph `F_2` is isomorphic to the
753
butterfly graph, otherwise known as the bowtie graph. ::
754
755
sage: G = graphs.FriendshipGraph(2); G
756
Friendship graph: Graph on 5 vertices
757
sage: G.is_isomorphic(graphs.ButterflyGraph())
758
True
759
760
If `n \geq 1`, then the friendship graph `F_n` has `2n + 1` vertices
761
and `3n` edges. It has radius 1, diameter 2, girth 3, and
762
chromatic number 3. Furthermore, `F_n` is planar and Eulerian. ::
763
764
sage: n = randint(1, 10^3)
765
sage: G = graphs.FriendshipGraph(n)
766
sage: G.order() == 2*n + 1
767
True
768
sage: G.size() == 3*n
769
True
770
sage: G.radius()
771
1
772
sage: G.diameter()
773
2
774
sage: G.girth()
775
3
776
sage: G.chromatic_number()
777
3
778
sage: G.is_planar()
779
True
780
sage: G.is_eulerian()
781
True
782
783
TESTS:
784
785
The input ``n`` must be a positive integer. ::
786
787
sage: graphs.FriendshipGraph(randint(-10^5, 0))
788
Traceback (most recent call last):
789
...
790
ValueError: n must be a positive integer
791
"""
792
# sanity checks
793
if n < 1:
794
raise ValueError("n must be a positive integer")
795
# construct the friendship graph
796
if n == 1:
797
from sage.graphs.generators.basic import CycleGraph
798
G = CycleGraph(3)
799
G.name("Friendship graph")
800
return G
801
# build the edge and position dictionaries
802
from sage.functions.trig import cos, sin
803
from sage.rings.real_mpfr import RR
804
from sage.symbolic.constants import pi
805
N = 2*n + 1 # order of F_n
806
d = (2*pi) / (N - 1) # angle between external nodes
807
edge_dict = {}
808
pos_dict = {}
809
for i in range(N - 2):
810
if i & 1: # odd numbered node
811
edge_dict.setdefault(i, [i + 1, N - 1])
812
else: # even numbered node
813
edge_dict.setdefault(i, [N - 1])
814
pos_dict.setdefault(i, [RR(cos(i*d)), RR(sin(i*d))])
815
edge_dict.setdefault(N - 2, [0, N - 1])
816
pos_dict.setdefault(N - 2, [RR(cos(d * (N-2))), RR(sin(d * (N-2)))])
817
pos_dict.setdefault(N - 1, [0, 0])
818
return Graph(edge_dict, pos=pos_dict, name="Friendship graph")
819
820
def FuzzyBallGraph(partition, q):
821
r"""
822
Construct a Fuzzy Ball graph with the integer partition
823
``partition`` and ``q`` extra vertices.
824
825
Let `q` be an integer and let `m_1,m_2,...,m_k` be a set of positive
826
integers. Let `n=q+m_1+...+m_k`. The Fuzzy Ball graph with partition
827
`m_1,m_2,...,m_k` and `q` extra vertices is the graph constructed from the
828
graph `G=K_n` by attaching, for each `i=1,2,...,k`, a new vertex `a_i` to
829
`m_i` distinct vertices of `G`.
830
831
For given positive integers `k` and `m` and nonnegative
832
integer `q`, the set of graphs ``FuzzyBallGraph(p, q)`` for
833
all partitions `p` of `m` with `k` parts are cospectral with
834
respect to the normalized Laplacian.
835
836
EXAMPLES::
837
838
sage: graphs.FuzzyBallGraph([3,1],2).adjacency_matrix()
839
[0 1 1 1 1 1 1 0]
840
[1 0 1 1 1 1 1 0]
841
[1 1 0 1 1 1 1 0]
842
[1 1 1 0 1 1 0 1]
843
[1 1 1 1 0 1 0 0]
844
[1 1 1 1 1 0 0 0]
845
[1 1 1 0 0 0 0 0]
846
[0 0 0 1 0 0 0 0]
847
848
849
Pick positive integers `m` and `k` and a nonnegative integer `q`.
850
All the FuzzyBallGraphs constructed from partitions of `m` with
851
`k` parts should be cospectral with respect to the normalized
852
Laplacian::
853
854
sage: m=4; q=2; k=2
855
sage: g_list=[graphs.FuzzyBallGraph(p,q) for p in Partitions(m, length=k)]
856
sage: set([g.laplacian_matrix(normalized=True).charpoly() for g in g_list]) # long time (7s on sage.math, 2011)
857
set([x^8 - 8*x^7 + 4079/150*x^6 - 68689/1350*x^5 + 610783/10800*x^4 - 120877/3240*x^3 + 1351/100*x^2 - 931/450*x])
858
"""
859
from sage.graphs.generators.basic import CompleteGraph
860
if len(partition)<1:
861
raise ValueError, "partition must be a nonempty list of positive integers"
862
n=q+sum(partition)
863
g=CompleteGraph(n)
864
curr_vertex=0
865
for e,p in enumerate(partition):
866
g.add_edges([(curr_vertex+i, 'a{0}'.format(e+1)) for i in range(p)])
867
curr_vertex+=p
868
return g
869
870
def FibonacciTree(n):
871
r"""
872
Returns the graph of the Fibonacci Tree `F_{i}` of order `n`.
873
`F_{i}` is recursively defined as the a tree with a root vertex
874
and two attached child trees `F_{i-1}` and `F_{i-2}`, where
875
`F_{1}` is just one vertex and `F_{0}` is empty.
876
877
INPUT:
878
879
- ``n`` - the recursion depth of the Fibonacci Tree
880
881
EXAMPLES::
882
883
sage: g = graphs.FibonacciTree(3)
884
sage: g.is_tree()
885
True
886
887
::
888
889
sage: l1 = [ len(graphs.FibonacciTree(_)) + 1 for _ in range(6) ]
890
sage: l2 = list(fibonacci_sequence(2,8))
891
sage: l1 == l2
892
True
893
894
AUTHORS:
895
896
- Harald Schilly and Yann Laigle-Chapuy (2010-03-25)
897
"""
898
T = Graph(name="Fibonacci-Tree-%d"%n)
899
if n == 1: T.add_vertex(0)
900
if n < 2: return T
901
902
from sage.combinat.combinat import fibonacci_sequence
903
F = list(fibonacci_sequence(n + 2))
904
s = 1.618 ** (n / 1.618 - 1.618)
905
pos = {}
906
907
def fib(level, node, y):
908
pos[node] = (node, y)
909
if level < 2: return
910
level -= 1
911
y -= s
912
diff = F[level]
913
T.add_edge(node, node - diff)
914
if level == 1: # only one child
915
pos[node - diff] = (node, y)
916
return
917
T.add_edge(node, node + diff)
918
fib(level, node - diff, y)
919
fib(level - 1, node + diff, y)
920
921
T.add_vertices(xrange(sum(F[:-1])))
922
fib(n, F[n + 1] - 1, 0)
923
T.set_pos(pos)
924
925
return T
926
927
def GeneralizedPetersenGraph(n,k):
928
r"""
929
Returns a generalized Petersen graph with `2n` nodes. The variables
930
`n`, `k` are integers such that `n>2` and `0<k\leq\lfloor(n-1)`/`2\rfloor`
931
932
For `k=1` the result is a graph isomorphic to the circular ladder graph
933
with the same `n`. The regular Petersen Graph has `n=5` and `k=2`.
934
Other named graphs that can be described using this notation include
935
the Desargues graph and the Moebius-Kantor graph.
936
937
INPUT:
938
939
- ``n`` - the number of nodes is `2*n`.
940
941
- ``k`` - integer `0<k\leq\lfloor(n-1)`/`2\rfloor`. Decides
942
how inner vertices are connected.
943
944
PLOTTING: Upon construction, the position dictionary is filled to
945
override the spring-layout algorithm. By convention, the generalized
946
Petersen graphs are displayed as an inner and outer cycle pair, with
947
the first n nodes drawn on the outer circle. The first (0) node is
948
drawn at the top of the outer-circle, moving counterclockwise after that.
949
The inner circle is drawn with the (n)th node at the top, then
950
counterclockwise as well.
951
952
EXAMPLES: For `k=1` the resulting graph will be isomorphic to a circular
953
ladder graph. ::
954
955
sage: g = graphs.GeneralizedPetersenGraph(13,1)
956
sage: g2 = graphs.CircularLadderGraph(13)
957
sage: g.is_isomorphic(g2)
958
True
959
960
The Desargues graph::
961
962
sage: g = graphs.GeneralizedPetersenGraph(10,3)
963
sage: g.girth()
964
6
965
sage: g.is_bipartite()
966
True
967
968
AUTHORS:
969
970
- Anders Jonsson (2009-10-15)
971
"""
972
if (n < 3):
973
raise ValueError("n must be larger than 2")
974
if (k < 1 or k>((n-1)/2)):
975
raise ValueError("k must be in 1<= k <=floor((n-1)/2)")
976
pos_dict = {}
977
G = Graph()
978
for i in range(n):
979
x = float(cos((pi/2) + ((2*pi)/n)*i))
980
y = float(sin((pi/2) + ((2*pi)/n)*i))
981
pos_dict[i] = (x,y)
982
for i in range(n, 2*n):
983
x = float(0.5*cos((pi/2) + ((2*pi)/n)*i))
984
y = float(0.5*sin((pi/2) + ((2*pi)/n)*i))
985
pos_dict[i] = (x,y)
986
for i in range(n):
987
G.add_edge(i, (i+1) % n)
988
G.add_edge(i, i+n)
989
G.add_edge(i+n, n + (i+k) % n)
990
return Graph(G, pos=pos_dict, name="Generalized Petersen graph (n="+str(n)+",k="+str(k)+")")
991
992
def HararyGraph( k, n ):
993
r"""
994
Returns the Harary graph on `n` vertices and connectivity `k`, where
995
`2 \leq k < n`.
996
997
A `k`-connected graph `G` on `n` vertices requires the minimum degree
998
`\delta(G)\geq k`, so the minimum number of edges `G` should have is
999
`\lceil kn/2\rceil`. Harary graphs achieve this lower bound, that is,
1000
Harary graphs are minimal `k`-connected graphs on `n` vertices.
1001
1002
The construction provided uses the method CirculantGraph. For more
1003
details, see the book D. B. West, Introduction to Graph Theory, 2nd
1004
Edition, Prentice Hall, 2001, p. 150--151; or the `MathWorld article on
1005
Harary graphs <http://mathworld.wolfram.com/HararyGraph.html>`_.
1006
1007
EXAMPLES:
1008
1009
Harary graphs `H_{k,n}`::
1010
1011
sage: h = graphs.HararyGraph(5,9); h
1012
Harary graph 5, 9: Graph on 9 vertices
1013
sage: h.order()
1014
9
1015
sage: h.size()
1016
23
1017
sage: h.vertex_connectivity()
1018
5
1019
1020
TESTS:
1021
1022
Connectivity of some Harary graphs::
1023
1024
sage: n=10
1025
sage: for k in range(2,n):
1026
... g = graphs.HararyGraph(k,n)
1027
... if k != g.vertex_connectivity():
1028
... print "Connectivity of Harary graphs not satisfied."
1029
"""
1030
if k < 2:
1031
raise ValueError("Connectivity parameter k should be at least 2.")
1032
if k >= n:
1033
raise ValueError("Number of vertices n should be greater than k.")
1034
1035
if k%2 == 0:
1036
G = CirculantGraph( n, range(1,k/2+1) )
1037
else:
1038
if n%2 == 0:
1039
G = CirculantGraph( n, range(1,(k-1)/2+1) )
1040
for i in range(n):
1041
G.add_edge( i, (i+n/2)%n )
1042
else:
1043
G = HararyGraph( k-1, n )
1044
for i in range((n-1)/2+1):
1045
G.add_edge( i, (i+(n-1)/2)%n )
1046
G.name('Harary graph {0}, {1}'.format(k,n))
1047
return G
1048
1049
def HyperStarGraph(n,k):
1050
r"""
1051
Returns the hyper-star graph HS(n,k).
1052
1053
The vertices of the hyper-star graph are the set of binary strings
1054
of length n which contain k 1s. Two vertices, u and v, are adjacent
1055
only if u can be obtained from v by swapping the first bit with a
1056
different symbol in another position.
1057
1058
INPUT:
1059
1060
- ``n``
1061
1062
- ``k``
1063
1064
EXAMPLES::
1065
1066
sage: g = graphs.HyperStarGraph(6,3)
1067
sage: g.plot() # long time
1068
1069
REFERENCES:
1070
1071
- Lee, Hyeong-Ok, Jong-Seok Kim, Eunseuk Oh, and Hyeong-Seok Lim.
1072
"Hyper-Star Graph: A New Interconnection Network Improving the
1073
Network Cost of the Hypercube." In Proceedings of the First EurAsian
1074
Conference on Information and Communication Technology, 858-865.
1075
Springer-Verlag, 2002.
1076
1077
AUTHORS:
1078
1079
- Michael Yurko (2009-09-01)
1080
"""
1081
from sage.combinat.combination import Combinations
1082
# dictionary associating the positions of the 1s to the corresponding
1083
# string: e.g. if n=6 and k=3, comb_to_str([0,1,4])=='110010'
1084
comb_to_str={}
1085
for c in Combinations(n,k):
1086
L = ['0']*n
1087
for i in c:
1088
L[i]='1'
1089
comb_to_str[tuple(c)] = ''.join(L)
1090
1091
g = Graph(name="HS(%d,%d)"%(n,k))
1092
g.add_vertices(comb_to_str.values())
1093
1094
for c in Combinations(range(1,n),k): # 0 is not in c
1095
L = []
1096
u = comb_to_str[tuple(c)]
1097
# switch 0 with the 1s
1098
for i in xrange(len(c)):
1099
v = tuple([0]+c[:i]+c[i+1:])
1100
g.add_edge( u , comb_to_str[v] )
1101
1102
return g
1103
1104
def LCFGraph(n, shift_list, repeats):
1105
"""
1106
Returns the cubic graph specified in LCF notation.
1107
1108
LCF (Lederberg-Coxeter-Fruchte) notation is a concise way of
1109
describing cubic Hamiltonian graphs. The way a graph is constructed
1110
is as follows. Since there is a Hamiltonian cycle, we first create
1111
a cycle on n nodes. The variable shift_list = [s_0, s_1, ...,
1112
s_k-1] describes edges to be created by the following scheme: for
1113
each i, connect vertex i to vertex (i + s_i). Then, repeats
1114
specifies the number of times to repeat this process, where on the
1115
jth repeat we connect vertex (i + j\*len(shift_list)) to vertex (
1116
i + j\*len(shift_list) + s_i).
1117
1118
INPUT:
1119
1120
1121
- ``n`` - the number of nodes.
1122
1123
- ``shift_list`` - a list of integer shifts mod n.
1124
1125
- ``repeats`` - the number of times to repeat the
1126
process.
1127
1128
1129
EXAMPLES::
1130
1131
sage: G = graphs.LCFGraph(4, [2,-2], 2)
1132
sage: G.is_isomorphic(graphs.TetrahedralGraph())
1133
True
1134
1135
::
1136
1137
sage: G = graphs.LCFGraph(20, [10,7,4,-4,-7,10,-4,7,-7,4], 2)
1138
sage: G.is_isomorphic(graphs.DodecahedralGraph())
1139
True
1140
1141
::
1142
1143
sage: G = graphs.LCFGraph(14, [5,-5], 7)
1144
sage: G.is_isomorphic(graphs.HeawoodGraph())
1145
True
1146
1147
The largest cubic nonplanar graph of diameter three::
1148
1149
sage: G = graphs.LCFGraph(20, [-10,-7,-5,4,7,-10,-7,-4,5,7,-10,-7,6,-5,7,-10,-7,5,-6,7], 1)
1150
sage: G.degree()
1151
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
1152
sage: G.diameter()
1153
3
1154
sage: G.show() # long time
1155
1156
PLOTTING: LCF Graphs are plotted as an n-cycle with edges in the
1157
middle, as described above.
1158
1159
REFERENCES:
1160
1161
- [1] Frucht, R. "A Canonical Representation of Trivalent
1162
Hamiltonian Graphs." J. Graph Th. 1, 45-60, 1976.
1163
1164
- [2] Grunbaum, B. Convex Polytope es. New York: Wiley,
1165
pp. 362-364, 1967.
1166
1167
- [3] Lederberg, J. 'DENDRAL-64: A System for Computer
1168
Construction, Enumeration and Notation of Organic Molecules
1169
as Tree Structures and Cyclic Graphs. Part II. Topology of
1170
Cyclic Graphs.' Interim Report to the National Aeronautics
1171
and Space Administration. Grant NsG 81-60. December 15,
1172
1965. http://profiles.nlm.nih.gov/BB/A/B/I/U/_/bbabiu.pdf.
1173
"""
1174
import networkx
1175
pos_dict = {}
1176
for i in range(n):
1177
x = float(cos(pi/2 + ((2*pi)/n)*i))
1178
y = float(sin(pi/2 + ((2*pi)/n)*i))
1179
pos_dict[i] = [x,y]
1180
return Graph(networkx.LCF_graph(n, shift_list, repeats),\
1181
pos=pos_dict, name="LCF Graph")
1182
1183
def MycielskiGraph(k=1, relabel=True):
1184
r"""
1185
Returns the `k`-th Mycielski Graph.
1186
1187
The graph `M_k` is triangle-free and has chromatic number
1188
equal to `k`. These graphs show, constructively, that there
1189
are triangle-free graphs with arbitrarily high chromatic
1190
number.
1191
1192
The Mycielski graphs are built recursively starting with
1193
`M_0`, an empty graph; `M_1`, a single vertex graph; and `M_2`
1194
is the graph `K_2`. `M_{k+1}` is then built from `M_k`
1195
as follows:
1196
1197
If the vertices of `M_k` are `v_1,\ldots,v_n`, then the
1198
vertices of `M_{k+1}` are
1199
`v_1,\ldots,v_n,w_1,\ldots,w_n,z`. Vertices `v_1,\ldots,v_n`
1200
induce a copy of `M_k`. Vertices `w_1,\ldots,w_n` are an
1201
independent set. Vertex `z` is adjacent to all the
1202
`w_i`-vertices. Finally, vertex `w_i` is adjacent to vertex
1203
`v_j` iff `v_i` is adjacent to `v_j`.
1204
1205
INPUT:
1206
1207
- ``k`` Number of steps in the construction process.
1208
1209
- ``relabel`` Relabel the vertices so their names are the integers
1210
``range(n)`` where ``n`` is the number of vertices in the graph.
1211
1212
EXAMPLE:
1213
1214
The Mycielski graph `M_k` is triangle-free and has chromatic
1215
number equal to `k`. ::
1216
1217
sage: g = graphs.MycielskiGraph(5)
1218
sage: g.is_triangle_free()
1219
True
1220
sage: g.chromatic_number()
1221
5
1222
1223
The graphs `M_4` is (isomorphic to) the Grotzsch graph. ::
1224
1225
sage: g = graphs.MycielskiGraph(4)
1226
sage: g.is_isomorphic(graphs.GrotzschGraph())
1227
True
1228
1229
REFERENCES:
1230
1231
- [1] Weisstein, Eric W. "Mycielski Graph."
1232
From MathWorld--A Wolfram Web Resource.
1233
http://mathworld.wolfram.com/MycielskiGraph.html
1234
1235
"""
1236
g = Graph()
1237
g.name("Mycielski Graph " + str(k))
1238
1239
if k<0:
1240
raise ValueError, "parameter k must be a nonnegative integer"
1241
1242
if k == 0:
1243
return g
1244
1245
if k == 1:
1246
g.add_vertex(0)
1247
return g
1248
1249
if k == 2:
1250
g.add_edge(0,1)
1251
return g
1252
1253
g0 = MycielskiGraph(k-1)
1254
g = MycielskiStep(g0)
1255
g.name("Mycielski Graph " + str(k))
1256
if relabel: g.relabel()
1257
1258
return g
1259
1260
def MycielskiStep(g):
1261
r"""
1262
Perform one iteration of the Mycielski construction.
1263
1264
See the documentation for ``MycielskiGraph`` which uses this
1265
method. We expose it to all users in case they may find it
1266
useful.
1267
1268
EXAMPLE. One iteration of the Mycielski step applied to the
1269
5-cycle yields a graph isomorphic to the Grotzsch graph ::
1270
1271
sage: g = graphs.CycleGraph(5)
1272
sage: h = graphs.MycielskiStep(g)
1273
sage: h.is_isomorphic(graphs.GrotzschGraph())
1274
True
1275
"""
1276
1277
# Make a copy of the input graph g
1278
gg = g.copy()
1279
1280
# rename a vertex v of gg as (1,v)
1281
renamer = dict( [ (v, (1,v)) for v in g.vertices() ] )
1282
gg.relabel(renamer)
1283
1284
# add the w vertices to gg as (2,v)
1285
wlist = [ (2,v) for v in g.vertices() ]
1286
gg.add_vertices(wlist)
1287
1288
# add the z vertex as (0,0)
1289
gg.add_vertex((0,0))
1290
1291
# add the edges from z to w_i
1292
gg.add_edges( [ ( (0,0) , (2,v) ) for v in g.vertices() ] )
1293
1294
# make the v_i w_j edges
1295
for v in g.vertices():
1296
gg.add_edges( [ ((1,v),(2,vv)) for vv in g.neighbors(v) ] )
1297
1298
return gg
1299
1300
def NKStarGraph(n,k):
1301
r"""
1302
Returns the (n,k)-star graph.
1303
1304
The vertices of the (n,k)-star graph are the set of all arrangements of
1305
n symbols into labels of length k. There are two adjacency rules for
1306
the (n,k)-star graph. First, two vertices are adjacent if one can be
1307
obtained from the other by swapping the first symbol with another
1308
symbol. Second, two vertices are adjacent if one can be obtained from
1309
the other by swapping the first symbol with an external symbol (a
1310
symbol not used in the original label).
1311
1312
INPUT:
1313
1314
- ``n``
1315
1316
- ``k``
1317
1318
EXAMPLES::
1319
1320
sage: g = graphs.NKStarGraph(4,2)
1321
sage: g.plot() # long time
1322
1323
REFERENCES:
1324
1325
- Wei-Kuo, Chiang, and Chen Rong-Jaye. "The (n, k)-star graph: A
1326
generalized star graph." Information Processing Letters 56,
1327
no. 5 (December 8, 1995): 259-264.
1328
1329
AUTHORS:
1330
1331
- Michael Yurko (2009-09-01)
1332
"""
1333
from sage.combinat.permutation import Arrangements
1334
#set from which to permute
1335
set = [str(i) for i in xrange(1,n+1)]
1336
#create dict
1337
d = {}
1338
for v in Arrangements(set,k):
1339
v = list(v) # So we can easily mutate it
1340
tmp_dict = {}
1341
#add edges of dimension i
1342
for i in xrange(1,k):
1343
#swap 0th and ith element
1344
v[0], v[i] = v[i], v[0]
1345
#convert to str and add to list
1346
vert = "".join(v)
1347
tmp_dict[vert] = None
1348
#swap back
1349
v[0], v[i] = v[i], v[0]
1350
#add other edges
1351
tmp_bit = v[0]
1352
for i in set:
1353
#check if external
1354
if not (i in v):
1355
v[0] = i
1356
#add edge
1357
vert = "".join(v)
1358
tmp_dict[vert] = None
1359
v[0] = tmp_bit
1360
d["".join(v)] = tmp_dict
1361
return Graph(d, name="(%d,%d)-star"%(n,k))
1362
1363
def NStarGraph(n):
1364
r"""
1365
Returns the n-star graph.
1366
1367
The vertices of the n-star graph are the set of permutations on n
1368
symbols. There is an edge between two vertices if their labels differ
1369
only in the first and one other position.
1370
1371
INPUT:
1372
1373
- ``n``
1374
1375
EXAMPLES::
1376
1377
sage: g = graphs.NStarGraph(4)
1378
sage: g.plot() # long time
1379
1380
REFERENCES:
1381
1382
- S.B. Akers, D. Horel and B. Krishnamurthy, The star graph: An
1383
attractive alternative to the previous n-cube. In: Proc. Internat.
1384
Conf. on Parallel Processing (1987), pp. 393--400.
1385
1386
AUTHORS:
1387
1388
- Michael Yurko (2009-09-01)
1389
"""
1390
from sage.combinat.permutation import Permutations
1391
#set from which to permute
1392
set = [str(i) for i in xrange(1,n+1)]
1393
#create dictionary of lists
1394
#vertices are adjacent if the first element
1395
#is swapped with the ith element
1396
d = {}
1397
for v in Permutations(set):
1398
v = list(v) # So we can easily mutate it
1399
tmp_dict = {}
1400
for i in xrange(1,n):
1401
if v[0] != v[i]:
1402
#swap 0th and ith element
1403
v[0], v[i] = v[i], v[0]
1404
#convert to str and add to list
1405
vert = "".join(v)
1406
tmp_dict[vert] = None
1407
#swap back
1408
v[0], v[i] = v[i], v[0]
1409
d["".join(v)] = tmp_dict
1410
return Graph(d, name = "%d-star"%n)
1411
1412
def OddGraph(n):
1413
r"""
1414
Returns the Odd Graph with parameter `n`.
1415
1416
The Odd Graph with parameter `n` is defined as the
1417
Kneser Graph with parameters `2n-1,n-1`.
1418
Equivalently, the Odd Graph is the graph whose vertices
1419
are the `n-1`-subsets of `[0,1,\dots,2(n-1)]`, and such
1420
that two vertices are adjacent if their corresponding sets
1421
are disjoint.
1422
1423
For example, the Petersen Graph can be defined
1424
as the Odd Graph with parameter `3`.
1425
1426
EXAMPLE::
1427
1428
sage: OG=graphs.OddGraph(3)
1429
sage: print OG.vertices()
1430
[{4, 5}, {1, 3}, {2, 5}, {2, 3}, {3, 4}, {3, 5}, {1, 4}, {1, 5}, {1, 2}, {2, 4}]
1431
sage: P=graphs.PetersenGraph()
1432
sage: P.is_isomorphic(OG)
1433
True
1434
1435
TESTS::
1436
1437
sage: KG=graphs.OddGraph(1)
1438
Traceback (most recent call last):
1439
...
1440
ValueError: Parameter n should be an integer strictly greater than 1
1441
"""
1442
1443
if not n>1:
1444
raise ValueError, "Parameter n should be an integer strictly greater than 1"
1445
g = KneserGraph(2*n-1,n-1)
1446
g.name("Odd Graph with parameter %s" % n)
1447
return g
1448
1449
def PaleyGraph(q):
1450
r"""
1451
Paley graph with `q` vertices
1452
1453
Parameter `q` must be the power of a prime number and congruent
1454
to 1 mod 4.
1455
1456
EXAMPLES::
1457
1458
sage: G=graphs.PaleyGraph(9);G
1459
Paley graph with parameter 9: Graph on 9 vertices
1460
sage: G.is_regular()
1461
True
1462
1463
A Paley graph is always self-complementary::
1464
1465
sage: G.complement().is_isomorphic(G)
1466
True
1467
"""
1468
from sage.rings.finite_rings.integer_mod import mod
1469
from sage.rings.finite_rings.constructor import FiniteField
1470
assert q.is_prime_power(), "Parameter q must be a prime power"
1471
assert mod(q,4)==1, "Parameter q must be congruent to 1 mod 4"
1472
g = Graph([FiniteField(q,'a'), lambda i,j: (i-j).is_square()],
1473
loops=False, name = "Paley graph with parameter %d"%q)
1474
return g
1475
1476
def HanoiTowerGraph(pegs, disks, labels=True, positions=True):
1477
r"""
1478
Returns the graph whose vertices are the states of the
1479
Tower of Hanoi puzzle, with edges representing legal moves between states.
1480
1481
INPUT:
1482
1483
- ``pegs`` - the number of pegs in the puzzle, 2 or greater
1484
- ``disks`` - the number of disks in the puzzle, 1 or greater
1485
- ``labels`` - default: ``True``, if ``True`` the graph contains
1486
more meaningful labels, see explanation below. For large instances,
1487
turn off labels for much faster creation of the graph.
1488
- ``positions`` - default: ``True``, if ``True`` the graph contains
1489
layout information. This creates a planar layout for the case
1490
of three pegs. For large instances, turn off layout information
1491
for much faster creation of the graph.
1492
1493
OUTPUT:
1494
1495
The Tower of Hanoi puzzle has a certain number of identical pegs
1496
and a certain number of disks, each of a different radius.
1497
Initially the disks are all on a single peg, arranged
1498
in order of their radii, with the largest on the bottom.
1499
1500
The goal of the puzzle is to move the disks to any other peg,
1501
arranged in the same order. The one constraint is that the
1502
disks resident on any one peg must always be arranged with larger
1503
radii lower down.
1504
1505
The vertices of this graph represent all the possible states
1506
of this puzzle. Each state of the puzzle is a tuple with length
1507
equal to the number of disks, ordered by largest disk first.
1508
The entry of the tuple is the peg where that disk resides.
1509
Since disks on a given peg must go down in size as we go
1510
up the peg, this totally describes the state of the puzzle.
1511
1512
For example ``(2,0,0)`` means the large disk is on peg 2, the
1513
medium disk is on peg 0, and the small disk is on peg 0
1514
(and we know the small disk must be above the medium disk).
1515
We encode these tuples as integers with a base equal to
1516
the number of pegs, and low-order digits to the right.
1517
1518
Two vertices are adjacent if we can change the puzzle from
1519
one state to the other by moving a single disk. For example,
1520
``(2,0,0)`` is adjacent to ``(2,0,1)`` since we can move
1521
the small disk off peg 0 and onto (the empty) peg 1.
1522
So the solution to a 3-disk puzzle (with at least
1523
two pegs) can be expressed by the shortest path between
1524
``(0,0,0)`` and ``(1,1,1)``. For more on this representation
1525
of the graph, or its properties, see [ARETT-DOREE]_.
1526
1527
For greatest speed we create graphs with integer vertices,
1528
where we encode the tuples as integers with a base equal
1529
to the number of pegs, and low-order digits to the right.
1530
So for example, in a 3-peg puzzle with 5 disks, the
1531
state ``(1,2,0,1,1)`` is encoded as
1532
`1\ast 3^4 + 2\ast 3^3 + 0\ast 3^2 + 1\ast 3^1 + 1\ast 3^0 = 139`.
1533
1534
For smaller graphs, the labels that are the tuples are informative,
1535
but slow down creation of the graph. Likewise computing layout
1536
information also incurs a significant speed penalty. For maximum
1537
speed, turn off labels and layout and decode the
1538
vertices explicitly as needed. The
1539
:meth:`sage.rings.integer.Integer.digits`
1540
with the ``padsto`` option is a quick way to do this, though you
1541
may want to reverse the list that is output.
1542
1543
PLOTTING:
1544
1545
The layout computed when ``positions = True`` will
1546
look especially good for the three-peg case, when the graph is known
1547
to be planar. Except for two small cases on 4 pegs, the graph is
1548
otherwise not planar, and likely there is a better way to layout
1549
the vertices.
1550
1551
EXAMPLES:
1552
1553
A classic puzzle uses 3 pegs. We solve the 5 disk puzzle using
1554
integer labels and report the minimum number of moves required.
1555
Note that `3^5-1` is the state where all 5 disks
1556
are on peg 2. ::
1557
1558
sage: H = graphs.HanoiTowerGraph(3, 5, labels=False, positions=False)
1559
sage: H.distance(0, 3^5-1)
1560
31
1561
1562
A slightly larger instance. ::
1563
1564
sage: H = graphs.HanoiTowerGraph(4, 6, labels=False, positions=False)
1565
sage: H.num_verts()
1566
4096
1567
sage: H.distance(0, 4^6-1)
1568
17
1569
1570
For a small graph, labels and layout information can be useful.
1571
Here we explicitly list a solution as a list of states. ::
1572
1573
sage: H = graphs.HanoiTowerGraph(3, 3, labels=True, positions=True)
1574
sage: H.shortest_path((0,0,0), (1,1,1))
1575
[(0, 0, 0), (0, 0, 1), (0, 2, 1), (0, 2, 2), (1, 2, 2), (1, 2, 0), (1, 1, 0), (1, 1, 1)]
1576
1577
Some facts about this graph with `p` pegs and `d` disks:
1578
1579
- only automorphisms are the "obvious" ones - renumber the pegs.
1580
- chromatic number is less than or equal to `p`
1581
- independence number is `p^{d-1}`
1582
1583
::
1584
1585
sage: H = graphs.HanoiTowerGraph(3,4,labels=False,positions=False)
1586
sage: H.automorphism_group().is_isomorphic(SymmetricGroup(3))
1587
True
1588
sage: H.chromatic_number()
1589
3
1590
sage: len(H.independent_set()) == 3^(4-1)
1591
True
1592
1593
TESTS:
1594
1595
It is an error to have just one peg (or less). ::
1596
1597
sage: graphs.HanoiTowerGraph(1, 5)
1598
Traceback (most recent call last):
1599
...
1600
ValueError: Pegs for Tower of Hanoi graph should be two or greater (not 1)
1601
1602
It is an error to have zero disks (or less). ::
1603
1604
sage: graphs.HanoiTowerGraph(2, 0)
1605
Traceback (most recent call last):
1606
...
1607
ValueError: Disks for Tower of Hanoi graph should be one or greater (not 0)
1608
1609
.. rubric:: Citations
1610
1611
.. [ARETT-DOREE] Arett, Danielle and Doree, Suzanne
1612
"Coloring and counting on the Hanoi graphs"
1613
Mathematics Magazine, Volume 83, Number 3, June 2010, pages 200-9
1614
1615
1616
AUTHOR:
1617
1618
- Rob Beezer, (2009-12-26), with assistance from Su Doree
1619
1620
"""
1621
1622
# sanitize input
1623
from sage.rings.all import Integer
1624
pegs = Integer(pegs)
1625
if pegs < 2:
1626
raise ValueError("Pegs for Tower of Hanoi graph should be two or greater (not %d)" % pegs)
1627
disks = Integer(disks)
1628
if disks < 1:
1629
raise ValueError("Disks for Tower of Hanoi graph should be one or greater (not %d)" % disks)
1630
1631
# Each state of the puzzle is a tuple with length
1632
# equal to the number of disks, ordered by largest disk first
1633
# The entry of the tuple is the peg where that disk resides
1634
# Since disks on a given peg must go down in size as we go
1635
# up the peg, this totally describes the puzzle
1636
# We encode these tuples as integers with a base equal to
1637
# the number of pegs, and low-order digits to the right
1638
1639
# complete graph on number of pegs when just a single disk
1640
edges = [[i,j] for i in range(pegs) for j in range(i+1,pegs)]
1641
1642
nverts = 1
1643
for d in range(2, disks+1):
1644
prevedges = edges # remember subgraph to build from
1645
nverts = pegs*nverts # pegs^(d-1)
1646
edges = []
1647
1648
# Take an edge, change its two states in the same way by adding
1649
# a large disk to the bottom of the same peg in each state
1650
# This is accomplished by adding a multiple of pegs^(d-1)
1651
for p in range(pegs):
1652
largedisk = p*nverts
1653
for anedge in prevedges:
1654
edges.append([anedge[0]+largedisk, anedge[1]+largedisk])
1655
1656
# Two new states may only differ in the large disk
1657
# being the only disk on two different pegs, thus
1658
# otherwise being a common state with one less disk
1659
# We construct all such pairs of new states and add as edges
1660
from sage.combinat.subset import Subsets
1661
for state in range(nverts):
1662
emptypegs = range(pegs)
1663
reduced_state = state
1664
for i in range(d-1):
1665
apeg = reduced_state % pegs
1666
if apeg in emptypegs:
1667
emptypegs.remove(apeg)
1668
reduced_state = reduced_state//pegs
1669
for freea, freeb in Subsets(emptypegs, 2):
1670
edges.append([freea*nverts+state,freeb*nverts+state])
1671
1672
H = Graph({}, loops=False, multiedges=False)
1673
H.add_edges(edges)
1674
1675
1676
# Making labels and/or computing positions can take a long time,
1677
# relative to just constructing the edges on integer vertices.
1678
# We try to minimize coercion overhead, but need Sage
1679
# Integers in order to use digits() for labels.
1680
# Getting the digits with custom code was no faster.
1681
# Layouts are circular (symmetric on the number of pegs)
1682
# radiating outward to the number of disks (radius)
1683
# Algorithm uses some combination of alternate
1684
# clockwise/counterclockwise placements, which
1685
# works well for three pegs (planar layout)
1686
#
1687
from sage.functions.trig import sin, cos, csc
1688
if labels or positions:
1689
mapping = {}
1690
pos = {}
1691
a = Integer(-1)
1692
one = Integer(1)
1693
if positions:
1694
radius_multiplier = 1 + csc(pi/pegs)
1695
sine = []; cosine = []
1696
for i in range(pegs):
1697
angle = 2*i*pi/float(pegs)
1698
sine.append(sin(angle))
1699
cosine.append(cos(angle))
1700
for i in range(pegs**disks):
1701
a += one
1702
state = a.digits(base=pegs, padto=disks)
1703
if labels:
1704
state.reverse()
1705
mapping[i] = tuple(state)
1706
state.reverse()
1707
if positions:
1708
locx = 0.0; locy = 0.0
1709
radius = 1.0
1710
parity = -1.0
1711
for index in range(disks):
1712
p = state[index]
1713
radius *= radius_multiplier
1714
parity *= -1.0
1715
locx_temp = cosine[p]*locx - parity*sine[p]*locy + radius*cosine[p]
1716
locy_temp = parity*sine[p]*locx + cosine[p]*locy - radius*parity*sine[p]
1717
locx = locx_temp
1718
locy = locy_temp
1719
pos[i] = (locx,locy)
1720
# set positions, then relabel (not vice versa)
1721
if positions:
1722
H.set_pos(pos)
1723
if labels:
1724
H.relabel(mapping)
1725
1726
return H
1727
1728
def line_graph_forbidden_subgraphs():
1729
r"""
1730
Returns the 9 forbidden subgraphs of a line graph.
1731
1732
`Wikipedia article on the line graphs
1733
<http://en.wikipedia.org/wiki/Line_graph>`_
1734
1735
The graphs are returned in the ordering given by the Wikipedia
1736
drawing, read from left to right and from top to bottom.
1737
1738
EXAMPLE::
1739
1740
sage: graphs.line_graph_forbidden_subgraphs()
1741
[Claw graph: Graph on 4 vertices,
1742
Graph on 6 vertices,
1743
Graph on 6 vertices,
1744
Graph on 5 vertices,
1745
Graph on 6 vertices,
1746
Graph on 6 vertices,
1747
Graph on 6 vertices,
1748
Graph on 6 vertices,
1749
Graph on 5 vertices]
1750
1751
"""
1752
from sage.graphs.all import Graph
1753
from sage.graphs.generators.basic import ClawGraph
1754
graphs = [ClawGraph()]
1755
1756
graphs.append(Graph({
1757
0: [1, 2, 3],
1758
1: [2, 3],
1759
4: [2],
1760
5: [3]
1761
}))
1762
1763
graphs.append(Graph({
1764
0: [1, 2, 3, 4],
1765
1: [2, 3, 4],
1766
3: [4],
1767
2: [5]
1768
}))
1769
1770
graphs.append(Graph({
1771
0: [1, 2, 3],
1772
1: [2, 3],
1773
4: [2, 3]
1774
}))
1775
1776
graphs.append(Graph({
1777
0: [1, 2, 3],
1778
1: [2, 3],
1779
4: [2],
1780
5: [3, 4]
1781
}))
1782
1783
graphs.append(Graph({
1784
0: [1, 2, 3, 4],
1785
1: [2, 3, 4],
1786
3: [4],
1787
5: [2, 0, 1]
1788
}))
1789
1790
graphs.append(Graph({
1791
5: [0, 1, 2, 3, 4],
1792
0: [1, 4],
1793
2: [1, 3],
1794
3: [4]
1795
}))
1796
1797
graphs.append(Graph({
1798
1: [0, 2, 3, 4],
1799
3: [0, 4],
1800
2: [4, 5],
1801
4: [5]
1802
}))
1803
1804
graphs.append(Graph({
1805
0: [1, 2, 3],
1806
1: [2, 3, 4],
1807
2: [3, 4],
1808
3: [4]
1809
}))
1810
1811
return graphs
1812
1813
def WheelGraph(n):
1814
"""
1815
Returns a Wheel graph with n nodes.
1816
1817
A Wheel graph is a basic structure where one node is connected to
1818
all other nodes and those (outer) nodes are connected cyclically.
1819
1820
This constructor depends on NetworkX numeric labels.
1821
1822
PLOTTING: Upon construction, the position dictionary is filled to
1823
override the spring-layout algorithm. By convention, each wheel
1824
graph will be displayed with the first (0) node in the center, the
1825
second node at the top, and the rest following in a
1826
counterclockwise manner.
1827
1828
With the wheel graph, we see that it doesn't take a very large n at
1829
all for the spring-layout to give a counter-intuitive display. (See
1830
Graphics Array examples below).
1831
1832
EXAMPLES: We view many wheel graphs with a Sage Graphics Array,
1833
first with this constructor (i.e., the position dictionary
1834
filled)::
1835
1836
sage: g = []
1837
sage: j = []
1838
sage: for i in range(9):
1839
... k = graphs.WheelGraph(i+3)
1840
... g.append(k)
1841
...
1842
sage: for i in range(3):
1843
... n = []
1844
... for m in range(3):
1845
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
1846
... j.append(n)
1847
...
1848
sage: G = sage.plot.graphics.GraphicsArray(j)
1849
sage: G.show() # long time
1850
1851
Next, using the spring-layout algorithm::
1852
1853
sage: import networkx
1854
sage: g = []
1855
sage: j = []
1856
sage: for i in range(9):
1857
... spr = networkx.wheel_graph(i+3)
1858
... k = Graph(spr)
1859
... g.append(k)
1860
...
1861
sage: for i in range(3):
1862
... n = []
1863
... for m in range(3):
1864
... n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
1865
... j.append(n)
1866
...
1867
sage: G = sage.plot.graphics.GraphicsArray(j)
1868
sage: G.show() # long time
1869
1870
Compare the plotting::
1871
1872
sage: n = networkx.wheel_graph(23)
1873
sage: spring23 = Graph(n)
1874
sage: posdict23 = graphs.WheelGraph(23)
1875
sage: spring23.show() # long time
1876
sage: posdict23.show() # long time
1877
"""
1878
pos_dict = {}
1879
pos_dict[0] = (0,0)
1880
for i in range(1,n):
1881
x = float(cos((pi/2) + ((2*pi)/(n-1))*(i-1)))
1882
y = float(sin((pi/2) + ((2*pi)/(n-1))*(i-1)))
1883
pos_dict[i] = (x,y)
1884
import networkx
1885
G = networkx.wheel_graph(n)
1886
return Graph(G, pos=pos_dict, name="Wheel graph")
1887
1888
def trees(vertices):
1889
r"""
1890
Returns a generator of the distinct trees on a fixed number of vertices.
1891
1892
INPUT:
1893
1894
- ``vertices`` - the size of the trees created.
1895
1896
OUTPUT:
1897
1898
A generator which creates an exhaustive, duplicate-free listing
1899
of the connected free (unlabeled) trees with ``vertices`` number
1900
of vertices. A tree is a graph with no cycles.
1901
1902
ALGORITHM:
1903
1904
Uses an algorithm that generates each new tree
1905
in constant time. See the documentation for, and implementation
1906
of, the :mod:`sage.graphs.trees` module, including a citation.
1907
1908
EXAMPLES:
1909
1910
We create an iterator, then loop over its elements. ::
1911
1912
sage: tree_iterator = graphs.trees(7)
1913
sage: for T in tree_iterator:
1914
... print T.degree_sequence()
1915
[2, 2, 2, 2, 2, 1, 1]
1916
[3, 2, 2, 2, 1, 1, 1]
1917
[3, 2, 2, 2, 1, 1, 1]
1918
[4, 2, 2, 1, 1, 1, 1]
1919
[3, 3, 2, 1, 1, 1, 1]
1920
[3, 3, 2, 1, 1, 1, 1]
1921
[4, 3, 1, 1, 1, 1, 1]
1922
[3, 2, 2, 2, 1, 1, 1]
1923
[4, 2, 2, 1, 1, 1, 1]
1924
[5, 2, 1, 1, 1, 1, 1]
1925
[6, 1, 1, 1, 1, 1, 1]
1926
1927
The number of trees on the first few vertex counts.
1928
This is sequence A000055 in Sloane's OEIS. ::
1929
1930
sage: [len(list(graphs.trees(i))) for i in range(0, 15)]
1931
[1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
1932
"""
1933
from sage.graphs.trees import TreeIterator
1934
return iter(TreeIterator(vertices))
1935
1936
def RingedTree(k, vertex_labels = True):
1937
r"""
1938
Return the ringed tree on k-levels.
1939
1940
A ringed tree of level `k` is a binary tree with `k` levels (counting
1941
the root as a level), in which all vertices at the same level are connected
1942
by a ring.
1943
1944
More precisely, in each layer of the binary tree (i.e. a layer is the set of
1945
vertices `[2^i...2^{i+1}-1]`) two vertices `u,v` are adjacent if `u=v+1` or
1946
if `u=2^i` and `v=`2^{i+1}-1`.
1947
1948
Ringed trees are defined in [CFHM12]_.
1949
1950
INPUT:
1951
1952
- ``k`` -- the number of levels of the ringed tree.
1953
1954
- ``vertex_labels`` (boolean) -- whether to label vertices as binary words
1955
(default) or as integers.
1956
1957
EXAMPLE::
1958
1959
sage: G = graphs.RingedTree(5)
1960
sage: P = G.plot(vertex_labels=False, vertex_size=10)
1961
sage: P.show() # long time
1962
sage: G.vertices()
1963
['', '0', '00', '000', '0000', '0001', '001', '0010', '0011', '01',
1964
'010', '0100', '0101', '011', '0110', '0111', '1', '10', '100',
1965
'1000', '1001', '101', '1010', '1011', '11', '110', '1100', '1101',
1966
'111', '1110', '1111']
1967
1968
TEST::
1969
1970
sage: G = graphs.RingedTree(-1)
1971
Traceback (most recent call last):
1972
...
1973
ValueError: The number of levels must be >= 1.
1974
sage: G = graphs.RingedTree(5, vertex_labels = False)
1975
sage: G.vertices()
1976
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
1977
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
1978
1979
REFERENCES:
1980
1981
.. [CFHM12] On the Hyperbolicity of Small-World and Tree-Like Random Graphs
1982
Wei Chen, Wenjie Fang, Guangda Hu, Michael W. Mahoney
1983
http://arxiv.org/abs/1201.1717
1984
"""
1985
if k<1:
1986
raise ValueError('The number of levels must be >= 1.')
1987
1988
from sage.graphs.graph_plot import _circle_embedding
1989
1990
# Creating the Balanced tree, which contains most edges already
1991
g = BalancedTree(2,k-1)
1992
g.name('Ringed Tree on '+str(k)+' levels')
1993
1994
# We consider edges layer by layer
1995
for i in range(1,k):
1996
vertices = range(2**(i)-1,2**(i+1)-1)
1997
1998
# Add the missing edges
1999
g.add_cycle(vertices)
2000
2001
# And set the vertices' positions
2002
radius = i if i <= 1 else 1.5**i
2003
shift = -2**(i-2)+.5 if i > 1 else 0
2004
_circle_embedding(g, vertices, radius = radius, shift = shift)
2005
2006
# Specific position for the central vertex
2007
g.get_pos()[0] = (0,0.2)
2008
2009
# Relabel vertices as binary words
2010
if not vertex_labels:
2011
return g
2012
2013
vertices = ['']
2014
for i in range(k-1):
2015
for j in range(2**(i)-1,2**(i+1)-1):
2016
v = vertices[j]
2017
vertices.append(v+'0')
2018
vertices.append(v+'1')
2019
2020
g.relabel(vertices)
2021
2022
return g
2023
2024
def SymplecticGraph(d,q):
2025
r"""
2026
Returns the Symplectic graph `Sp(d,q)`
2027
2028
The Symplectic Graph `Sp(d,q)` is built from a projective space of dimension
2029
`d-1` over a field `F_q`, and a symplectic form `f`. Two vertices `u,v` are
2030
made adjacent if `f(u,v)=0`.
2031
2032
See the `page on symplectic graphs on Andries Brouwer's website
2033
<http://www.win.tue.nl/~aeb/graphs/Sp.html>`_.
2034
2035
INPUT:
2036
2037
- ``d,q`` (integers) -- note that only even values of `d` are accepted by
2038
the function.
2039
2040
EXAMPLES::
2041
2042
sage: g = graphs.SymplecticGraph(6,2)
2043
sage: g.is_strongly_regular(parameters=True)
2044
(63, 30, 13, 15)
2045
sage: set(g.spectrum()) == {-5, 3, 30}
2046
True
2047
"""
2048
from sage.rings.finite_rings.constructor import FiniteField
2049
from sage.modules.free_module import VectorSpace
2050
from sage.schemes.projective.projective_space import ProjectiveSpace
2051
from sage.matrix.constructor import identity_matrix, block_matrix, zero_matrix
2052
2053
if d < 1 or d%2 != 0:
2054
raise ValueError("d must be even and greater than 2")
2055
2056
F = FiniteField(q,"x")
2057
M = block_matrix(F, 2, 2,
2058
[zero_matrix(F,d/2),
2059
identity_matrix(F,d/2),
2060
-identity_matrix(F,d/2),
2061
zero_matrix(F,d/2)])
2062
2063
V = VectorSpace(F,d)
2064
PV = list(ProjectiveSpace(d-1,F))
2065
G = Graph([map(tuple,PV), lambda x,y:V(x)*(M*V(y)) == 0], loops = False)
2066
G.name("Symplectic Graph Sp("+str(d)+","+str(q)+")")
2067
G.relabel()
2068
return G
2069
2070
2071