passagemath-graphs 10.5.43__cp39-cp39-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. passagemath_graphs-10.5.43.dist-info/METADATA +293 -0
  2. passagemath_graphs-10.5.43.dist-info/RECORD +258 -0
  3. passagemath_graphs-10.5.43.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.5.43.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2552 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +125 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1556 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2262 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +534 -0
  25. sage/combinat/designs/database.py +5614 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-39-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-39-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +548 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2243 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-39-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +966 -0
  44. sage/combinat/designs/resolvable_bibd.py +781 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-39-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/cartesian_product.py +493 -0
  57. sage/combinat/posets/d_complete.py +182 -0
  58. sage/combinat/posets/elements.py +273 -0
  59. sage/combinat/posets/forest.py +30 -0
  60. sage/combinat/posets/hasse_cython.cpython-39-aarch64-linux-gnu.so +0 -0
  61. sage/combinat/posets/hasse_cython.pyx +174 -0
  62. sage/combinat/posets/hasse_diagram.py +3678 -0
  63. sage/combinat/posets/incidence_algebras.py +796 -0
  64. sage/combinat/posets/lattices.py +5119 -0
  65. sage/combinat/posets/linear_extension_iterator.cpython-39-aarch64-linux-gnu.so +0 -0
  66. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  67. sage/combinat/posets/linear_extensions.py +1039 -0
  68. sage/combinat/posets/mobile.py +275 -0
  69. sage/combinat/posets/moebius_algebra.py +776 -0
  70. sage/combinat/posets/poset_examples.py +2131 -0
  71. sage/combinat/posets/posets.py +9169 -0
  72. sage/combinat/rooted_tree.py +1070 -0
  73. sage/combinat/shard_order.py +239 -0
  74. sage/combinat/tamari_lattices.py +384 -0
  75. sage/combinat/yang_baxter_graph.py +923 -0
  76. sage/databases/all__sagemath_graphs.py +1 -0
  77. sage/databases/knotinfo_db.py +1230 -0
  78. sage/ext_data/all__sagemath_graphs.py +1 -0
  79. sage/ext_data/graphs/graph_plot_js.html +330 -0
  80. sage/ext_data/kenzo/CP2.txt +45 -0
  81. sage/ext_data/kenzo/CP3.txt +349 -0
  82. sage/ext_data/kenzo/CP4.txt +4774 -0
  83. sage/ext_data/kenzo/README.txt +49 -0
  84. sage/ext_data/kenzo/S4.txt +20 -0
  85. sage/graphs/all.py +42 -0
  86. sage/graphs/asteroidal_triples.cpython-39-aarch64-linux-gnu.so +0 -0
  87. sage/graphs/asteroidal_triples.pyx +299 -0
  88. sage/graphs/base/all.py +1 -0
  89. sage/graphs/base/boost_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sage/graphs/base/boost_graph.pxd +106 -0
  91. sage/graphs/base/boost_graph.pyx +3045 -0
  92. sage/graphs/base/c_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  93. sage/graphs/base/c_graph.pxd +106 -0
  94. sage/graphs/base/c_graph.pyx +5096 -0
  95. sage/graphs/base/dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  96. sage/graphs/base/dense_graph.pxd +26 -0
  97. sage/graphs/base/dense_graph.pyx +757 -0
  98. sage/graphs/base/graph_backends.cpython-39-aarch64-linux-gnu.so +0 -0
  99. sage/graphs/base/graph_backends.pxd +5 -0
  100. sage/graphs/base/graph_backends.pyx +797 -0
  101. sage/graphs/base/overview.py +85 -0
  102. sage/graphs/base/sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  103. sage/graphs/base/sparse_graph.pxd +90 -0
  104. sage/graphs/base/sparse_graph.pyx +1653 -0
  105. sage/graphs/base/static_dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  106. sage/graphs/base/static_dense_graph.pxd +5 -0
  107. sage/graphs/base/static_dense_graph.pyx +1032 -0
  108. sage/graphs/base/static_sparse_backend.cpython-39-aarch64-linux-gnu.so +0 -0
  109. sage/graphs/base/static_sparse_backend.pxd +27 -0
  110. sage/graphs/base/static_sparse_backend.pyx +1580 -0
  111. sage/graphs/base/static_sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  112. sage/graphs/base/static_sparse_graph.pxd +37 -0
  113. sage/graphs/base/static_sparse_graph.pyx +1304 -0
  114. sage/graphs/bipartite_graph.py +2709 -0
  115. sage/graphs/centrality.cpython-39-aarch64-linux-gnu.so +0 -0
  116. sage/graphs/centrality.pyx +965 -0
  117. sage/graphs/cographs.py +519 -0
  118. sage/graphs/comparability.cpython-39-aarch64-linux-gnu.so +0 -0
  119. sage/graphs/comparability.pyx +813 -0
  120. sage/graphs/connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/connectivity.pxd +157 -0
  122. sage/graphs/connectivity.pyx +4813 -0
  123. sage/graphs/convexity_properties.cpython-39-aarch64-linux-gnu.so +0 -0
  124. sage/graphs/convexity_properties.pxd +16 -0
  125. sage/graphs/convexity_properties.pyx +827 -0
  126. sage/graphs/digraph.py +4410 -0
  127. sage/graphs/digraph_generators.py +1921 -0
  128. sage/graphs/distances_all_pairs.cpython-39-aarch64-linux-gnu.so +0 -0
  129. sage/graphs/distances_all_pairs.pxd +12 -0
  130. sage/graphs/distances_all_pairs.pyx +2938 -0
  131. sage/graphs/domination.py +1363 -0
  132. sage/graphs/dot2tex_utils.py +100 -0
  133. sage/graphs/edge_connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  134. sage/graphs/edge_connectivity.pyx +1215 -0
  135. sage/graphs/generators/all.py +1 -0
  136. sage/graphs/generators/basic.py +1769 -0
  137. sage/graphs/generators/chessboard.py +538 -0
  138. sage/graphs/generators/classical_geometries.py +1611 -0
  139. sage/graphs/generators/degree_sequence.py +235 -0
  140. sage/graphs/generators/distance_regular.cpython-39-aarch64-linux-gnu.so +0 -0
  141. sage/graphs/generators/distance_regular.pyx +2846 -0
  142. sage/graphs/generators/families.py +4749 -0
  143. sage/graphs/generators/intersection.py +565 -0
  144. sage/graphs/generators/platonic_solids.py +262 -0
  145. sage/graphs/generators/random.py +2623 -0
  146. sage/graphs/generators/smallgraphs.py +5741 -0
  147. sage/graphs/generators/world_map.py +724 -0
  148. sage/graphs/generic_graph.py +26395 -0
  149. sage/graphs/generic_graph_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  150. sage/graphs/generic_graph_pyx.pxd +34 -0
  151. sage/graphs/generic_graph_pyx.pyx +1626 -0
  152. sage/graphs/genus.cpython-39-aarch64-linux-gnu.so +0 -0
  153. sage/graphs/genus.pyx +623 -0
  154. sage/graphs/graph.py +9362 -0
  155. sage/graphs/graph_coloring.cpython-39-aarch64-linux-gnu.so +0 -0
  156. sage/graphs/graph_coloring.pyx +2284 -0
  157. sage/graphs/graph_database.py +1122 -0
  158. sage/graphs/graph_decompositions/all.py +1 -0
  159. sage/graphs/graph_decompositions/bandwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  160. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  161. sage/graphs/graph_decompositions/clique_separators.cpython-39-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/clique_separators.pyx +595 -0
  163. sage/graphs/graph_decompositions/cutwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  165. sage/graphs/graph_decompositions/fast_digraph.cpython-39-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  167. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  168. sage/graphs/graph_decompositions/graph_products.cpython-39-aarch64-linux-gnu.so +0 -0
  169. sage/graphs/graph_decompositions/graph_products.pyx +462 -0
  170. sage/graphs/graph_decompositions/modular_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  173. sage/graphs/graph_decompositions/slice_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  174. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.pyx +1080 -0
  176. sage/graphs/graph_decompositions/tree_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  177. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  179. sage/graphs/graph_decompositions/vertex_separation.cpython-39-aarch64-linux-gnu.so +0 -0
  180. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  181. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  182. sage/graphs/graph_editor.py +82 -0
  183. sage/graphs/graph_generators.py +3301 -0
  184. sage/graphs/graph_generators_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  185. sage/graphs/graph_generators_pyx.pyx +95 -0
  186. sage/graphs/graph_input.py +812 -0
  187. sage/graphs/graph_latex.py +2064 -0
  188. sage/graphs/graph_list.py +367 -0
  189. sage/graphs/graph_plot.py +1749 -0
  190. sage/graphs/graph_plot_js.py +338 -0
  191. sage/graphs/hyperbolicity.cpython-39-aarch64-linux-gnu.so +0 -0
  192. sage/graphs/hyperbolicity.pyx +1702 -0
  193. sage/graphs/hypergraph_generators.py +364 -0
  194. sage/graphs/independent_sets.cpython-39-aarch64-linux-gnu.so +0 -0
  195. sage/graphs/independent_sets.pxd +13 -0
  196. sage/graphs/independent_sets.pyx +402 -0
  197. sage/graphs/isgci.py +1033 -0
  198. sage/graphs/isoperimetric_inequalities.cpython-39-aarch64-linux-gnu.so +0 -0
  199. sage/graphs/isoperimetric_inequalities.pyx +453 -0
  200. sage/graphs/line_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/line_graph.pyx +627 -0
  202. sage/graphs/lovasz_theta.py +77 -0
  203. sage/graphs/matching.py +1633 -0
  204. sage/graphs/matching_covered_graph.py +3566 -0
  205. sage/graphs/orientations.py +1504 -0
  206. sage/graphs/partial_cube.py +459 -0
  207. sage/graphs/path_enumeration.cpython-39-aarch64-linux-gnu.so +0 -0
  208. sage/graphs/path_enumeration.pyx +2040 -0
  209. sage/graphs/pq_trees.py +1129 -0
  210. sage/graphs/print_graphs.py +201 -0
  211. sage/graphs/schnyder.py +865 -0
  212. sage/graphs/spanning_tree.cpython-39-aarch64-linux-gnu.so +0 -0
  213. sage/graphs/spanning_tree.pyx +1457 -0
  214. sage/graphs/strongly_regular_db.cpython-39-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/strongly_regular_db.pyx +3340 -0
  216. sage/graphs/traversals.cpython-39-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/traversals.pxd +9 -0
  218. sage/graphs/traversals.pyx +1871 -0
  219. sage/graphs/trees.cpython-39-aarch64-linux-gnu.so +0 -0
  220. sage/graphs/trees.pxd +15 -0
  221. sage/graphs/trees.pyx +310 -0
  222. sage/graphs/tutte_polynomial.py +713 -0
  223. sage/graphs/views.cpython-39-aarch64-linux-gnu.so +0 -0
  224. sage/graphs/views.pyx +794 -0
  225. sage/graphs/weakly_chordal.cpython-39-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/weakly_chordal.pyx +562 -0
  227. sage/groups/all__sagemath_graphs.py +1 -0
  228. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  229. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-39-aarch64-linux-gnu.so +0 -0
  231. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  233. sage/knots/all.py +6 -0
  234. sage/knots/free_knotinfo_monoid.py +507 -0
  235. sage/knots/gauss_code.py +291 -0
  236. sage/knots/knot.py +682 -0
  237. sage/knots/knot_table.py +284 -0
  238. sage/knots/knotinfo.py +2880 -0
  239. sage/knots/link.py +4682 -0
  240. sage/sandpiles/all.py +13 -0
  241. sage/sandpiles/examples.py +225 -0
  242. sage/sandpiles/sandpile.py +6365 -0
  243. sage/topology/all.py +22 -0
  244. sage/topology/cell_complex.py +1214 -0
  245. sage/topology/cubical_complex.py +1977 -0
  246. sage/topology/delta_complex.py +1806 -0
  247. sage/topology/filtered_simplicial_complex.py +744 -0
  248. sage/topology/moment_angle_complex.py +823 -0
  249. sage/topology/simplicial_complex.py +5161 -0
  250. sage/topology/simplicial_complex_catalog.py +86 -0
  251. sage/topology/simplicial_complex_examples.py +1692 -0
  252. sage/topology/simplicial_complex_homset.py +205 -0
  253. sage/topology/simplicial_complex_morphism.py +836 -0
  254. sage/topology/simplicial_set.py +4102 -0
  255. sage/topology/simplicial_set_catalog.py +55 -0
  256. sage/topology/simplicial_set_constructions.py +2954 -0
  257. sage/topology/simplicial_set_examples.py +865 -0
  258. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,4749 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Various families of graphs
4
+
5
+ The methods defined here appear in :mod:`sage.graphs.graph_generators`.
6
+ """
7
+
8
+ # ****************************************************************************
9
+ # Copyright (C) 2006 Robert L. Miller <rlmillster@gmail.com>
10
+ # Emily A. Kirkman
11
+ # 2009 Michael C. Yurko <myurko@gmail.com>
12
+ # 2016 Rowan Schrecker <rowan.schrecker@hertford.ox.ac.uk>
13
+ #
14
+ # This program is free software: you can redistribute it and/or modify
15
+ # it under the terms of the GNU General Public License as published by
16
+ # the Free Software Foundation, either version 2 of the License, or
17
+ # (at your option) any later version.
18
+ # https://www.gnu.org/licenses/
19
+ # ****************************************************************************
20
+
21
+ from copy import copy
22
+ from math import sin, cos, pi
23
+ from sage.graphs.graph import Graph
24
+ from itertools import combinations
25
+ import subprocess
26
+
27
+
28
+ def JohnsonGraph(n, k):
29
+ r"""
30
+ Return 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. See the :wikipedia:`Johnson_graph` for more
36
+ information.
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() # needs sage.numerical.mip
44
+ True
45
+
46
+ Every Johnson graph is vertex transitive::
47
+
48
+ sage: g = graphs.JohnsonGraph(6, 4)
49
+ sage: g.is_vertex_transitive() # needs sage.groups
50
+ True
51
+
52
+ The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser
53
+ Graph `K(n,2)`. In particular 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
+ Return 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
+ EXAMPLES::
91
+
92
+ sage: KG = graphs.KneserGraph(5,2)
93
+ sage: sorted(KG.vertex_iterator(), key=str)
94
+ [{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5},
95
+ {3, 4}, {3, 5}, {4, 5}]
96
+ sage: P = graphs.PetersenGraph()
97
+ sage: P.is_isomorphic(KG)
98
+ True
99
+
100
+ TESTS::
101
+
102
+ sage: KG = graphs.KneserGraph(0,0)
103
+ Traceback (most recent call last):
104
+ ...
105
+ ValueError: Parameter n should be a strictly positive integer
106
+ sage: KG = graphs.KneserGraph(5,6)
107
+ Traceback (most recent call last):
108
+ ...
109
+ ValueError: Parameter k should be a strictly positive integer inferior to n
110
+ """
111
+
112
+ if n <= 0:
113
+ raise ValueError("Parameter n should be a strictly positive integer")
114
+ if k <= 0 or k > n:
115
+ raise ValueError("Parameter k should be a strictly positive integer inferior to n")
116
+
117
+ g = Graph(name="Kneser graph with parameters {},{}".format(n, k))
118
+
119
+ from sage.combinat.subset import Subsets
120
+ S = Subsets(n, k)
121
+ if 2 * k > n:
122
+ g.add_vertices(S)
123
+
124
+ s0 = S.underlying_set() # {1,2,...,n}
125
+ for s in S:
126
+ for t in Subsets(s0.difference(s), k):
127
+ g.add_edge(s, t)
128
+
129
+ return g
130
+
131
+
132
+ def FurerGadget(k, prefix=None):
133
+ r"""
134
+ Return a Furer gadget of order ``k`` and their coloring.
135
+
136
+ Construct the Furer gadget described in [CFI1992]_,
137
+ a graph composed by a middle layer of `2^(k-1)` nodes
138
+ and two sets of nodes `(a_0, ... , a_{k-1})` and
139
+ `(b_0, ... , b_{k-1})`.
140
+ Each node in the middle is connected to either `a_i` or `b_i`,
141
+ for each i in [0,k[.
142
+ To read about the complete construction, see [CFI1992]_.
143
+ The returned coloring colors the middle section with one color, and
144
+ then each pair `(a_i, b_i)` with another color.
145
+ Since this method is mainly used to create Furer gadgets for the
146
+ Cai-Furer-Immerman construction, returning gadgets that don't
147
+ always have the same vertex labels is important, that's why there is
148
+ a parameter to manually set a prefix to be appended to each vertex label.
149
+
150
+ INPUT:
151
+
152
+ - ``k`` -- the order of the returned Furer gadget, greater than 0
153
+
154
+ - ``prefix`` -- prefix of to be appended to each vertex label,
155
+ so as to individualise the returned Furer gadget; must be comparable for
156
+ equality and hashable
157
+
158
+ OUTPUT:
159
+
160
+ - ``G`` -- the Furer gadget of order ``k``
161
+
162
+ - ``coloring`` -- list of list of vertices, representing the
163
+ partition induced by the coloring of ``G``'s vertices
164
+
165
+ EXAMPLES:
166
+
167
+ Furer gadget of order 3, without any prefix. ::
168
+
169
+ sage: G, p = graphs.FurerGadget(3)
170
+ sage: sorted(G, key=str)
171
+ [(), (0, 'a'), (0, 'b'), (0, 1), (0, 2),
172
+ (1, 'a'), (1, 'b'), (1, 2), (2, 'a'), (2, 'b')]
173
+ sage: sorted(G.edge_iterator(), key=str)
174
+ [((), (0, 'b'), None), ((), (1, 'b'), None),
175
+ ((), (2, 'b'), None), ((0, 'b'), (1, 2), None),
176
+ ((0, 1), (0, 'a'), None), ((0, 1), (1, 'a'), None),
177
+ ((0, 1), (2, 'b'), None), ((0, 2), (0, 'a'), None),
178
+ ((0, 2), (1, 'b'), None), ((0, 2), (2, 'a'), None),
179
+ ((1, 2), (1, 'a'), None), ((1, 2), (2, 'a'), None)]
180
+
181
+ Furer gadget of order 3, with a prefix. ::
182
+
183
+ sage: G, p = graphs.FurerGadget(3, 'Prefix')
184
+ sage: sorted(G, key=str)
185
+ [('Prefix', ()), ('Prefix', (0, 'a')), ('Prefix', (0, 'b')),
186
+ ('Prefix', (0, 1)), ('Prefix', (0, 2)), ('Prefix', (1, 'a')),
187
+ ('Prefix', (1, 'b')), ('Prefix', (1, 2)), ('Prefix', (2, 'a')),
188
+ ('Prefix', (2, 'b'))]
189
+ sage: sorted(G.edge_iterator(), key=str)
190
+ [(('Prefix', ()), ('Prefix', (0, 'b')), None),
191
+ (('Prefix', ()), ('Prefix', (1, 'b')), None),
192
+ (('Prefix', ()), ('Prefix', (2, 'b')), None),
193
+ (('Prefix', (0, 'b')), ('Prefix', (1, 2)), None),
194
+ (('Prefix', (0, 1)), ('Prefix', (0, 'a')), None),
195
+ (('Prefix', (0, 1)), ('Prefix', (1, 'a')), None),
196
+ (('Prefix', (0, 1)), ('Prefix', (2, 'b')), None),
197
+ (('Prefix', (0, 2)), ('Prefix', (0, 'a')), None),
198
+ (('Prefix', (0, 2)), ('Prefix', (1, 'b')), None),
199
+ (('Prefix', (0, 2)), ('Prefix', (2, 'a')), None),
200
+ (('Prefix', (1, 2)), ('Prefix', (1, 'a')), None),
201
+ (('Prefix', (1, 2)), ('Prefix', (2, 'a')), None)]
202
+ """
203
+ from itertools import repeat as rep, chain
204
+ if k <= 0:
205
+ raise ValueError("The order of the Furer gadget must be greater than zero")
206
+ G = Graph()
207
+ V_a = list(enumerate(rep('a', k)))
208
+ V_b = list(enumerate(rep('b', k)))
209
+ if prefix is not None:
210
+ V_a = list(zip(rep(prefix, k), V_a))
211
+ V_b = list(zip(rep(prefix, k), V_b))
212
+ G.add_vertices(V_a)
213
+ G.add_vertices(V_b)
214
+ powerset = list(chain.from_iterable(combinations(range(k), r) for r in range(0, k + 1, 2)))
215
+ if prefix is not None:
216
+ G.add_edges(chain.from_iterable([((prefix, s), (prefix, (i, 'a'))) for i in s] for s in powerset))
217
+ G.add_edges(chain.from_iterable([((prefix, s), (prefix, (i, 'b'))) for i in range(k) if i not in s] for s in powerset))
218
+ else:
219
+ G.add_edges(chain.from_iterable([(s, (i, 'a')) for i in s] for s in powerset))
220
+ G.add_edges(chain.from_iterable([(s, (i, 'b')) for i in range(k) if i not in s] for s in powerset))
221
+ partition = []
222
+ for i in range(k):
223
+ partition.append([V_a[i], V_b[i]])
224
+ if prefix is not None:
225
+ powerset = [(prefix, s) for s in powerset]
226
+ partition.append(powerset)
227
+ return G, partition
228
+
229
+
230
+ def CaiFurerImmermanGraph(G, twisted=False):
231
+ r"""
232
+ Return the a Cai-Furer-Immerman graph from `G`, possibly a twisted
233
+ one, and a partition of its nodes.
234
+
235
+ A Cai-Furer-Immerman graph from/on `G` is a graph created by
236
+ applying the transformation described in [CFI1992]_ on a graph
237
+ `G`, that is substituting every vertex v in `G` with a
238
+ Furer gadget `F(v)` of order d equal to the degree of the vertex,
239
+ and then substituting every edge `(v,u)` in `G`
240
+ with a pair of edges, one connecting the two "a" nodes of
241
+ `F(v)` and `F(u)` and the other their two "b" nodes.
242
+ The returned coloring of the vertices is made by the union of the
243
+ colorings of each single Furer gadget, individualised for each
244
+ vertex of `G`.
245
+ To understand better what these "a" and "b" nodes are, see the
246
+ documentation on Furer gadgets.
247
+
248
+ Furthermore, this method can apply what is described in the paper
249
+ mentioned above as a "twist" on an edge, that is taking only one of
250
+ the pairs of edges introduced in the new graph and swap two of their
251
+ extremes, making each edge go from an "a" node to a "b" node.
252
+ This is only doable if the original graph G is connected.
253
+
254
+ A CaiFurerImmerman graph on a graph with no balanced vertex
255
+ separators smaller than s and its twisted version
256
+ cannot be distinguished by k-WL for any k < s.
257
+
258
+ INPUT:
259
+
260
+ - ``G`` -- an undirected graph on which to construct the
261
+ Cai-Furer-Immerman graph
262
+
263
+ - ``twisted`` -- a boolean indicating if the version to construct
264
+ is a twisted one or not
265
+
266
+ OUTPUT:
267
+
268
+ - ``H`` -- the Cai-Furer-Immerman graph on ``G``
269
+
270
+ - ``coloring`` -- list of list of vertices, representing the
271
+ partition induced by the coloring on ``H``
272
+
273
+ EXAMPLES:
274
+
275
+ CaiFurerImmerman graph with no balanced vertex separator smaller
276
+ than 2 ::
277
+
278
+ sage: G = graphs.CycleGraph(4)
279
+ sage: CFI, p = graphs.CaiFurerImmermanGraph(G)
280
+ sage: sorted(CFI, key=str)
281
+ [(0, ()), (0, (0, 'a')), (0, (0, 'b')), (0, (0, 1)), (0, (1, 'a')),
282
+ (0, (1, 'b')), (1, ()), (1, (0, 'a')), (1, (0, 'b')), (1, (0, 1)),
283
+ (1, (1, 'a')), (1, (1, 'b')), (2, ()), (2, (0, 'a')), (2, (0, 'b')),
284
+ (2, (0, 1)), (2, (1, 'a')), (2, (1, 'b')), (3, ()), (3, (0, 'a')),
285
+ (3, (0, 'b')), (3, (0, 1)), (3, (1, 'a')), (3, (1, 'b'))]
286
+ sage: sorted(CFI.edge_iterator(), key=str)
287
+ [((0, ()), (0, (0, 'b')), None),
288
+ ((0, ()), (0, (1, 'b')), None),
289
+ ((0, (0, 'a')), (1, (0, 'a')), None),
290
+ ((0, (0, 'b')), (1, (0, 'b')), None),
291
+ ((0, (0, 1)), (0, (0, 'a')), None),
292
+ ((0, (0, 1)), (0, (1, 'a')), None),
293
+ ((0, (1, 'a')), (3, (0, 'a')), None),
294
+ ((0, (1, 'b')), (3, (0, 'b')), None),
295
+ ((1, ()), (1, (0, 'b')), None),
296
+ ((1, ()), (1, (1, 'b')), None),
297
+ ((1, (0, 1)), (1, (0, 'a')), None),
298
+ ((1, (0, 1)), (1, (1, 'a')), None),
299
+ ((1, (1, 'a')), (2, (0, 'a')), None),
300
+ ((1, (1, 'b')), (2, (0, 'b')), None),
301
+ ((2, ()), (2, (0, 'b')), None),
302
+ ((2, ()), (2, (1, 'b')), None),
303
+ ((2, (0, 1)), (2, (0, 'a')), None),
304
+ ((2, (0, 1)), (2, (1, 'a')), None),
305
+ ((2, (1, 'a')), (3, (1, 'a')), None),
306
+ ((2, (1, 'b')), (3, (1, 'b')), None),
307
+ ((3, ()), (3, (0, 'b')), None),
308
+ ((3, ()), (3, (1, 'b')), None),
309
+ ((3, (0, 1)), (3, (0, 'a')), None),
310
+ ((3, (0, 1)), (3, (1, 'a')), None)]
311
+ """
312
+ isConnected = G.is_connected()
313
+ newG = Graph()
314
+ total_partition = []
315
+ edge_index = {}
316
+ for v in G:
317
+ Fk, p = FurerGadget(G.degree(v), v)
318
+ total_partition += p
319
+ newG = newG.union(Fk)
320
+ edge_index[v] = 0
321
+ for v, u in G.edge_iterator(labels=False):
322
+ i = edge_index[v]
323
+ edge_index[v] += 1
324
+ j = edge_index[u]
325
+ edge_index[u] += 1
326
+ edge_va = (v, (i, 'a'))
327
+ edge_vb = (v, (i, 'b'))
328
+ edge_ua = (u, (j, 'a'))
329
+ edge_ub = (u, (j, 'b'))
330
+ if isConnected and twisted:
331
+ temp = edge_ua
332
+ edge_ua = edge_ub
333
+ edge_ub = temp
334
+ isConnected = False
335
+ newG.add_edge(edge_va, edge_ua)
336
+ newG.add_edge(edge_vb, edge_ub)
337
+ if twisted and G.is_connected():
338
+ s = " twisted"
339
+ else:
340
+ s = ""
341
+ newG.name("CaiFurerImmerman" + s + " graph constructed from a " + G.name())
342
+ return newG, total_partition
343
+
344
+
345
+ def EgawaGraph(p, s):
346
+ r"""
347
+ Return the Egawa graph with parameters `p`, `s`.
348
+
349
+ Egawa graphs are a peculiar family of graphs devised by Yoshimi
350
+ Egawa in [Ega1981]_ .
351
+ The Shrikhande graph is a special case of this family of graphs,
352
+ with parameters `(1,0)`.
353
+ All the graphs in this family are not recognizable by 1-WL
354
+ (Weisfeiler Lehamn algorithm of the first order) and 2-WL, that is
355
+ their orbits are not correctly returned by k-WL for k lower than 3.
356
+
357
+ Furthermore, all the graphs in this family are distance-regular, but
358
+ they are not distance-transitive if `p \neq 0`.
359
+
360
+ The Egawa graph with parameters `(0, s)` is isomorphic to the
361
+ Hamming graph with parameters `(s, 4)`, when the underlying
362
+ set of the Hamming graph is `[0,1,2,3]`
363
+
364
+ INPUT:
365
+
366
+ - ``p`` -- power to which the graph named `Y` in the reference
367
+ provided above will be raised
368
+
369
+ - ``s`` -- power to which the graph named `X` in the reference
370
+ provided above will be raised
371
+
372
+ OUTPUT:
373
+
374
+ - ``G`` -- the Egawa graph with parameters (p,s)
375
+
376
+ EXAMPLES:
377
+
378
+ Every Egawa graph is distance regular. ::
379
+
380
+ sage: g = graphs.EgawaGraph(1, 2)
381
+ sage: g.is_distance_regular()
382
+ True
383
+
384
+ An Egawa graph with parameters (0,s) is isomorphic to the Hamming
385
+ graph with parameters (s, 4). ::
386
+
387
+ sage: g = graphs.EgawaGraph(0, 4)
388
+ sage: g.is_isomorphic(graphs.HammingGraph(4,4))
389
+ True
390
+ """
391
+ from sage.graphs.generators.basic import CompleteGraph
392
+ from itertools import product, chain, repeat
393
+ g = Graph(name="Egawa Graph with parameters " + str(p) + "," + str(s), multiedges=False)
394
+ X = CompleteGraph(4)
395
+ Y = Graph('O?Wse@UgqqT_LUebWkbT_')
396
+ g.add_vertices(product(*chain(repeat(Y, p), repeat(X, s))))
397
+ for v in g:
398
+ for i in range(p):
399
+ prefix = v[:i]
400
+ suffix = v[i+1:]
401
+ for el in Y.neighbor_iterator(v[i]):
402
+ u = prefix + (el,) + suffix
403
+ g.add_edge(v, u)
404
+ for i in range(p, s + p):
405
+ prefix = v[:i]
406
+ suffix = v[i+1:]
407
+ for el in X:
408
+ if el == v[i]:
409
+ continue
410
+ u = prefix + (el,) + suffix
411
+ g.add_edge(v, u)
412
+ return g
413
+
414
+
415
+ def HammingGraph(n, q, X=None):
416
+ r"""
417
+ Return the Hamming graph with parameters `n`, `q` over `X`.
418
+
419
+ Hamming graphs are graphs over the cartesian product of n copies
420
+ of `X`, where `q = |X|`, where the vertices, labelled with the
421
+ corresponding tuple in `X^n`, are connected if the Hamming distance
422
+ between their labels is 1. All Hamming graphs are regular,
423
+ vertex-transitive and distance-regular.
424
+
425
+ Hamming graphs with parameters `(1,q)` represent the complete graph
426
+ with q vertices over the set `X`.
427
+
428
+ INPUT:
429
+
430
+ - ``n`` -- power to which ``X`` will be raised to provide vertices
431
+ for the Hamming graph
432
+
433
+ - ``q`` -- cardinality of ``X``
434
+
435
+ - ``X`` -- list of labels representing the vertices of the underlying graph
436
+ the Hamming graph will be based on; if ``None`` (or left unused), the
437
+ list `[0, ... , q-1]` will be used
438
+
439
+ OUTPUT:
440
+
441
+ - ``G`` -- the Hamming graph with parameters `(n,q,X)`
442
+
443
+ EXAMPLES:
444
+
445
+ Every Hamming graph is distance-regular, regular and vertex-transitive::
446
+
447
+ sage: g = graphs.HammingGraph(3, 7)
448
+ sage: g.is_distance_regular()
449
+ True
450
+ sage: g.is_regular()
451
+ True
452
+ sage: g.is_vertex_transitive() # needs sage.groups
453
+ True
454
+
455
+ A Hamming graph with parameters `(1,q)` is isomorphic to the
456
+ Complete graph with parameter `q`::
457
+
458
+ sage: g = graphs.HammingGraph(1, 23)
459
+ sage: g.is_isomorphic(graphs.CompleteGraph(23))
460
+ True
461
+
462
+ If a parameter `q` is provided which is not equal to `X`'s
463
+ cardinality, an exception is raised::
464
+
465
+ sage: X = ['a','b','c','d','e']
466
+ sage: g = graphs.HammingGraph(2, 3, X)
467
+ Traceback (most recent call last):
468
+ ...
469
+ ValueError: q must be the cardinality of X
470
+
471
+ REFERENCES:
472
+
473
+ For a more accurate description, see the following wikipedia page:
474
+ :wikipedia:`Hamming_graph`
475
+ """
476
+ from itertools import product, repeat
477
+ if not X:
478
+ X = list(range(q))
479
+ if q != len(X):
480
+ raise ValueError("q must be the cardinality of X")
481
+ g = Graph(name="Hamming Graph with parameters " + str(n) + "," + str(q), multiedges=False)
482
+ g.add_vertices(product(*repeat(X, n)))
483
+ for v in g:
484
+ for i in range(n):
485
+ prefix = v[:i]
486
+ suffix = v[i+1:]
487
+ for el in X:
488
+ if el == v[i]:
489
+ continue
490
+ u = prefix + (el,) + suffix
491
+ g.add_edge(v, u)
492
+ return g
493
+
494
+
495
+ def BalancedTree(r, h):
496
+ r"""
497
+ Return the perfectly balanced tree of height `h \geq 1`,
498
+ whose root has degree `r \geq 2`.
499
+
500
+ The number of vertices of this graph is
501
+ `1 + r + r^2 + \cdots + r^h`, that is,
502
+ `\frac{r^{h+1} - 1}{r - 1}`. The number of edges is one
503
+ less than the number of vertices.
504
+
505
+ INPUT:
506
+
507
+ - ``r`` -- positive integer `\geq 2`; the degree of the root node
508
+
509
+ - ``h`` -- positive integer `\geq 1`; the height of the balanced tree
510
+
511
+ OUTPUT:
512
+
513
+ The perfectly balanced tree of height `h \geq 1` and whose root has
514
+ degree `r \geq 2`.
515
+
516
+ EXAMPLES:
517
+
518
+ A balanced tree whose root node has degree `r = 2`, and of height
519
+ `h = 1`, has order 3 and size 2::
520
+
521
+ sage: G = graphs.BalancedTree(2, 1); G
522
+ Balanced tree: Graph on 3 vertices
523
+ sage: G.order()
524
+ 3
525
+ sage: G.size()
526
+ 2
527
+ sage: r = 2; h = 1
528
+ sage: v = 1 + r
529
+ sage: v; v - 1
530
+ 3
531
+ 2
532
+
533
+ Plot a balanced tree of height 5, whose root node has degree `r = 3`::
534
+
535
+ sage: G = graphs.BalancedTree(3, 5)
536
+ sage: G.plot() # long time # needs sage.plot
537
+ Graphics object consisting of 728 graphics primitives
538
+
539
+ A tree is bipartite. If its vertex set is finite, then it is planar. ::
540
+
541
+ sage: # needs networkx
542
+ sage: r = randint(2, 5); h = randint(1, 7)
543
+ sage: T = graphs.BalancedTree(r, h)
544
+ sage: T.is_bipartite()
545
+ True
546
+ sage: T.is_planar() # needs planarity
547
+ True
548
+ sage: v = (r^(h + 1) - 1) / (r - 1)
549
+ sage: T.order() == v
550
+ True
551
+ sage: T.size() == v - 1
552
+ True
553
+
554
+ TESTS:
555
+
556
+ Normally we would only consider balanced trees whose root node
557
+ has degree `r \geq 2`, but the construction degenerates
558
+ gracefully::
559
+
560
+ sage: graphs.BalancedTree(1, 10)
561
+ Balanced tree: Graph on 11 vertices
562
+
563
+ Similarly, we usually want the tree must have height `h \geq 1`
564
+ but the algorithm also degenerates gracefully here::
565
+
566
+ sage: graphs.BalancedTree(3, 0)
567
+ Balanced tree: Graph on 1 vertex
568
+
569
+ The construction is the same as the one of networkx::
570
+
571
+ sage: # needs networkx
572
+ sage: import networkx
573
+ sage: r = randint(2, 4); h = randint(1, 5)
574
+ sage: T = graphs.BalancedTree(r, h)
575
+ sage: N = Graph(networkx.balanced_tree(r, h), name="Balanced tree")
576
+ sage: T.is_isomorphic(N)
577
+ True
578
+ """
579
+ # Compute the number of vertices per level of the tree
580
+ order = [r**l for l in range(h + 1)]
581
+ # Compute the first index of the vertices of a level
582
+ begin = [0]
583
+ begin.extend(begin[-1] + val for val in order)
584
+ # The number of vertices of the tree is the first index of level h + 1
585
+ T = Graph(begin[-1], name="Balanced tree")
586
+
587
+ # Add edges of the r-ary tree
588
+ for level in range(h):
589
+ start = begin[level + 1]
590
+ for u in range(begin[level], begin[level + 1]):
591
+ T.add_edges((u, v) for v in range(start, start + r))
592
+ start += r
593
+ return T
594
+
595
+
596
+ def BarbellGraph(n1, n2):
597
+ r"""
598
+ Return a barbell graph with `2 n_1 + n_2` nodes.
599
+
600
+ The argument `n_1` must be greater than or equal to 2.
601
+
602
+ A barbell graph is a basic structure that consists of a path graph
603
+ of order `n_2` connecting two complete graphs of order `n_1` each.
604
+
605
+ INPUT:
606
+
607
+ - ``n1`` -- integer `\geq 2`; the order of each of the two
608
+ complete graphs
609
+
610
+ - ``n2`` -- nonnegative integer; the order of the path graph
611
+ connecting the two complete graphs
612
+
613
+ OUTPUT:
614
+
615
+ A barbell graph of order `2*n_1 + n_2`. A :exc:`ValueError` is
616
+ returned if `n_1 < 2` or `n_2 < 0`.
617
+
618
+ PLOTTING:
619
+
620
+ Upon construction, the position dictionary is filled to
621
+ override the spring-layout algorithm. By convention, each barbell
622
+ graph will be displayed with the two complete graphs in the
623
+ lower-left and upper-right corners, with the path graph connecting
624
+ diagonally between the two. Thus the `n_1`-th node will be drawn at a
625
+ 45 degree angle from the horizontal right center of the first
626
+ complete graph, and the `n_1 + n_2 + 1`-th node will be drawn 45
627
+ degrees below the left horizontal center of the second complete graph.
628
+
629
+ EXAMPLES:
630
+
631
+ Construct and show a barbell graph ``Bar = 4``, ``Bells = 9``::
632
+
633
+ sage: g = graphs.BarbellGraph(9, 4); g
634
+ Barbell graph: Graph on 22 vertices
635
+ sage: g.show() # long time # needs sage.plot
636
+
637
+ An `n_1 \geq 2`, `n_2 \geq 0` barbell graph has order `2*n_1 + n_2`. It
638
+ has the complete graph on `n_1` vertices as a subgraph. It also has
639
+ the path graph on `n_2` vertices as a subgraph. ::
640
+
641
+ sage: n1 = randint(2, 2*10^2)
642
+ sage: n2 = randint(0, 2*10^2)
643
+ sage: g = graphs.BarbellGraph(n1, n2)
644
+ sage: v = 2*n1 + n2
645
+ sage: g.order() == v
646
+ True
647
+ sage: K_n1 = graphs.CompleteGraph(n1)
648
+ sage: P_n2 = graphs.PathGraph(n2)
649
+
650
+ sage: # needs sage.modules
651
+ sage: s_K = g.subgraph_search(K_n1, induced=True)
652
+ sage: s_P = g.subgraph_search(P_n2, induced=True)
653
+ sage: K_n1.is_isomorphic(s_K)
654
+ True
655
+ sage: P_n2.is_isomorphic(s_P)
656
+ True
657
+
658
+ TESTS::
659
+
660
+ sage: n1, n2 = randint(3, 10), randint(0, 10)
661
+ sage: g = graphs.BarbellGraph(n1, n2)
662
+ sage: g.num_verts() == 2 * n1 + n2
663
+ True
664
+ sage: g.num_edges() == 2 * binomial(n1, 2) + n2 + 1 # needs sage.symbolic
665
+ True
666
+ sage: g.is_connected()
667
+ True
668
+ sage: g.girth() == 3
669
+ True
670
+
671
+ The input `n_1` must be `\geq 2`::
672
+
673
+ sage: graphs.BarbellGraph(1, randint(0, 10^6))
674
+ Traceback (most recent call last):
675
+ ...
676
+ ValueError: invalid graph description, n1 should be >= 2
677
+ sage: graphs.BarbellGraph(randint(-10^6, 1), randint(0, 10^6))
678
+ Traceback (most recent call last):
679
+ ...
680
+ ValueError: invalid graph description, n1 should be >= 2
681
+
682
+ The input `n_2` must be `\geq 0`::
683
+
684
+ sage: graphs.BarbellGraph(randint(2, 10^6), -1)
685
+ Traceback (most recent call last):
686
+ ...
687
+ ValueError: invalid graph description, n2 should be >= 0
688
+ sage: graphs.BarbellGraph(randint(2, 10^6), randint(-10^6, -1))
689
+ Traceback (most recent call last):
690
+ ...
691
+ ValueError: invalid graph description, n2 should be >= 0
692
+ sage: graphs.BarbellGraph(randint(-10^6, 1), randint(-10^6, -1))
693
+ Traceback (most recent call last):
694
+ ...
695
+ ValueError: invalid graph description, n1 should be >= 2
696
+ """
697
+ # sanity checks
698
+ if n1 < 2:
699
+ raise ValueError("invalid graph description, n1 should be >= 2")
700
+ if n2 < 0:
701
+ raise ValueError("invalid graph description, n2 should be >= 0")
702
+
703
+ G = Graph(name="Barbell graph")
704
+ G.add_clique(list(range(n1)))
705
+ G.add_path(list(range(n1 - 1, n1 + n2 + 1)))
706
+ G.add_clique(list(range(n1 + n2, n1 + n2 + n1)))
707
+
708
+ G._circle_embedding(list(range(n1)), shift=1, angle=pi/4)
709
+ G._line_embedding(list(range(n1, n1 + n2)), first=(2, 2), last=(n2 + 1, n2 + 1))
710
+ G._circle_embedding(list(range(n1 + n2, n1 + n2 + n1)), center=(n2 + 3, n2 + 3), angle=5*pi/4)
711
+ return G
712
+
713
+
714
+ def LollipopGraph(n1, n2):
715
+ r"""
716
+ Return a lollipop graph with `n_1 + n_2` nodes.
717
+
718
+ A lollipop graph is a path graph (order `n_2`) connected to a complete
719
+ graph (order `n_1`). (A barbell graph minus one of the bells).
720
+
721
+ PLOTTING: Upon construction, the position dictionary is filled to
722
+ override the spring-layout algorithm. By convention, the complete
723
+ graph will be drawn in the lower-left corner with the `n_1`-th node
724
+ at a 45 degree angle above the right horizontal center of the
725
+ complete graph, leading directly into the path graph.
726
+
727
+ EXAMPLES:
728
+
729
+ Construct and show a lollipop graph Candy = 13, Stick = 4::
730
+
731
+ sage: g = graphs.LollipopGraph(13,4); g
732
+ Lollipop graph: Graph on 17 vertices
733
+ sage: g.show() # long time # needs sage.plot
734
+
735
+ TESTS::
736
+
737
+ sage: n1, n2 = randint(3, 10), randint(0, 10)
738
+ sage: g = graphs.LollipopGraph(n1, n2)
739
+ sage: g.num_verts() == n1 + n2
740
+ True
741
+ sage: g.num_edges() == binomial(n1, 2) + n2 # needs sage.symbolic
742
+ True
743
+ sage: g.is_connected()
744
+ True
745
+ sage: g.girth() == 3
746
+ True
747
+ sage: graphs.LollipopGraph(n1, 0).is_isomorphic(graphs.CompleteGraph(n1))
748
+ True
749
+ sage: graphs.LollipopGraph(0, n2).is_isomorphic(graphs.PathGraph(n2))
750
+ True
751
+ sage: graphs.LollipopGraph(0, 0).is_isomorphic(graphs.EmptyGraph())
752
+ True
753
+
754
+ The input `n_1` must be `\geq 0`::
755
+
756
+ sage: graphs.LollipopGraph(-1, randint(0, 10^6))
757
+ Traceback (most recent call last):
758
+ ...
759
+ ValueError: invalid graph description, n1 should be >= 0
760
+
761
+ The input `n_2` must be `\geq 0`::
762
+
763
+ sage: graphs.LollipopGraph(randint(2, 10^6), -1)
764
+ Traceback (most recent call last):
765
+ ...
766
+ ValueError: invalid graph description, n2 should be >= 0
767
+ """
768
+ # sanity checks
769
+ if n1 < 0:
770
+ raise ValueError("invalid graph description, n1 should be >= 0")
771
+ if n2 < 0:
772
+ raise ValueError("invalid graph description, n2 should be >= 0")
773
+
774
+ G = Graph(n1 + n2, name="Lollipop graph")
775
+ G.add_clique(list(range(n1)))
776
+ G.add_path(list(range(n1, n1 + n2)))
777
+ if n1 * n2 > 0:
778
+ G.add_edge(n1 - 1, n1)
779
+ if n1 == 1:
780
+ G.set_pos({0: (0, 0)})
781
+ else:
782
+ G._circle_embedding(list(range(n1)), shift=1, angle=pi/4)
783
+ G._line_embedding(list(range(n1, n1 + n2)), first=(2, 2), last=(n2 + 1, n2 + 1))
784
+ return G
785
+
786
+
787
+ def TadpoleGraph(n1, n2):
788
+ r"""
789
+ Return a tadpole graph with `n_1 + n_2` nodes.
790
+
791
+ A tadpole graph is a path graph (order `n_2`) connected to a cycle graph
792
+ (order `n_1`).
793
+
794
+ PLOTTING: Upon construction, the position dictionary is filled to override
795
+ the spring-layout algorithm. By convention, the cycle graph will be drawn
796
+ in the lower-left corner with the `n_1`-th node at a 45 degree angle above
797
+ the right horizontal center of the cycle graph, leading directly into the
798
+ path graph.
799
+
800
+ EXAMPLES:
801
+
802
+ Construct and show a tadpole graph Cycle = 13, Stick = 4::
803
+
804
+ sage: g = graphs.TadpoleGraph(13, 4); g
805
+ Tadpole graph: Graph on 17 vertices
806
+ sage: g.show() # long time # needs sage.plot
807
+
808
+ TESTS::
809
+
810
+ sage: n1, n2 = randint(3, 10), randint(0, 10)
811
+ sage: g = graphs.TadpoleGraph(n1, n2)
812
+ sage: g.num_verts() == n1 + n2
813
+ True
814
+ sage: g.num_edges() == n1 + n2
815
+ True
816
+ sage: g.girth() == n1
817
+ True
818
+ sage: graphs.TadpoleGraph(n1, 0).is_isomorphic(graphs.CycleGraph(n1))
819
+ True
820
+
821
+ The input `n_1` must be `\geq 3`::
822
+
823
+ sage: graphs.TadpoleGraph(2, randint(0, 10^6))
824
+ Traceback (most recent call last):
825
+ ...
826
+ ValueError: invalid graph description, n1 should be >= 3
827
+
828
+ The input `n_2` must be `\geq 0`::
829
+
830
+ sage: graphs.TadpoleGraph(randint(2, 10^6), -1)
831
+ Traceback (most recent call last):
832
+ ...
833
+ ValueError: invalid graph description, n2 should be >= 0
834
+ """
835
+ # sanity checks
836
+ if n1 < 3:
837
+ raise ValueError("invalid graph description, n1 should be >= 3")
838
+ if n2 < 0:
839
+ raise ValueError("invalid graph description, n2 should be >= 0")
840
+
841
+ G = Graph(n1 + n2, name="Tadpole graph")
842
+ G.add_cycle(list(range(n1)))
843
+ G.add_path(list(range(n1, n1 + n2)))
844
+ if n1 * n2 > 0:
845
+ G.add_edge(n1 - 1, n1)
846
+ G._circle_embedding(list(range(n1)), shift=1, angle=pi/4)
847
+ G._line_embedding(list(range(n1, n1 + n2)), first=(2, 2), last=(n2 + 1, n2 + 1))
848
+ return G
849
+
850
+
851
+ def AztecDiamondGraph(n):
852
+ """
853
+ Return the Aztec Diamond graph of order ``n``.
854
+
855
+ See the :wikipedia:`Aztec_diamond` for more information.
856
+
857
+ EXAMPLES::
858
+
859
+ sage: graphs.AztecDiamondGraph(2)
860
+ Aztec Diamond graph of order 2
861
+
862
+ sage: [graphs.AztecDiamondGraph(i).num_verts() for i in range(8)]
863
+ [0, 4, 12, 24, 40, 60, 84, 112]
864
+
865
+ sage: [graphs.AztecDiamondGraph(i).num_edges() for i in range(8)]
866
+ [0, 4, 16, 36, 64, 100, 144, 196]
867
+
868
+ sage: G = graphs.AztecDiamondGraph(3)
869
+ sage: sum(1 for p in G.perfect_matchings())
870
+ 64
871
+ """
872
+ from sage.graphs.generators.basic import Grid2dGraph
873
+ if n:
874
+ N = 2 * n
875
+ G = Grid2dGraph(N, N)
876
+ H = G.subgraph([(i, j) for i in range(N) for j in range(N)
877
+ if i - n <= j <= n + i and
878
+ n - 1 - i <= j <= 3 * n - i - 1])
879
+ else:
880
+ H = Graph()
881
+ H.rename('Aztec Diamond graph of order {}'.format(n))
882
+ return H
883
+
884
+
885
+ def DipoleGraph(n):
886
+ r"""
887
+ Return a dipole graph with `n` edges.
888
+
889
+ A dipole graph is a multigraph consisting of 2 vertices connected with `n`
890
+ parallel edges.
891
+
892
+ EXAMPLES:
893
+
894
+ Construct and show a dipole graph with 13 edges::
895
+
896
+ sage: g = graphs.DipoleGraph(13); g
897
+ Dipole graph: Multi-graph on 2 vertices
898
+ sage: g.show() # long time # needs sage.plot
899
+
900
+ TESTS::
901
+
902
+ sage: n = randint(0, 10)
903
+ sage: g = graphs.DipoleGraph(n)
904
+ sage: g.num_verts() == 2
905
+ True
906
+ sage: g.num_edges() == n
907
+ True
908
+ sage: g.is_connected() == (n > 0)
909
+ True
910
+ sage: g.diameter() == (1 if n > 0 else infinity)
911
+ True
912
+
913
+ The input ``n`` must be `\geq 0`::
914
+
915
+ sage: graphs.DipoleGraph(-randint(1, 10))
916
+ Traceback (most recent call last):
917
+ ...
918
+ ValueError: invalid graph description, n should be >= 0
919
+ """
920
+ # sanity checks
921
+ if n < 0:
922
+ raise ValueError("invalid graph description, n should be >= 0")
923
+
924
+ return Graph([[0, 1], [(0, 1)]*n], name="Dipole graph", multiedges=True)
925
+
926
+
927
+ def BubbleSortGraph(n):
928
+ r"""
929
+ Return the bubble sort graph `B(n)`.
930
+
931
+ The vertices of the bubble sort graph are the set of permutations
932
+ on `n` symbols. Two vertices are adjacent if one can be obtained
933
+ from the other by swapping the labels in the `i`-th and `(i+1)`-th
934
+ positions for `1 \leq i \leq n-1`. In total, `B(n)` has order
935
+ `n!`. Swapping two labels as described previously corresponds to
936
+ multiplying on the right the permutation corresponding to the node
937
+ by an elementary transposition in the
938
+ :class:`~sage.groups.perm_gps.permgroup_named.SymmetricGroup`.
939
+
940
+ The bubble sort graph is the underlying graph of the
941
+ :meth:`~sage.geometry.polyhedron.library.Polytopes.permutahedron`.
942
+
943
+ INPUT:
944
+
945
+ - ``n`` -- positive integer. The number of symbols to permute
946
+
947
+ OUTPUT:
948
+
949
+ The bubble sort graph `B(n)` on `n` symbols. If `n < 1`, a
950
+ :exc:`ValueError` is returned.
951
+
952
+ EXAMPLES::
953
+
954
+ sage: g = graphs.BubbleSortGraph(4); g
955
+ Bubble sort: Graph on 24 vertices
956
+ sage: g.plot() # long time # needs sage.plot
957
+ Graphics object consisting of 61 graphics primitives
958
+
959
+ The bubble sort graph on `n = 1` symbol is the trivial graph `K_1`::
960
+
961
+ sage: graphs.BubbleSortGraph(1)
962
+ Bubble sort: Graph on 1 vertex
963
+
964
+ If `n \geq 1`, then the order of `B(n)` is `n!`::
965
+
966
+ sage: n = randint(1, 8)
967
+ sage: g = graphs.BubbleSortGraph(n)
968
+ sage: g.order() == factorial(n)
969
+ True
970
+
971
+ .. SEEALSO::
972
+
973
+ * :meth:`~sage.geometry.polyhedron.library.Polytopes.permutahedron`
974
+
975
+ TESTS:
976
+
977
+ Input ``n`` must be positive::
978
+
979
+ sage: graphs.BubbleSortGraph(0)
980
+ Traceback (most recent call last):
981
+ ...
982
+ ValueError: Invalid number of symbols to permute, n should be >= 1
983
+ sage: graphs.BubbleSortGraph(randint(-10^6, 0))
984
+ Traceback (most recent call last):
985
+ ...
986
+ ValueError: Invalid number of symbols to permute, n should be >= 1
987
+
988
+ AUTHORS:
989
+
990
+ - Michael Yurko (2009-09-01)
991
+ """
992
+ # sanity checks
993
+ if n < 1:
994
+ raise ValueError(
995
+ "Invalid number of symbols to permute, n should be >= 1")
996
+ if n == 1:
997
+ from sage.graphs.generators.basic import CompleteGraph
998
+ return Graph(CompleteGraph(n), name="Bubble sort")
999
+ from sage.combinat.permutation import Permutations
1000
+ # create set from which to permute
1001
+ label_set = [str(i) for i in range(1, n + 1)]
1002
+ d = {}
1003
+ # iterate through all vertices
1004
+ for v in Permutations(label_set):
1005
+ v = list(v) # So we can easily mutate it
1006
+ tmp_dict = {}
1007
+ # add all adjacencies
1008
+ for i in range(n - 1):
1009
+ # swap entries
1010
+ v[i], v[i + 1] = v[i + 1], v[i]
1011
+ # add new vertex
1012
+ new_vert = ''.join(v)
1013
+ tmp_dict[new_vert] = None
1014
+ # swap back
1015
+ v[i], v[i + 1] = v[i + 1], v[i]
1016
+ # add adjacency dict
1017
+ d[''.join(v)] = tmp_dict
1018
+ return Graph(d, name="Bubble sort")
1019
+
1020
+
1021
+ def chang_graphs():
1022
+ r"""
1023
+ Return the three Chang graphs.
1024
+
1025
+ Three of the four strongly regular graphs of parameters `(28,12,6,4)` are
1026
+ called the Chang graphs. The fourth is the line graph of `K_8`. For more
1027
+ information about the Chang graphs, see the :wikipedia:`Chang_graphs` or
1028
+ https://www.win.tue.nl/~aeb/graphs/Chang.html.
1029
+
1030
+ EXAMPLES: check that we get 4 non-isomorphic s.r.g.'s with the
1031
+ same parameters::
1032
+
1033
+ sage: chang_graphs = graphs.chang_graphs()
1034
+ sage: K8 = graphs.CompleteGraph(8)
1035
+ sage: T8 = K8.line_graph()
1036
+ sage: four_srg = chang_graphs + [T8]
1037
+ sage: for g in four_srg:
1038
+ ....: print(g.is_strongly_regular(parameters=True))
1039
+ (28, 12, 6, 4)
1040
+ (28, 12, 6, 4)
1041
+ (28, 12, 6, 4)
1042
+ (28, 12, 6, 4)
1043
+ sage: from itertools import combinations
1044
+ sage: for g1,g2 in combinations(four_srg,2):
1045
+ ....: assert not g1.is_isomorphic(g2)
1046
+
1047
+ Construct the Chang graphs by Seidel switching::
1048
+
1049
+ sage: c3c5 = graphs.CycleGraph(3).disjoint_union(graphs.CycleGraph(5))
1050
+ sage: c8 = graphs.CycleGraph(8)
1051
+ sage: s = [K8.subgraph_search(c8).edges(sort=False), # needs sage.modules
1052
+ ....: [(0,1,None),(2,3,None),(4,5,None),(6,7,None)],
1053
+ ....: K8.subgraph_search(c3c5).edges(sort=False)]
1054
+ sage: [T8.seidel_switching(x, inplace=False).is_isomorphic(G) # needs sage.modules
1055
+ ....: for x, G in zip(s, chang_graphs)]
1056
+ [True, True, True]
1057
+ """
1058
+ g1 = Graph("[}~~EebhkrRb_~SoLOIiAZ?LBBxDb?bQcggjHKEwoZFAaiZ?Yf[?dxb@@tdWGkwn",
1059
+ loops=False, multiedges=False)
1060
+ g2 = Graph("[~z^UipkkZPr_~Y_LOIiATOLBBxPR@`acoojBBSoWXTaabN?Yts?Yji_QyioClXZ",
1061
+ loops=False, multiedges=False)
1062
+ g3 = Graph(r"[~~vVMWdKFpV`^UGIaIERQ`\DBxpA@g`CbGRI`AxICNaFM[?fM\?Ytj@CxrGGlYt",
1063
+ loops=False, multiedges=False)
1064
+ return [g1, g2, g3]
1065
+
1066
+
1067
+ def CirculantGraph(n, adjacency):
1068
+ r"""
1069
+ Return a circulant graph with `n` nodes.
1070
+
1071
+ A circulant graph has the property that the vertex `i` is connected
1072
+ with the vertices `i+j` and `i-j` for each `j` in ``adjacency``.
1073
+
1074
+ INPUT:
1075
+
1076
+ - ``n`` -- number of vertices in the graph
1077
+
1078
+ - ``adjacency`` -- the list of `j` values
1079
+
1080
+ PLOTTING: Upon construction, the position dictionary is filled to
1081
+ override the spring-layout algorithm. By convention, each circulant
1082
+ graph will be displayed with the first (0) node at the top, with
1083
+ the rest following in a counterclockwise manner.
1084
+
1085
+ Filling the position dictionary in advance adds `O(n)` to the
1086
+ constructor.
1087
+
1088
+ .. SEEALSO::
1089
+
1090
+ * :meth:`~sage.graphs.generic_graph.GenericGraph.is_circulant`
1091
+ -- checks whether a (di)graph is circulant, and/or returns
1092
+ all possible sets of parameters.
1093
+
1094
+ EXAMPLES: Compare plotting using the predefined layout and
1095
+ networkx::
1096
+
1097
+ sage: # needs networkx
1098
+ sage: import networkx
1099
+ sage: n = networkx.cycle_graph(23)
1100
+ sage: spring23 = Graph(n)
1101
+ sage: posdict23 = graphs.CirculantGraph(23,2)
1102
+ sage: spring23.show() # long time
1103
+ sage: posdict23.show() # long time
1104
+
1105
+ We next view many cycle graphs as a Sage graphics array. First we
1106
+ use the ``CirculantGraph`` constructor, which fills in
1107
+ the position dictionary::
1108
+
1109
+ sage: # needs sage.plot
1110
+ sage: g = []
1111
+ sage: j = []
1112
+ sage: for i in range(9):
1113
+ ....: k = graphs.CirculantGraph(i+4, i+1)
1114
+ ....: g.append(k)
1115
+ sage: for i in range(3):
1116
+ ....: n = []
1117
+ ....: for m in range(3):
1118
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
1119
+ ....: j.append(n)
1120
+ sage: G = graphics_array(j)
1121
+ sage: G.show() # long time
1122
+
1123
+ Compare to plotting with the spring-layout algorithm::
1124
+
1125
+ sage: # needs networkx sage.plot
1126
+ sage: g = []
1127
+ sage: j = []
1128
+ sage: for i in range(9):
1129
+ ....: spr = networkx.cycle_graph(i+3)
1130
+ ....: k = Graph(spr)
1131
+ ....: g.append(k)
1132
+ sage: for i in range(3):
1133
+ ....: n = []
1134
+ ....: for m in range(3):
1135
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
1136
+ ....: j.append(n)
1137
+ sage: G = graphics_array(j)
1138
+ sage: G.show() # long time
1139
+
1140
+ Passing a 1 into adjacency should give the cycle.
1141
+
1142
+ ::
1143
+
1144
+ sage: graphs.CirculantGraph(6,1) == graphs.CycleGraph(6)
1145
+ True
1146
+ sage: graphs.CirculantGraph(7,[1,3]).edges(sort=True, labels=false)
1147
+ [(0, 1),
1148
+ (0, 3),
1149
+ (0, 4),
1150
+ (0, 6),
1151
+ (1, 2),
1152
+ (1, 4),
1153
+ (1, 5),
1154
+ (2, 3),
1155
+ (2, 5),
1156
+ (2, 6),
1157
+ (3, 4),
1158
+ (3, 6),
1159
+ (4, 5),
1160
+ (5, 6)]
1161
+ """
1162
+ if not isinstance(adjacency, list):
1163
+ adjacency = [adjacency]
1164
+
1165
+ G = Graph(n, name="Circulant graph (" + str(adjacency) + ")")
1166
+ G._circle_embedding(list(range(n)))
1167
+
1168
+ for v in G:
1169
+ G.add_edges([(v, (v + j) % n) for j in adjacency])
1170
+
1171
+ return G
1172
+
1173
+
1174
+ def CubeGraph(n, embedding=1):
1175
+ r"""
1176
+ Return the `n`-cube graph, also called the hypercube in `n` dimensions.
1177
+
1178
+ The hypercube in `n` dimension is build upon the binary strings on `n` bits,
1179
+ two of them being adjacent if they differ in exactly one bit. Hence, the
1180
+ distance between two vertices in the hypercube is the Hamming distance.
1181
+
1182
+ INPUT:
1183
+
1184
+ - ``n`` -- integer; the dimension of the cube graph
1185
+
1186
+ - ``embedding`` -- integer (default: `1`); two embeddings of the `n`-cube
1187
+ are available:
1188
+
1189
+ - ``1`` -- the `n`-cube is projected inside a regular `2n`-gonal polygon by
1190
+ a skew orthogonal projection. See the :wikipedia:`Hypercube` for more
1191
+ details.
1192
+
1193
+ - ``2`` -- orthogonal projection of the `n`-cube. This orientation shows
1194
+ columns of independent vertices such that the neighbors of a vertex are
1195
+ located in the columns on the left and on the right. The number of
1196
+ vertices in each column represents rows in Pascal's triangle. See for
1197
+ instance the :wikipedia:`10-cube` for more details.
1198
+
1199
+ - ``3`` -- oblique projection of the `n`-cube. Oblique projection involves
1200
+ aligning one face parallel to the viewer and projecting at a specified
1201
+ angle, maintaining equal size for edges parallel to one axis while
1202
+ applying fixed foreshortening to others. This method simplifies the
1203
+ representation of a four-dimensional hypercube onto a two-dimensional
1204
+ plane, offering a geometrically consistent visualization.
1205
+
1206
+ - ``None`` or ``O`` -- no embedding is provided
1207
+
1208
+ EXAMPLES:
1209
+
1210
+ The distance between `0100110` and `1011010` is `5`, as expected::
1211
+
1212
+ sage: g = graphs.CubeGraph(7)
1213
+ sage: g.distance('0100110','1011010')
1214
+ 5
1215
+
1216
+ Plot several `n`-cubes in a Sage Graphics Array::
1217
+
1218
+ sage: # needs sage.plot
1219
+ sage: g = []
1220
+ sage: j = []
1221
+ sage: for i in range(6):
1222
+ ....: k = graphs.CubeGraph(i+1)
1223
+ ....: g.append(k)
1224
+ ...
1225
+ sage: for i in range(2):
1226
+ ....: n = []
1227
+ ....: for m in range(3):
1228
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
1229
+ ....: j.append(n)
1230
+ ...
1231
+ sage: G = graphics_array(j)
1232
+ sage: G.show(figsize=[6,4]) # long time
1233
+
1234
+ Use the plot options to display larger `n`-cubes::
1235
+
1236
+ sage: g = graphs.CubeGraph(9, embedding=1)
1237
+ sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time, needs sage.plot
1238
+ sage: g = graphs.CubeGraph(9, embedding=2)
1239
+ sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time, needs sage.plot
1240
+ sage: g = graphs.CubeGraph(9, embedding=3)
1241
+ sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time, needs sage.plot
1242
+
1243
+ AUTHORS:
1244
+
1245
+ - Robert Miller
1246
+ - David Coudert
1247
+ """
1248
+ if embedding == 1 or embedding == 3:
1249
+ # construct recursively the adjacency dict and the embedding
1250
+ theta = float(pi/n)
1251
+ if embedding == 3 and n > 2:
1252
+ theta = float(pi/(2*n-2))
1253
+
1254
+ d = {'': []}
1255
+ dn = {}
1256
+ p = {'': (float(0), float(0))}
1257
+ pn = {}
1258
+
1259
+ for i in range(n):
1260
+ ci = float(cos(i*theta))
1261
+ si = float(sin(i*theta))
1262
+ for v, e in d.items():
1263
+ v0 = v + '0'
1264
+ v1 = v + '1'
1265
+ l0 = [v1]
1266
+ l1 = [v0]
1267
+ for m in e:
1268
+ l0.append(m + '0')
1269
+ l1.append(m + '1')
1270
+ dn[v0] = l0
1271
+ dn[v1] = l1
1272
+ x, y = p[v]
1273
+ pn[v0] = (x, y)
1274
+ pn[v1] = (x + ci, y + si)
1275
+ d, dn = dn, {}
1276
+ p, pn = pn, {}
1277
+
1278
+ # construct the graph
1279
+ G = Graph(d, format='dict_of_lists', pos=p, name="%d-Cube" % n)
1280
+
1281
+ else:
1282
+ # construct recursively the adjacency dict
1283
+ d = {'': []}
1284
+ dn = {}
1285
+
1286
+ for i in range(n):
1287
+ for v, e in d.items():
1288
+ v0 = v + '0'
1289
+ v1 = v + '1'
1290
+ l0 = [v1]
1291
+ l1 = [v0]
1292
+ for m in e:
1293
+ l0.append(m + '0')
1294
+ l1.append(m + '1')
1295
+ dn[v0] = l0
1296
+ dn[v1] = l1
1297
+ d, dn = dn, {}
1298
+
1299
+ # construct the graph
1300
+ G = Graph(d, name="%d-Cube" % n, format='dict_of_lists')
1301
+
1302
+ if embedding == 2:
1303
+ # Orthogonal projection
1304
+ s = '0'*n
1305
+ L = [[] for _ in range(n + 1)]
1306
+ for u, d in G.breadth_first_search(s, report_distance=True):
1307
+ L[d].append(u)
1308
+
1309
+ p = G._circle_embedding(list(range(2*n)), radius=(n + 1)//2, angle=pi, return_dict=True)
1310
+ for i in range(n + 1):
1311
+ y = p[i][1] / 1.5
1312
+ G._line_embedding(L[i], first=(i, y), last=(i, -y), return_dict=False)
1313
+
1314
+ return G
1315
+
1316
+
1317
+ def GoethalsSeidelGraph(k, r):
1318
+ r"""
1319
+ Return the graph `\text{Goethals-Seidel}(k,r)`.
1320
+
1321
+ The graph `\text{Goethals-Seidel}(k,r)` comes from a construction presented
1322
+ in Theorem 2.4 of [GS1970]_. It relies on a :func:`(v,k)-BIBD
1323
+ <sage.combinat.designs.bibd.balanced_incomplete_block_design>` with `r`
1324
+ blocks and a
1325
+ :func:`~sage.combinat.matrices.hadamard_matrix.hadamard_matrix` of order
1326
+ `r+1`. The result is a
1327
+ :func:`sage.graphs.strongly_regular_db.strongly_regular_graph` on `v(r+1)`
1328
+ vertices with degree `k=(n+r-1)/2`.
1329
+
1330
+ It appears under this name in Andries Brouwer's `database of strongly
1331
+ regular graphs <https://www.win.tue.nl/~aeb/graphs/srg/srgtab.html>`__.
1332
+
1333
+ INPUT:
1334
+
1335
+ - ``k``, ``r`` -- integers
1336
+
1337
+ .. SEEALSO::
1338
+
1339
+ - :func:`~sage.graphs.strongly_regular_db.is_goethals_seidel`
1340
+
1341
+ EXAMPLES::
1342
+
1343
+ sage: graphs.GoethalsSeidelGraph(3,3) # needs sage.combinat sage.modules
1344
+ Graph on 28 vertices
1345
+ sage: graphs.GoethalsSeidelGraph(3,3).is_strongly_regular(parameters=True) # needs sage.combinat sage.modules
1346
+ (28, 15, 6, 10)
1347
+ """
1348
+ from sage.combinat.designs.bibd import balanced_incomplete_block_design
1349
+ from sage.combinat.matrices.hadamard_matrix import hadamard_matrix
1350
+ from sage.matrix.constructor import Matrix
1351
+ from sage.matrix.constructor import block_matrix
1352
+
1353
+ v = (k-1)*r + 1
1354
+ n = v*(r + 1)
1355
+
1356
+ # N is the (v times b) incidence matrix of a bibd
1357
+ N = balanced_incomplete_block_design(v, k).incidence_matrix()
1358
+
1359
+ # L is a (r+1 times r) matrix, where r is the row sum of N
1360
+ L = hadamard_matrix(r + 1).submatrix(0, 1)
1361
+ L = [Matrix(C).transpose() for C in L.columns()]
1362
+ zero = Matrix(r + 1, 1, [0]*(r + 1))
1363
+
1364
+ # For every row of N, we replace the 0s with a column of zeros, and we
1365
+ # replace the ith 1 with the ith column of L. The result is P.
1366
+ P = []
1367
+ for row in N:
1368
+ Ltmp = L[:]
1369
+ P.append([Ltmp.pop(0) if i else zero
1370
+ for i in row])
1371
+
1372
+ P = block_matrix(P)
1373
+
1374
+ # The final graph
1375
+ PP = P*P.transpose()
1376
+ for i in range(n):
1377
+ PP[i, i] = 0
1378
+
1379
+ G = Graph(PP, format='seidel_adjacency_matrix')
1380
+ return G
1381
+
1382
+
1383
+ def DorogovtsevGoltsevMendesGraph(n):
1384
+ """
1385
+ Construct the `n`-th generation of the Dorogovtsev-Goltsev-Mendes
1386
+ graph.
1387
+
1388
+ EXAMPLES::
1389
+
1390
+ sage: G = graphs.DorogovtsevGoltsevMendesGraph(8) # needs networkx
1391
+ sage: G.size() # needs networkx
1392
+ 6561
1393
+
1394
+ REFERENCE:
1395
+
1396
+ - [1] Dorogovtsev, S. N., Goltsev, A. V., and Mendes, J.
1397
+ F. F., Pseudofractal scale-free web, Phys. Rev. E 066122
1398
+ (2002).
1399
+ """
1400
+ import networkx
1401
+ return Graph(networkx.dorogovtsev_goltsev_mendes_graph(n),
1402
+ name="Dorogovtsev-Goltsev-Mendes Graph, %d-th generation" % n)
1403
+
1404
+
1405
+ def FoldedCubeGraph(n):
1406
+ r"""
1407
+ Return the folded cube graph of order `2^{n-1}`.
1408
+
1409
+ The folded cube graph on `2^{n-1}` vertices can be obtained from a cube
1410
+ graph on `2^n` vertices by merging together opposed
1411
+ vertices. Alternatively, it can be obtained from a cube graph on
1412
+ `2^{n-1}` vertices by adding an edge between opposed vertices. This
1413
+ second construction is the one produced by this method.
1414
+
1415
+ See the :wikipedia:`Folded_cube_graph` for more information.
1416
+
1417
+ EXAMPLES:
1418
+
1419
+ The folded cube graph of order five is the Clebsch graph::
1420
+
1421
+ sage: fc = graphs.FoldedCubeGraph(5)
1422
+ sage: clebsch = graphs.ClebschGraph()
1423
+ sage: fc.is_isomorphic(clebsch)
1424
+ True
1425
+ """
1426
+ if n < 1:
1427
+ raise ValueError("The value of n must be at least 2")
1428
+
1429
+ g = CubeGraph(n - 1)
1430
+ g.name("Folded Cube Graph")
1431
+
1432
+ # Complementing the binary word
1433
+ def complement(x):
1434
+ x = x.replace('0', 'a')
1435
+ x = x.replace('1', '0')
1436
+ x = x.replace('a', '1')
1437
+ return x
1438
+
1439
+ for x in g:
1440
+ if x[0] == '0':
1441
+ g.add_edge(x, complement(x))
1442
+
1443
+ return g
1444
+
1445
+
1446
+ def FriendshipGraph(n):
1447
+ r"""
1448
+ Return the friendship graph `F_n`.
1449
+
1450
+ The friendship graph is also known as the Dutch windmill graph. Let
1451
+ `C_3` be the cycle graph on 3 vertices. Then `F_n` is constructed by
1452
+ joining `n \geq 1` copies of `C_3` at a common vertex. If `n = 1`,
1453
+ then `F_1` is isomorphic to `C_3` (the triangle graph). If `n = 2`,
1454
+ then `F_2` is the butterfly graph, otherwise known as the bowtie
1455
+ graph. For more information, see the :wikipedia:`Friendship_graph`.
1456
+
1457
+ INPUT:
1458
+
1459
+ - ``n`` -- positive integer; the number of copies of `C_3` to use in
1460
+ constructing `F_n`
1461
+
1462
+ OUTPUT:
1463
+
1464
+ - The friendship graph `F_n` obtained from `n` copies of the cycle
1465
+ graph `C_3`.
1466
+
1467
+ .. SEEALSO::
1468
+
1469
+ - :meth:`GraphGenerators.ButterflyGraph`
1470
+
1471
+ EXAMPLES:
1472
+
1473
+ The first few friendship graphs. ::
1474
+
1475
+ sage: # needs sage.plot
1476
+ sage: A = []; B = []
1477
+ sage: for i in range(9):
1478
+ ....: g = graphs.FriendshipGraph(i + 1)
1479
+ ....: A.append(g)
1480
+ sage: for i in range(3):
1481
+ ....: n = []
1482
+ ....: for j in range(3):
1483
+ ....: n.append(A[3*i + j].plot(vertex_size=20, vertex_labels=False))
1484
+ ....: B.append(n)
1485
+ sage: G = graphics_array(B)
1486
+ sage: G.show() # long time
1487
+
1488
+ For `n = 1`, the friendship graph `F_1` is isomorphic to the cycle
1489
+ graph `C_3`, whose visual representation is a triangle. ::
1490
+
1491
+ sage: G = graphs.FriendshipGraph(1); G
1492
+ Friendship graph: Graph on 3 vertices
1493
+ sage: G.show() # long time # needs sage.plot
1494
+ sage: G.is_isomorphic(graphs.CycleGraph(3))
1495
+ True
1496
+
1497
+ For `n = 2`, the friendship graph `F_2` is isomorphic to the
1498
+ butterfly graph, otherwise known as the bowtie graph. ::
1499
+
1500
+ sage: G = graphs.FriendshipGraph(2); G
1501
+ Friendship graph: Graph on 5 vertices
1502
+ sage: G.is_isomorphic(graphs.ButterflyGraph())
1503
+ True
1504
+
1505
+ If `n \geq 2`, then the friendship graph `F_n` has `2n + 1` vertices
1506
+ and `3n` edges. It has radius 1, diameter 2, girth 3, and
1507
+ chromatic number 3. Furthermore, `F_n` is planar and Eulerian. ::
1508
+
1509
+ sage: n = randint(2, 10^3)
1510
+ sage: G = graphs.FriendshipGraph(n)
1511
+ sage: G.order() == 2*n + 1
1512
+ True
1513
+ sage: G.size() == 3*n
1514
+ True
1515
+ sage: G.radius()
1516
+ 1
1517
+ sage: G.diameter()
1518
+ 2
1519
+ sage: G.girth()
1520
+ 3
1521
+ sage: G.chromatic_number() # needs cliquer
1522
+ 3
1523
+ sage: G.is_planar() # needs planarity
1524
+ True
1525
+ sage: G.is_eulerian()
1526
+ True
1527
+
1528
+ TESTS:
1529
+
1530
+ The input ``n`` must be a positive integer. ::
1531
+
1532
+ sage: graphs.FriendshipGraph(randint(-10^5, 0))
1533
+ Traceback (most recent call last):
1534
+ ...
1535
+ ValueError: n must be a positive integer
1536
+ """
1537
+ # sanity checks
1538
+ if n < 1:
1539
+ raise ValueError("n must be a positive integer")
1540
+ # construct the friendship graph
1541
+ if n == 1:
1542
+ from sage.graphs.generators.basic import CycleGraph
1543
+ G = CycleGraph(3)
1544
+ G.name("Friendship graph")
1545
+ return G
1546
+ # build the edges and position dictionaries
1547
+ N = 2 * n + 1 # order of F_n
1548
+ center = 2 * n
1549
+ G = Graph(N, name="Friendship graph")
1550
+ for i in range(0, N - 1, 2):
1551
+ G.add_cycle([center, i, i + 1])
1552
+ G.set_pos({center: (0, 0)})
1553
+ G._circle_embedding(list(range(N - 1)), radius=1)
1554
+ return G
1555
+
1556
+
1557
+ def FuzzyBallGraph(partition, q):
1558
+ r"""
1559
+ Construct a Fuzzy Ball graph with the integer partition
1560
+ ``partition`` and ``q`` extra vertices.
1561
+
1562
+ Let `q` be an integer and let `m_1,m_2,...,m_k` be a set of positive
1563
+ integers. Let `n=q+m_1+...+m_k`. The Fuzzy Ball graph with partition
1564
+ `m_1,m_2,...,m_k` and `q` extra vertices is the graph constructed from the
1565
+ graph `G=K_n` by attaching, for each `i=1,2,...,k`, a new vertex `a_i` to
1566
+ `m_i` distinct vertices of `G`.
1567
+
1568
+ For given positive integers `k` and `m` and nonnegative
1569
+ integer `q`, the set of graphs ``FuzzyBallGraph(p, q)`` for
1570
+ all partitions `p` of `m` with `k` parts are cospectral with
1571
+ respect to the normalized Laplacian.
1572
+
1573
+ EXAMPLES::
1574
+
1575
+ sage: F = graphs.FuzzyBallGraph([3,1],2)
1576
+ sage: F.adjacency_matrix(vertices=list(F)) # needs sage.modules
1577
+ [0 0 1 1 1 0 0 0]
1578
+ [0 0 0 0 0 1 0 0]
1579
+ [1 0 0 1 1 1 1 1]
1580
+ [1 0 1 0 1 1 1 1]
1581
+ [1 0 1 1 0 1 1 1]
1582
+ [0 1 1 1 1 0 1 1]
1583
+ [0 0 1 1 1 1 0 1]
1584
+ [0 0 1 1 1 1 1 0]
1585
+
1586
+ Pick positive integers `m` and `k` and a nonnegative integer `q`.
1587
+ All the FuzzyBallGraphs constructed from partitions of `m` with
1588
+ `k` parts should be cospectral with respect to the normalized
1589
+ Laplacian::
1590
+
1591
+ sage: m = 4; q = 2; k = 2
1592
+ sage: g_list = [graphs.FuzzyBallGraph(p,q) # needs sage.combinat sage.modules
1593
+ ....: for p in Partitions(m, length=k)]
1594
+ sage: set(g.laplacian_matrix(normalized=True, # long time (7s on sage.math, 2011), needs sage.combinat sage.modules
1595
+ ....: vertices=list(g)).charpoly()
1596
+ ....: for g in g_list)
1597
+ {x^8 - 8*x^7 + 4079/150*x^6 - 68689/1350*x^5 + 610783/10800*x^4
1598
+ - 120877/3240*x^3 + 1351/100*x^2 - 931/450*x}
1599
+ """
1600
+ from sage.graphs.generators.basic import CompleteGraph
1601
+ if len(partition) < 1:
1602
+ raise ValueError("partition must be a nonempty list of positive integers")
1603
+ n = q + sum(partition)
1604
+ g = CompleteGraph(n)
1605
+ curr_vertex = 0
1606
+ for e, p in enumerate(partition):
1607
+ g.add_edges([(curr_vertex + i, 'a{0}'.format(e + 1)) for i in range(p)])
1608
+ curr_vertex += p
1609
+ return g
1610
+
1611
+
1612
+ def FibonacciTree(n):
1613
+ r"""
1614
+ Return the graph of the Fibonacci Tree `F_{i}` of order `n`.
1615
+
1616
+ The Fibonacci tree `F_{i}` is recursively defined as the tree
1617
+ with a root vertex and two attached child trees `F_{i-1}` and
1618
+ `F_{i-2}`, where `F_{1}` is just one vertex and `F_{0}` is empty.
1619
+
1620
+ INPUT:
1621
+
1622
+ - ``n`` -- the recursion depth of the Fibonacci Tree
1623
+
1624
+ EXAMPLES::
1625
+
1626
+ sage: g = graphs.FibonacciTree(3) # needs sage.libs.pari
1627
+ sage: g.is_tree() # needs sage.libs.pari
1628
+ True
1629
+
1630
+ ::
1631
+
1632
+ sage: l1 = [ len(graphs.FibonacciTree(_)) + 1 for _ in range(6) ] # needs sage.libs.pari
1633
+ sage: l2 = list(fibonacci_sequence(2,8)) # needs sage.libs.pari
1634
+ sage: l1 == l2 # needs sage.libs.pari
1635
+ True
1636
+
1637
+ AUTHORS:
1638
+
1639
+ - Harald Schilly and Yann Laigle-Chapuy (2010-03-25)
1640
+ """
1641
+ T = Graph(name="Fibonacci-Tree-%d" % n)
1642
+ if n == 1:
1643
+ T.add_vertex(0)
1644
+ if n < 2:
1645
+ return T
1646
+
1647
+ from sage.combinat.combinat import fibonacci_sequence
1648
+ F = list(fibonacci_sequence(n + 2))
1649
+ s = 1.618 ** (n / 1.618 - 1.618)
1650
+ pos = {}
1651
+
1652
+ def fib(level, node, y):
1653
+ pos[node] = (node, y)
1654
+ if level < 2:
1655
+ return
1656
+ level -= 1
1657
+ y -= s
1658
+ diff = F[level]
1659
+ T.add_edge(node, node - diff)
1660
+ if level == 1: # only one child
1661
+ pos[node - diff] = (node, y)
1662
+ return
1663
+ T.add_edge(node, node + diff)
1664
+ fib(level, node - diff, y)
1665
+ fib(level - 1, node + diff, y)
1666
+
1667
+ T.add_vertices(range(sum(F[:-1])))
1668
+ fib(n, F[n + 1] - 1, 0)
1669
+ T.set_pos(pos)
1670
+
1671
+ return T
1672
+
1673
+
1674
+ def GeneralizedPetersenGraph(n, k):
1675
+ r"""
1676
+ Return a generalized Petersen graph with `2n` nodes. The variables
1677
+ `n`, `k` are integers such that `n>2` and `0<k\leq\lfloor(n-1)`/`2\rfloor`
1678
+
1679
+ For `k=1` the result is a graph isomorphic to the circular ladder graph
1680
+ with the same `n`. The regular Petersen Graph has `n=5` and `k=2`.
1681
+ Other named graphs that can be described using this notation include
1682
+ the Desargues graph and the Möbius-Kantor graph.
1683
+
1684
+ INPUT:
1685
+
1686
+ - ``n`` -- the number of nodes is `2*n`
1687
+
1688
+ - ``k`` -- integer (`0<k\leq\lfloor(n-1)`/`2\rfloor`); decides
1689
+ how inner vertices are connected
1690
+
1691
+ PLOTTING: Upon construction, the position dictionary is filled to
1692
+ override the spring-layout algorithm. By convention, the generalized
1693
+ Petersen graphs are displayed as an inner and outer cycle pair, with
1694
+ the first n nodes drawn on the outer circle. The first (0) node is
1695
+ drawn at the top of the outer-circle, moving counterclockwise after that.
1696
+ The inner circle is drawn with the (n)th node at the top, then
1697
+ counterclockwise as well.
1698
+
1699
+ EXAMPLES: For `k=1` the resulting graph will be isomorphic to a circular
1700
+ ladder graph. ::
1701
+
1702
+ sage: g = graphs.GeneralizedPetersenGraph(13,1)
1703
+ sage: g2 = graphs.CircularLadderGraph(13)
1704
+ sage: g.is_isomorphic(g2)
1705
+ True
1706
+
1707
+ The Desargues graph::
1708
+
1709
+ sage: g = graphs.GeneralizedPetersenGraph(10,3)
1710
+ sage: g.girth()
1711
+ 6
1712
+ sage: g.is_bipartite()
1713
+ True
1714
+
1715
+ AUTHORS:
1716
+
1717
+ - Anders Jonsson (2009-10-15)
1718
+ """
1719
+ if n < 3:
1720
+ raise ValueError("n must be larger than 2")
1721
+ if k < 1 or k > (n - 1) // 2:
1722
+ raise ValueError("k must be in 1<= k <=floor((n-1)/2)")
1723
+ G = Graph(2 * n, name="Generalized Petersen graph (n='+str(n)+',k="+str(k)+")")
1724
+ for i in range(n):
1725
+ G.add_edge(i, (i+1) % n)
1726
+ G.add_edge(i, i+n)
1727
+ G.add_edge(i+n, n + (i+k) % n)
1728
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1729
+ G._circle_embedding(list(range(n, 2*n)), radius=.5, angle=pi/2)
1730
+ return G
1731
+
1732
+
1733
+ def IGraph(n, j, k):
1734
+ r"""
1735
+ Return an I-graph with `2n` nodes.
1736
+
1737
+ The I-Graph family as been proposed in [BCMS1988]_ as a generalization of
1738
+ the generalized Petersen graphs. The variables `n`, `j`, `k` are integers
1739
+ such that `n > 2` and `0 < j, k \leq \lfloor (n - 1) / 2 \rfloor`.
1740
+ When `j = 1` the resulting graph is isomorphic to the generalized Petersen
1741
+ graph with the same `n` and `k`.
1742
+
1743
+ INPUT:
1744
+
1745
+ - ``n`` -- the number of nodes is `2 * n`
1746
+
1747
+ - ``j`` -- integer such that `0 < j \leq \lfloor (n-1) / 2 \rfloor`
1748
+ determining how outer vertices are connected
1749
+
1750
+ - ``k`` -- integer such that `0 < k \leq \lfloor (n-1) / 2 \rfloor`
1751
+ determining how inner vertices are connected
1752
+
1753
+ PLOTTING: Upon construction, the position dictionary is filled to override
1754
+ the spring-layout algorithm. By convention, the I-graphs are displayed as an
1755
+ inner and outer cycle pair, with the first n nodes drawn on the outer
1756
+ circle. The first (0) node is drawn at the top of the outer-circle, moving
1757
+ counterclockwise after that. The inner circle is drawn with the (n)th node
1758
+ at the top, then counterclockwise as well.
1759
+
1760
+ EXAMPLES:
1761
+
1762
+ When `j = 1` the resulting graph will be isomorphic to a generalized
1763
+ Petersen graph::
1764
+
1765
+ sage: g = graphs.IGraph(7,1,2)
1766
+ sage: g2 = graphs.GeneralizedPetersenGraph(7,2)
1767
+ sage: g.is_isomorphic(g2)
1768
+ True
1769
+
1770
+ The IGraph with parameters `(n, j, k)` is isomorphic to the IGraph with
1771
+ parameters `(n, k, j)`::
1772
+
1773
+ sage: g = graphs.IGraph(7, 2, 3)
1774
+ sage: h = graphs.IGraph(7, 3, 2)
1775
+ sage: g.is_isomorphic(h)
1776
+ True
1777
+
1778
+ TESTS::
1779
+
1780
+ sage: graphs.IGraph(1, 1, 1)
1781
+ Traceback (most recent call last):
1782
+ ...
1783
+ ValueError: n must be larger than 2
1784
+ sage: graphs.IGraph(3, 0, 1)
1785
+ Traceback (most recent call last):
1786
+ ...
1787
+ ValueError: j must be in 1 <= j <= floor((n - 1) / 2)
1788
+ sage: graphs.IGraph(3, 33, 1)
1789
+ Traceback (most recent call last):
1790
+ ...
1791
+ ValueError: j must be in 1 <= j <= floor((n - 1) / 2)
1792
+ sage: graphs.IGraph(3, 1, 0)
1793
+ Traceback (most recent call last):
1794
+ ...
1795
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1796
+ sage: graphs.IGraph(3, 1, 3)
1797
+ Traceback (most recent call last):
1798
+ ...
1799
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1800
+ """
1801
+ if n < 3:
1802
+ raise ValueError("n must be larger than 2")
1803
+ if j < 1 or j > (n - 1) // 2:
1804
+ raise ValueError("j must be in 1 <= j <= floor((n - 1) / 2)")
1805
+ if k < 1 or k > (n - 1) // 2:
1806
+ raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)")
1807
+
1808
+ G = Graph(2 * n, name="I-graph (n={}, j={}, k={})".format(n, j, k))
1809
+ for i in range(n):
1810
+ G.add_edge(i, (i + j) % n)
1811
+ G.add_edge(i, i + n)
1812
+ G.add_edge(i + n, n + (i + k) % n)
1813
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1814
+ G._circle_embedding(list(range(n, 2 * n)), radius=.5, angle=pi/2)
1815
+ return G
1816
+
1817
+
1818
+ def DoubleGeneralizedPetersenGraph(n, k):
1819
+ r"""
1820
+ Return a double generalized Petersen graph with `4n` nodes.
1821
+
1822
+ The double generalized Petersen graphs is a family of graphs proposed in
1823
+ [ZF2012]_ as a variant of generalized Petersen graphs. The variables `n`,
1824
+ `k` are integers such that `n > 2` and `0 < k \leq \lfloor (n-1) / 2
1825
+ \rfloor`.
1826
+
1827
+ INPUT:
1828
+
1829
+ - ``n`` -- the number of nodes is `4 * n`
1830
+
1831
+ - ``k`` -- integer such that `0 < k \leq \lfloor (n-1) / 2 \rfloor`
1832
+ determining how vertices on second and third inner rims are connected
1833
+
1834
+ PLOTTING: Upon construction, the position dictionary is filled to override
1835
+ the spring-layout algorithm. By convention, the double generalized Petersen
1836
+ graphs are displayed as 4 cocentric cycles, with the first n nodes drawn on
1837
+ the outer circle. The first (0) node is drawn at the top of the
1838
+ outer-circle, moving counterclockwise after that. The second circle is drawn
1839
+ with the (n)th node at the top, then counterclockwise as well. The tird
1840
+ cycle is drawn with the (2n)th node at the top, then counterclockwise. And
1841
+ the fourth cycle is drawn with the (3n)th node at the top, then again
1842
+ counterclockwise.
1843
+
1844
+ EXAMPLES:
1845
+
1846
+ When `n` is even the resulting graph will be isomorphic to a double
1847
+ generalized Petersen graph with `k' = n / 2 - k`::
1848
+
1849
+ sage: g = graphs.DoubleGeneralizedPetersenGraph(10, 2)
1850
+ sage: g2 = graphs.DoubleGeneralizedPetersenGraph(10, 3)
1851
+ sage: g.is_isomorphic(g2)
1852
+ True
1853
+
1854
+ TESTS::
1855
+
1856
+ sage: graphs.DoubleGeneralizedPetersenGraph(1, 1)
1857
+ Traceback (most recent call last):
1858
+ ...
1859
+ ValueError: n must be larger than 2
1860
+ sage: graphs.DoubleGeneralizedPetersenGraph(3, 0)
1861
+ Traceback (most recent call last):
1862
+ ...
1863
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1864
+ sage: graphs.DoubleGeneralizedPetersenGraph(3, 3)
1865
+ Traceback (most recent call last):
1866
+ ...
1867
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1868
+ """
1869
+ if n < 3:
1870
+ raise ValueError("n must be larger than 2")
1871
+ if k < 1 or k > (n - 1) // 2:
1872
+ raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)")
1873
+
1874
+ G = Graph(4 * n, name="Double generalized Petersen graph (n={}, k={})".format(n, k))
1875
+ for i in range(n):
1876
+ G.add_edge(i, (i + 1) % n)
1877
+ G.add_edge(i + 3 * n, (i + 1) % n + 3 * n)
1878
+ G.add_edge(i, i + n)
1879
+ G.add_edge(i + 2 * n, i + 3 * n)
1880
+ G.add_edge(i + n, (i + k) % n + 2 * n)
1881
+ G.add_edge(i + 2 * n, (i + k) % n + n)
1882
+ G._circle_embedding(list(range(n)), radius=3, angle=pi/2)
1883
+ G._circle_embedding(list(range(n, 2 * n)), radius=2, angle=pi/2)
1884
+ G._circle_embedding(list(range(2 * n, 3 * n)), radius=1.5, angle=pi/2)
1885
+ G._circle_embedding(list(range(3 * n, 4 * n)), radius=0.5, angle=pi/2)
1886
+ return G
1887
+
1888
+
1889
+ def RoseWindowGraph(n, a, r):
1890
+ r"""
1891
+ Return a rose window graph with `2n` nodes.
1892
+
1893
+ The rose window graphs is a family of tetravalant graphs introduced in
1894
+ [Wilson2008]_. The parameters `n`, `a` and `r` are integers such that
1895
+ `n > 2`, `1 \leq a, r < n`, and `r \neq n / 2`.
1896
+
1897
+ INPUT:
1898
+
1899
+ - ``n`` -- the number of nodes is `2 * n`
1900
+
1901
+ - ``a`` -- integer such that `1 \leq a < n` determining a-spoke edges
1902
+
1903
+ - ``r`` -- integer such that `1 \leq r < n` and `r \neq n / 2` determining
1904
+ how inner vertices are connected
1905
+
1906
+ PLOTTING: Upon construction, the position dictionary is filled to override
1907
+ the spring-layout algorithm. By convention, the rose window graphs are
1908
+ displayed as an inner and outer cycle pair, with the first `n` nodes drawn
1909
+ on the outer circle. The first (0) node is drawn at the top of the
1910
+ outer-circle, moving counterclockwise after that. The inner circle is drawn
1911
+ with the (`n`)th node at the top, then counterclockwise as well. Vertices in
1912
+ the outer circle are connected in the circular manner, vertices in the inner
1913
+ circle are connected when their label have difference `r \pmod{n}`. Vertices
1914
+ on the outer rim are connected with the vertices on the inner rim when they
1915
+ are at the same position and when they are `a` apart.
1916
+
1917
+ EXAMPLES:
1918
+
1919
+ The vertices of a rose window graph have all degree 4::
1920
+
1921
+ sage: G = graphs.RoseWindowGraph(5, 1, 2)
1922
+ sage: all(G.degree(u) == 4 for u in G)
1923
+ True
1924
+
1925
+ The smallest rose window graph as parameters `(3, 2, 1)`::
1926
+
1927
+ sage: G = graphs.RoseWindowGraph(3, 2, 1)
1928
+ sage: all(G.degree(u) == 4 for u in G)
1929
+ True
1930
+
1931
+ TESTS::
1932
+
1933
+ sage: graphs.RoseWindowGraph(1, 1, 1)
1934
+ Traceback (most recent call last):
1935
+ ...
1936
+ ValueError: n must be larger than 2
1937
+ sage: graphs.RoseWindowGraph(6, 0, 2)
1938
+ Traceback (most recent call last):
1939
+ ...
1940
+ ValueError: a must be an integer such that 1 <= a < n
1941
+ sage: graphs.RoseWindowGraph(6, 6, 2)
1942
+ Traceback (most recent call last):
1943
+ ...
1944
+ ValueError: a must be an integer such that 1 <= a < n
1945
+ sage: graphs.RoseWindowGraph(6, 3, 0)
1946
+ Traceback (most recent call last):
1947
+ ...
1948
+ ValueError: r must be an integer such that 1 <= r < n
1949
+ sage: graphs.RoseWindowGraph(6, 3, 6)
1950
+ Traceback (most recent call last):
1951
+ ...
1952
+ ValueError: r must be an integer such that 1 <= r < n
1953
+ sage: graphs.RoseWindowGraph(6, 3, 3)
1954
+ Traceback (most recent call last):
1955
+ ...
1956
+ ValueError: r must be different than n / 2
1957
+ """
1958
+ if n < 3:
1959
+ raise ValueError("n must be larger than 2")
1960
+ if a < 1 or a >= n:
1961
+ raise ValueError("a must be an integer such that 1 <= a < n")
1962
+ if r < 1 or r >= n:
1963
+ raise ValueError("r must be an integer such that 1 <= r < n")
1964
+ if r == n / 2:
1965
+ raise ValueError("r must be different than n / 2")
1966
+
1967
+ G = Graph(2 * n, name="rose window graph (n={}, a={}, r={})".format(n, a, r))
1968
+ for i in range(n):
1969
+ G.add_edge(i, (i + 1) % n)
1970
+ G.add_edge(i, i + n)
1971
+ G.add_edge((i + a) % n, i + n)
1972
+ G.add_edge(i + n, (i + r) % n + n)
1973
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1974
+ G._circle_embedding(list(range(n, 2 * n)), radius=0.5, angle=pi/2)
1975
+ return G
1976
+
1977
+
1978
+ def TabacjnGraph(n, a, b, r):
1979
+ r"""
1980
+ Return a Tabačjn graph with `2n` nodes.
1981
+
1982
+ The Tabačjn graphs is a family of pentavalent bicirculants graphs proposed
1983
+ in [AHKOS2014]_ as a generalization of generalized Petersen graphs. The
1984
+ parameters `n`, `a`, `b`, `r` are integers such that `n \geq 3`, `1 \leq a,
1985
+ b, r \leq n - 1`, with `a \neq b` and `r \neq n / 2`.
1986
+
1987
+ INPUT:
1988
+
1989
+ - ``n`` -- the number of nodes is `2 * n`
1990
+
1991
+ - ``a`` -- integer such that `0 < a < n` and `a \neq b`, that determines
1992
+ a-spoke edges
1993
+
1994
+ - ``b`` -- integer such that `0 < b < n` and `b \neq a`, that determines
1995
+ b-spoke edges
1996
+
1997
+ - ``r`` -- integer such that `0 < r < n` and `r \neq n/2` determining how
1998
+ inner vertices are connected
1999
+
2000
+ PLOTTING: Upon construction, the position dictionary is filled to override
2001
+ the spring-layout algorithm. By convention, the rose window graphs are
2002
+ displayed as an inner and outer cycle pair, with the first `n` nodes drawn
2003
+ on the outer circle. The first (0) node is drawn at the top of the
2004
+ outer-circle, moving counterclockwise after that. The inner circle is drawn
2005
+ with the (`n`)th node at the top, then counterclockwise as well. Vertices in
2006
+ the outer circle are connected in the circular manner, vertices in the inner
2007
+ circle are connected when their label have difference `r \pmod{n}`. Vertices
2008
+ on the outer rim are connected with the vertices on the inner rim when they
2009
+ are at the same position and when they are `a` and `b` apart.
2010
+
2011
+ EXAMPLES::
2012
+
2013
+ sage: G = graphs.TabacjnGraph(3, 1, 2, 1)
2014
+ sage: G.degree()
2015
+ [5, 5, 5, 5, 5, 5]
2016
+ sage: G.is_isomorphic(graphs.CompleteGraph(6))
2017
+ True
2018
+ sage: G = graphs.TabacjnGraph(6, 1, 5, 2)
2019
+ sage: I = graphs.IcosahedralGraph()
2020
+ sage: G.is_isomorphic(I)
2021
+ True
2022
+
2023
+ TESTS::
2024
+
2025
+ sage: graphs.TabacjnGraph(1, 1, 1, 1)
2026
+ Traceback (most recent call last):
2027
+ ...
2028
+ ValueError: n must be larger than 2
2029
+ sage: graphs.TabacjnGraph(3, 0, 1, 1)
2030
+ Traceback (most recent call last):
2031
+ ...
2032
+ ValueError: a must be an integer such that 1 <= a < n
2033
+ sage: graphs.TabacjnGraph(3, 3, 1, 1)
2034
+ Traceback (most recent call last):
2035
+ ...
2036
+ ValueError: a must be an integer such that 1 <= a < n
2037
+ sage: graphs.TabacjnGraph(3, 1, 0, 1)
2038
+ Traceback (most recent call last):
2039
+ ...
2040
+ ValueError: b must be an integer such that 1 <= b < n
2041
+ sage: graphs.TabacjnGraph(3, 1, 3, 1)
2042
+ Traceback (most recent call last):
2043
+ ...
2044
+ ValueError: b must be an integer such that 1 <= b < n
2045
+ sage: graphs.TabacjnGraph(3, 1, 1, 1)
2046
+ Traceback (most recent call last):
2047
+ ...
2048
+ ValueError: a must be different than b
2049
+ sage: graphs.TabacjnGraph(3, 1, 2, 0)
2050
+ Traceback (most recent call last):
2051
+ ...
2052
+ ValueError: r must be an integer such that 1 <= r < n
2053
+ sage: graphs.TabacjnGraph(3, 1, 2, 3)
2054
+ Traceback (most recent call last):
2055
+ ...
2056
+ ValueError: r must be an integer such that 1 <= r < n
2057
+ sage: graphs.TabacjnGraph(4, 1, 2, 2)
2058
+ Traceback (most recent call last):
2059
+ ...
2060
+ ValueError: r must be different than n / 2
2061
+ """
2062
+ if n < 3:
2063
+ raise ValueError("n must be larger than 2")
2064
+ if a < 1 or a >= n:
2065
+ raise ValueError("a must be an integer such that 1 <= a < n")
2066
+ if b < 1 or b >= n:
2067
+ raise ValueError("b must be an integer such that 1 <= b < n")
2068
+ if a == b:
2069
+ raise ValueError("a must be different than b")
2070
+ if r < 1 or r >= n:
2071
+ raise ValueError("r must be an integer such that 1 <= r < n")
2072
+ if r == n/2:
2073
+ raise ValueError("r must be different than n / 2")
2074
+
2075
+ G = Graph(2 * n, name="Tabačjn graph (n={}, a={}, b={}, r={})".format(n, a, b, r))
2076
+ for i in range(n):
2077
+ G.add_edge(i, (i + 1) % n)
2078
+ G.add_edge(i, i + n)
2079
+ G.add_edge(i + n, n + (i + r) % n)
2080
+ G.add_edge(i, (i + a) % n + n)
2081
+ G.add_edge(i, (i + b) % n + n)
2082
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
2083
+ G._circle_embedding(list(range(n, 2 * n)), radius=0.5, angle=pi/2)
2084
+ return G
2085
+
2086
+
2087
+ def HararyGraph(k, n):
2088
+ r"""
2089
+ Return the Harary graph on `n` vertices and connectivity `k`, where
2090
+ `2 \leq k < n`.
2091
+
2092
+ A `k`-connected graph `G` on `n` vertices requires the minimum degree
2093
+ `\delta(G)\geq k`, so the minimum number of edges `G` should have is
2094
+ `\lceil kn/2\rceil`. Harary graphs achieve this lower bound, that is,
2095
+ Harary graphs are minimal `k`-connected graphs on `n` vertices.
2096
+
2097
+ The construction provided uses the method CirculantGraph. For more
2098
+ details, see the book [West2001]_ or the `MathWorld article on
2099
+ Harary graphs <http://mathworld.wolfram.com/HararyGraph.html>`_.
2100
+
2101
+ EXAMPLES:
2102
+
2103
+ Harary graphs `H_{k,n}`::
2104
+
2105
+ sage: h = graphs.HararyGraph(5,9); h
2106
+ Harary graph 5, 9: Graph on 9 vertices
2107
+ sage: h.order()
2108
+ 9
2109
+ sage: h.size()
2110
+ 23
2111
+ sage: h.vertex_connectivity() # needs sage.numerical.mip
2112
+ 5
2113
+
2114
+ TESTS:
2115
+
2116
+ Connectivity of some Harary graphs::
2117
+
2118
+ sage: n = 10
2119
+ sage: for k in range(2,n): # needs sage.numerical.mip
2120
+ ....: g = graphs.HararyGraph(k,n)
2121
+ ....: if k != g.vertex_connectivity():
2122
+ ....: print("Connectivity of Harary graphs not satisfied.")
2123
+ """
2124
+ if k < 2:
2125
+ raise ValueError("Connectivity parameter k should be at least 2.")
2126
+ if k >= n:
2127
+ raise ValueError("Number of vertices n should be greater than k.")
2128
+
2129
+ if k % 2 == 0:
2130
+ G = CirculantGraph(n, list(range(1, k//2 + 1)))
2131
+ else:
2132
+ if n % 2 == 0:
2133
+ G = CirculantGraph(n, list(range(1, (k - 1)//2 + 1)))
2134
+ for i in range(n):
2135
+ G.add_edge(i, (i + n//2) % n)
2136
+ else:
2137
+ G = HararyGraph(k - 1, n)
2138
+ for i in range((n - 1)//2 + 1):
2139
+ G.add_edge(i, (i + (n - 1)//2) % n)
2140
+ G.name('Harary graph {0}, {1}'.format(k, n))
2141
+ return G
2142
+
2143
+
2144
+ def HyperStarGraph(n, k):
2145
+ r"""
2146
+ Return the hyper-star graph `HS(n, k)`.
2147
+
2148
+ The vertices of the hyper-star graph are the set of binary strings of length
2149
+ `n` which contain `k` 1s. Two vertices, `u` and `v`, are adjacent only if
2150
+ `u` can be obtained from `v` by swapping the first bit with a different
2151
+ symbol in another position. For instance, vertex ``'011100'`` of `HS(6, 3)`
2152
+ is adjacent to vertices ``'101100'``, ``'110100'`` and ``'111000'``.
2153
+ See [LKOL2002]_ for more details.
2154
+
2155
+ INPUT:
2156
+
2157
+ - ``n`` -- nonnegative integer; length of the binary strings
2158
+
2159
+ - ``k`` -- nonnegative integer; number of 1s per binary string
2160
+
2161
+ EXAMPLES::
2162
+
2163
+ sage: g = graphs.HyperStarGraph(6,3)
2164
+ sage: sorted(g.neighbors('011100'))
2165
+ ['101100', '110100', '111000']
2166
+ sage: g.plot() # long time # needs sage.plot
2167
+ Graphics object consisting of 51 graphics primitives
2168
+
2169
+ TESTS::
2170
+
2171
+ sage: graphs.HyperStarGraph(-1, 1)
2172
+ Traceback (most recent call last):
2173
+ ...
2174
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2175
+ sage: graphs.HyperStarGraph(1, -1)
2176
+ Traceback (most recent call last):
2177
+ ...
2178
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2179
+ sage: graphs.HyperStarGraph(1, 2)
2180
+ Traceback (most recent call last):
2181
+ ...
2182
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2183
+
2184
+ AUTHORS:
2185
+
2186
+ - Michael Yurko (2009-09-01)
2187
+ """
2188
+ if n < 0 or k < 0 or k > n:
2189
+ raise ValueError("parameters n and k must be nonnegative integers "
2190
+ "satisfying n >= k >= 0")
2191
+ if not n:
2192
+ adj = {}
2193
+ elif not k:
2194
+ adj = {'0'*n: []}
2195
+ elif k == n:
2196
+ adj = {'1'*n: []}
2197
+ else:
2198
+ from sage.data_structures.bitset import Bitset
2199
+ adj = dict()
2200
+ # We consider the strings of n bits with k 1s and starting with a 0
2201
+ for c in combinations(range(1, n), k):
2202
+ u = str(Bitset(c, capacity=n))
2203
+ L = []
2204
+ c = list(c)
2205
+ # The neighbors of u are all the strings obtained by swapping a 1
2206
+ # with the first bit (0)
2207
+ for i in range(k):
2208
+ one = c[i]
2209
+ c[i] = 0
2210
+ L.append(str(Bitset(c, capacity=n)))
2211
+ c[i] = one
2212
+ adj[u] = L
2213
+
2214
+ return Graph(adj, format='dict_of_lists', name="HS(%d,%d)" % (n, k))
2215
+
2216
+
2217
+ def LCFGraph(n, shift_list, repeats):
2218
+ r"""
2219
+ Return the cubic graph specified in LCF notation.
2220
+
2221
+ LCF (Lederberg-Coxeter-Fruchte) notation is a concise way of describing
2222
+ cubic Hamiltonian graphs. The way a graph is constructed is as
2223
+ follows. Since there is a Hamiltonian cycle, we first create a cycle on `n`
2224
+ nodes. The variable ``shift_list`` = `[s_0, s_1, ..., s_{k-1}]` describes
2225
+ edges to be created by the following scheme: for each `i \in \{0, 1, \dots,
2226
+ k-1\}`, connect vertex `i` to vertex `(i + s_i) \pmod{n}`. Then, ``repeats``
2227
+ specifies the number of times to repeat this process, where on the `j`-th
2228
+ repeat we connect vertex `(i + j k) \pmod{n}` to vertex `(i + j k + s_i)
2229
+ \pmod{n}`.
2230
+
2231
+ For more details, see the :wikipedia:`LCF_notation` and [Fru1977]_,
2232
+ [Gru2003]_ pp. 357-365, and [Led1965]_.
2233
+
2234
+ INPUT:
2235
+
2236
+ - ``n`` -- the number of nodes
2237
+
2238
+ - ``shift_list`` -- a list of integer shifts mod `n`
2239
+
2240
+ - ``repeats`` -- the number of times to repeat the process
2241
+
2242
+ EXAMPLES::
2243
+
2244
+ sage: G = graphs.LCFGraph(4, [2,-2], 2) # needs networkx
2245
+ sage: G.is_isomorphic(graphs.TetrahedralGraph()) # needs networkx
2246
+ True
2247
+
2248
+ ::
2249
+
2250
+ sage: G = graphs.LCFGraph(20, [10,7,4,-4,-7,10,-4,7,-7,4], 2) # needs networkx
2251
+ sage: G.is_isomorphic(graphs.DodecahedralGraph()) # needs networkx
2252
+ True
2253
+
2254
+ ::
2255
+
2256
+ sage: G = graphs.LCFGraph(14, [5,-5], 7) # needs networkx
2257
+ sage: G.is_isomorphic(graphs.HeawoodGraph()) # needs networkx
2258
+ True
2259
+
2260
+ The largest cubic nonplanar graph of diameter three::
2261
+
2262
+ sage: # needs networkx
2263
+ sage: G = graphs.LCFGraph(20, [-10,-7,-5,4,7,-10,-7,-4,5,7,
2264
+ ....: -10,-7,6,-5,7,-10,-7,5,-6,7], 1)
2265
+ sage: G.degree()
2266
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
2267
+ sage: G.diameter()
2268
+ 3
2269
+ sage: G.show() # long time # needs sage.plot
2270
+
2271
+ PLOTTING: LCF Graphs are plotted as an `n`-cycle with edges in the
2272
+ middle, as described above.
2273
+ """
2274
+ import networkx
2275
+ G = Graph(networkx.LCF_graph(n, shift_list, repeats), name="LCF Graph")
2276
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
2277
+ return G
2278
+
2279
+
2280
+ def MycielskiGraph(k=1, relabel=True):
2281
+ r"""
2282
+ Return the `k`-th Mycielski Graph.
2283
+
2284
+ The graph `M_k` is triangle-free and has chromatic number
2285
+ equal to `k`. These graphs show, constructively, that there
2286
+ are triangle-free graphs with arbitrarily high chromatic
2287
+ number.
2288
+
2289
+ The Mycielski graphs are built recursively starting with
2290
+ `M_0`, an empty graph; `M_1`, a single vertex graph; and `M_2`
2291
+ is the graph `K_2`. `M_{k+1}` is then built from `M_k`
2292
+ as follows:
2293
+
2294
+ If the vertices of `M_k` are `v_1,\ldots,v_n`, then the
2295
+ vertices of `M_{k+1}` are
2296
+ `v_1,\ldots,v_n,w_1,\ldots,w_n,z`. Vertices `v_1,\ldots,v_n`
2297
+ induce a copy of `M_k`. Vertices `w_1,\ldots,w_n` are an
2298
+ independent set. Vertex `z` is adjacent to all the
2299
+ `w_i`-vertices. Finally, vertex `w_i` is adjacent to vertex
2300
+ `v_j` iff `v_i` is adjacent to `v_j`.
2301
+
2302
+ For more details, see the :wikipedia:`Mycielskian`.
2303
+
2304
+ INPUT:
2305
+
2306
+ - ``k`` -- number of steps in the construction process
2307
+
2308
+ - ``relabel`` -- relabel the vertices so their names are the integers
2309
+ ``range(n)`` where ``n`` is the number of vertices in the graph
2310
+
2311
+ EXAMPLES:
2312
+
2313
+ The Mycielski graph `M_k` is triangle-free and has chromatic
2314
+ number equal to `k`. ::
2315
+
2316
+ sage: g = graphs.MycielskiGraph(5)
2317
+ sage: g.is_triangle_free()
2318
+ True
2319
+ sage: g.chromatic_number() # needs cliquer
2320
+ 5
2321
+
2322
+ The graphs `M_4` is (isomorphic to) the Grotzsch graph. ::
2323
+
2324
+ sage: g = graphs.MycielskiGraph(4)
2325
+ sage: g.is_isomorphic(graphs.GrotzschGraph())
2326
+ True
2327
+ """
2328
+ g = Graph()
2329
+ g.name("Mycielski Graph " + str(k))
2330
+
2331
+ if k < 0:
2332
+ raise ValueError("parameter k must be a nonnegative integer")
2333
+
2334
+ if k == 0:
2335
+ return g
2336
+
2337
+ if k == 1:
2338
+ g.add_vertex(0)
2339
+ return g
2340
+
2341
+ if k == 2:
2342
+ g.add_edge(0, 1)
2343
+ return g
2344
+
2345
+ g0 = MycielskiGraph(k - 1)
2346
+ g = MycielskiStep(g0)
2347
+ g.name("Mycielski Graph " + str(k))
2348
+ if relabel:
2349
+ g.relabel()
2350
+
2351
+ return g
2352
+
2353
+
2354
+ def MycielskiStep(g):
2355
+ r"""
2356
+ Perform one iteration of the Mycielski construction.
2357
+
2358
+ See the documentation for ``MycielskiGraph`` which uses this
2359
+ method. We expose it to all users in case they may find it
2360
+ useful.
2361
+
2362
+ EXAMPLE. One iteration of the Mycielski step applied to the
2363
+ 5-cycle yields a graph isomorphic to the Grotzsch graph ::
2364
+
2365
+ sage: g = graphs.CycleGraph(5)
2366
+ sage: h = graphs.MycielskiStep(g)
2367
+ sage: h.is_isomorphic(graphs.GrotzschGraph())
2368
+ True
2369
+ """
2370
+ # Make a copy of the input graph g
2371
+ gg = copy(g)
2372
+
2373
+ # rename a vertex v of gg as (1,v)
2374
+ renamer = {v: (1, v) for v in g}
2375
+ gg.relabel(renamer)
2376
+
2377
+ # add the w vertices to gg as (2,v)
2378
+ wlist = [(2, v) for v in g]
2379
+ gg.add_vertices(wlist)
2380
+
2381
+ # add the z vertex as (0,0)
2382
+ gg.add_vertex((0, 0))
2383
+
2384
+ # add the edges from z to w_i
2385
+ gg.add_edges([((0, 0), (2, v)) for v in g])
2386
+
2387
+ # make the v_i w_j edges
2388
+ for v in g:
2389
+ gg.add_edges([((1, v), (2, vv)) for vv in g.neighbors(v)])
2390
+
2391
+ return gg
2392
+
2393
+
2394
+ def NKStarGraph(n, k):
2395
+ r"""
2396
+ Return the `(n,k)`-star graph.
2397
+
2398
+ The vertices of the `(n,k)`-star graph are the set of all arrangements of
2399
+ `n` symbols into labels of length `k`. There are two adjacency rules for the
2400
+ `(n,k)`-star graph. First, two vertices are adjacent if one can be obtained
2401
+ from the other by swapping the first symbol with another symbol. Second, two
2402
+ vertices are adjacent if one can be obtained from the other by swapping the
2403
+ first symbol with an external symbol (a symbol not used in the original
2404
+ label).
2405
+
2406
+ INPUT:
2407
+
2408
+ - ``n`` -- integer; number of symbols
2409
+
2410
+ - ``k`` -- integer; length of the labels of the vertices
2411
+
2412
+ EXAMPLES::
2413
+
2414
+ sage: g = graphs.NKStarGraph(4,2)
2415
+ sage: g.plot() # long time # needs sage.plot
2416
+ Graphics object consisting of 31 graphics primitives
2417
+
2418
+ REFERENCES:
2419
+
2420
+ [CC1995]_
2421
+
2422
+ AUTHORS:
2423
+
2424
+ - Michael Yurko (2009-09-01)
2425
+ """
2426
+ from sage.combinat.permutation import Arrangements
2427
+ # set from which to permute
2428
+ set = [str(i) for i in range(1, n + 1)]
2429
+ # create dict
2430
+ d = {}
2431
+ for v in Arrangements(set, k):
2432
+ v = list(v) # So we can easily mutate it
2433
+ tmp_dict = {}
2434
+ # add edges of dimension i
2435
+ for i in range(1, k):
2436
+ # swap 0th and ith element
2437
+ v[0], v[i] = v[i], v[0]
2438
+ # convert to str and add to list
2439
+ vert = "".join(v)
2440
+ tmp_dict[vert] = None
2441
+ # swap back
2442
+ v[0], v[i] = v[i], v[0]
2443
+ # add other edges
2444
+ tmp_bit = v[0]
2445
+ for i in set:
2446
+ # check if external
2447
+ if i not in v:
2448
+ v[0] = i
2449
+ # add edge
2450
+ vert = "".join(v)
2451
+ tmp_dict[vert] = None
2452
+ v[0] = tmp_bit
2453
+ d["".join(v)] = tmp_dict
2454
+ return Graph(d, name="(%d,%d)-star" % (n, k))
2455
+
2456
+
2457
+ def NStarGraph(n):
2458
+ r"""
2459
+ Return the `n`-star graph.
2460
+
2461
+ The vertices of the `n`-star graph are the set of permutations on `n`
2462
+ symbols. There is an edge between two vertices if their labels differ
2463
+ only in the first and one other position.
2464
+
2465
+ INPUT:
2466
+
2467
+ - ``n`` -- integer; number of symbols
2468
+
2469
+ EXAMPLES::
2470
+
2471
+ sage: g = graphs.NStarGraph(4)
2472
+ sage: g.plot() # long time # needs sage.plot
2473
+ Graphics object consisting of 61 graphics primitives
2474
+
2475
+ REFERENCES:
2476
+
2477
+ [AHK1994]_
2478
+
2479
+ AUTHORS:
2480
+
2481
+ - Michael Yurko (2009-09-01)
2482
+ """
2483
+ from sage.combinat.permutation import Permutations
2484
+ # set from which to permute
2485
+ set = [str(i) for i in range(1, n + 1)]
2486
+ # create dictionary of lists
2487
+ # vertices are adjacent if the first element is swapped with the ith element
2488
+ d = {}
2489
+ for v in Permutations(set):
2490
+ v = list(v) # So we can easily mutate it
2491
+ tmp_dict = {}
2492
+ for i in range(1, n):
2493
+ if v[0] != v[i]:
2494
+ # swap 0th and ith element
2495
+ v[0], v[i] = v[i], v[0]
2496
+ # convert to str and add to list
2497
+ vert = "".join(v)
2498
+ tmp_dict[vert] = None
2499
+ # swap back
2500
+ v[0], v[i] = v[i], v[0]
2501
+ d["".join(v)] = tmp_dict
2502
+ return Graph(d, name="%d-star" % n)
2503
+
2504
+
2505
+ def OddGraph(n):
2506
+ r"""
2507
+ Return the Odd Graph with parameter `n`.
2508
+
2509
+ The Odd Graph with parameter `n` is defined as the
2510
+ Kneser Graph with parameters `2n-1,n-1`.
2511
+ Equivalently, the Odd Graph is the graph whose vertices
2512
+ are the `n-1`-subsets of `[0,1,\dots,2(n-1)]`, and such
2513
+ that two vertices are adjacent if their corresponding sets
2514
+ are disjoint.
2515
+
2516
+ For example, the Petersen Graph can be defined
2517
+ as the Odd Graph with parameter `3`.
2518
+
2519
+ EXAMPLES::
2520
+
2521
+ sage: OG = graphs.OddGraph(3)
2522
+ sage: sorted(OG.vertex_iterator(), key=str)
2523
+ [{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5},
2524
+ {3, 4}, {3, 5}, {4, 5}]
2525
+ sage: P = graphs.PetersenGraph()
2526
+ sage: P.is_isomorphic(OG)
2527
+ True
2528
+
2529
+ TESTS::
2530
+
2531
+ sage: KG = graphs.OddGraph(1)
2532
+ Traceback (most recent call last):
2533
+ ...
2534
+ ValueError: Parameter n should be an integer strictly greater than 1
2535
+ """
2536
+ if n <= 1:
2537
+ raise ValueError("Parameter n should be an integer strictly greater than 1")
2538
+ g = KneserGraph(2*n - 1, n - 1)
2539
+ g.name("Odd Graph with parameter %s" % n)
2540
+ return g
2541
+
2542
+
2543
+ def PaleyGraph(q):
2544
+ r"""
2545
+ Paley graph with `q` vertices.
2546
+
2547
+ Parameter `q` must be the power of a prime number and congruent
2548
+ to 1 mod 4.
2549
+
2550
+ EXAMPLES::
2551
+
2552
+ sage: G = graphs.PaleyGraph(9); G # needs sage.rings.finite_rings
2553
+ Paley graph with parameter 9: Graph on 9 vertices
2554
+ sage: G.is_regular() # needs sage.rings.finite_rings
2555
+ True
2556
+
2557
+ A Paley graph is always self-complementary::
2558
+
2559
+ sage: G.is_self_complementary() # needs sage.rings.finite_rings
2560
+ True
2561
+
2562
+ TESTS:
2563
+
2564
+ Wrong parameter::
2565
+
2566
+ sage: graphs.PaleyGraph(6)
2567
+ Traceback (most recent call last):
2568
+ ...
2569
+ ValueError: parameter q must be a prime power
2570
+ sage: graphs.PaleyGraph(3)
2571
+ Traceback (most recent call last):
2572
+ ...
2573
+ ValueError: parameter q must be congruent to 1 mod 4
2574
+ """
2575
+ from sage.rings.finite_rings.integer_mod import mod
2576
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
2577
+ from sage.arith.misc import is_prime_power
2578
+ if not is_prime_power(q):
2579
+ raise ValueError("parameter q must be a prime power")
2580
+ if not mod(q, 4) == 1:
2581
+ raise ValueError("parameter q must be congruent to 1 mod 4")
2582
+ g = Graph([FiniteField(q, 'a'), lambda i, j: (i - j).is_square()],
2583
+ loops=False, name="Paley graph with parameter {}".format(q))
2584
+ return g
2585
+
2586
+
2587
+ def PasechnikGraph(n):
2588
+ r"""
2589
+ Pasechnik strongly regular graph on `(4n-1)^2` vertices.
2590
+
2591
+ A strongly regular graph with parameters of the orthogonal array graph
2592
+ :func:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`,
2593
+ also known as pseudo Latin squares graph `L_{2n-1}(4n-1)`, constructed from
2594
+ a skew Hadamard matrix of order `4n` following [Pas1992]_.
2595
+
2596
+ .. SEEALSO::
2597
+
2598
+ - :func:`~sage.graphs.strongly_regular_db.is_orthogonal_array_block_graph`
2599
+
2600
+ EXAMPLES::
2601
+
2602
+ sage: graphs.PasechnikGraph(4).is_strongly_regular(parameters=True) # needs sage.combinat sage.modules
2603
+ (225, 98, 43, 42)
2604
+ sage: graphs.PasechnikGraph(5).is_strongly_regular(parameters=True) # long time, needs sage.combinat sage.modules
2605
+ (361, 162, 73, 72)
2606
+ sage: graphs.PasechnikGraph(9).is_strongly_regular(parameters=True) # not tested
2607
+ (1225, 578, 273, 272)
2608
+
2609
+ TESTS::
2610
+
2611
+ sage: graphs.PasechnikGraph(0)
2612
+ Traceback (most recent call last):
2613
+ ...
2614
+ ValueError: parameter n must be >= 1
2615
+ """
2616
+ if n < 1:
2617
+ raise ValueError("parameter n must be >= 1")
2618
+ from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix
2619
+ from sage.matrix.constructor import identity_matrix
2620
+ H = skew_hadamard_matrix(4 * n)
2621
+ M = H[1:].T[1:] - identity_matrix(4 * n - 1)
2622
+ G = Graph(M.tensor_product(M.T), format='seidel_adjacency_matrix')
2623
+ G.relabel()
2624
+ G.name("Pasechnik Graph_{}".format(n))
2625
+ return G
2626
+
2627
+
2628
+ def SquaredSkewHadamardMatrixGraph(n):
2629
+ r"""
2630
+ Pseudo-`OA(2n,4n-1)`-graph from a skew Hadamard matrix of order `4n`.
2631
+
2632
+ A strongly regular graph with parameters of the orthogonal array graph
2633
+ :func:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`,
2634
+ also known as pseudo Latin squares graph `L_{2n}(4n-1)`, constructed from a
2635
+ skew Hadamard matrix of order `4n`, due to Goethals and Seidel, see
2636
+ [BL1984]_.
2637
+
2638
+ .. SEEALSO::
2639
+
2640
+ - :func:`~sage.graphs.strongly_regular_db.is_orthogonal_array_block_graph`
2641
+
2642
+ EXAMPLES::
2643
+
2644
+ sage: # needs sage.combinat sage.modules
2645
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(4)
2646
+ sage: G.is_strongly_regular(parameters=True)
2647
+ (225, 112, 55, 56)
2648
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(5)
2649
+ sage: G.is_strongly_regular(parameters=True) # long time
2650
+ (361, 180, 89, 90)
2651
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(9)
2652
+ sage: G.is_strongly_regular(parameters=True) # not tested
2653
+ (1225, 612, 305, 306)
2654
+
2655
+ TESTS::
2656
+
2657
+ sage: graphs.SquaredSkewHadamardMatrixGraph(0)
2658
+ Traceback (most recent call last):
2659
+ ...
2660
+ ValueError: parameter n must be >= 1
2661
+ """
2662
+ if n < 1:
2663
+ raise ValueError("parameter n must be >= 1")
2664
+ from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix
2665
+ from sage.matrix.constructor import identity_matrix, matrix
2666
+ idm = identity_matrix(4 * n - 1)
2667
+ e = matrix([1] * (4 * n - 1))
2668
+ H = skew_hadamard_matrix(4 * n)
2669
+ M = H[1:].T[1:] - idm
2670
+ s = M.tensor_product(M.T) - idm.tensor_product(e.T * e - idm)
2671
+ G = Graph(s, format='seidel_adjacency_matrix')
2672
+ G.relabel()
2673
+ G.name("skewhad^2_{}".format(n))
2674
+ return G
2675
+
2676
+
2677
+ def SwitchedSquaredSkewHadamardMatrixGraph(n):
2678
+ r"""
2679
+ A strongly regular graph in Seidel switching class of
2680
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.SquaredSkewHadamardMatrixGraph`.
2681
+
2682
+ A strongly regular graph in the :meth:`Seidel switching
2683
+ <Graph.seidel_switching>` class of the disjoint union of a 1-vertex graph
2684
+ and the one produced by :func:`Pseudo-L_{2n}(4n-1)
2685
+ <sage.graphs.graph_generators.GraphGenerators.SquaredSkewHadamardMatrixGraph>`
2686
+
2687
+ In this case, the other possible parameter set of a strongly regular graph
2688
+ in the Seidel switching class of the latter graph (see [BH2012]_) coincides
2689
+ with the set of parameters of the complement of the graph returned by this
2690
+ function.
2691
+
2692
+ .. SEEALSO::
2693
+
2694
+ - :func:`~sage.graphs.strongly_regular_db.is_switch_skewhad`
2695
+
2696
+ EXAMPLES::
2697
+
2698
+ sage: # needs sage.combinat sage.modules
2699
+ sage: g = graphs.SwitchedSquaredSkewHadamardMatrixGraph(4)
2700
+ sage: g.is_strongly_regular(parameters=True)
2701
+ (226, 105, 48, 49)
2702
+ sage: from sage.combinat.designs.twographs import twograph_descendant
2703
+ sage: twograph_descendant(g, 0).is_strongly_regular(parameters=True)
2704
+ (225, 112, 55, 56)
2705
+ sage: gc = g.complement()
2706
+ sage: twograph_descendant(gc, 0).is_strongly_regular(parameters=True)
2707
+ (225, 112, 55, 56)
2708
+
2709
+ TESTS::
2710
+
2711
+ sage: graphs.SwitchedSquaredSkewHadamardMatrixGraph(0)
2712
+ Traceback (most recent call last):
2713
+ ...
2714
+ ValueError: parameter n must be >= 1
2715
+ """
2716
+ G = SquaredSkewHadamardMatrixGraph(n).complement()
2717
+ G.add_vertex((4 * n - 1)**2)
2718
+ G.seidel_switching(list(range((4 * n - 1) * (2 * n - 1))))
2719
+ G.name("switch skewhad^2+*_" + str(n))
2720
+ return G
2721
+
2722
+
2723
+ def HanoiTowerGraph(pegs, disks, labels=True, positions=True):
2724
+ r"""
2725
+ Return the graph whose vertices are the states of the
2726
+ Tower of Hanoi puzzle, with edges representing legal moves between states.
2727
+
2728
+ INPUT:
2729
+
2730
+ - ``pegs`` -- the number of pegs in the puzzle, 2 or greater
2731
+ - ``disks`` -- the number of disks in the puzzle, 1 or greater
2732
+ - ``labels`` -- (default: ``True``) if ``True`` the graph contains
2733
+ more meaningful labels, see explanation below. For large instances,
2734
+ turn off labels for much faster creation of the graph.
2735
+ - ``positions`` -- (default: ``True``) if ``True`` the graph contains
2736
+ layout information. This creates a planar layout for the case
2737
+ of three pegs. For large instances, turn off layout information
2738
+ for much faster creation of the graph.
2739
+
2740
+ OUTPUT:
2741
+
2742
+ The Tower of Hanoi puzzle has a certain number of identical pegs
2743
+ and a certain number of disks, each of a different radius.
2744
+ Initially the disks are all on a single peg, arranged
2745
+ in order of their radii, with the largest on the bottom.
2746
+
2747
+ The goal of the puzzle is to move the disks to any other peg,
2748
+ arranged in the same order. The one constraint is that the
2749
+ disks resident on any one peg must always be arranged with larger
2750
+ radii lower down.
2751
+
2752
+ The vertices of this graph represent all the possible states
2753
+ of this puzzle. Each state of the puzzle is a tuple with length
2754
+ equal to the number of disks, ordered by largest disk first.
2755
+ The entry of the tuple is the peg where that disk resides.
2756
+ Since disks on a given peg must go down in size as we go
2757
+ up the peg, this totally describes the state of the puzzle.
2758
+
2759
+ For example ``(2,0,0)`` means the large disk is on peg 2, the
2760
+ medium disk is on peg 0, and the small disk is on peg 0
2761
+ (and we know the small disk must be above the medium disk).
2762
+ We encode these tuples as integers with a base equal to
2763
+ the number of pegs, and low-order digits to the right.
2764
+
2765
+ Two vertices are adjacent if we can change the puzzle from
2766
+ one state to the other by moving a single disk. For example,
2767
+ ``(2,0,0)`` is adjacent to ``(2,0,1)`` since we can move
2768
+ the small disk off peg 0 and onto (the empty) peg 1.
2769
+ So the solution to a 3-disk puzzle (with at least
2770
+ two pegs) can be expressed by the shortest path between
2771
+ ``(0,0,0)`` and ``(1,1,1)``. For more on this representation
2772
+ of the graph, or its properties, see [AD2010]_.
2773
+
2774
+ For greatest speed we create graphs with integer vertices,
2775
+ where we encode the tuples as integers with a base equal
2776
+ to the number of pegs, and low-order digits to the right.
2777
+ So for example, in a 3-peg puzzle with 5 disks, the
2778
+ state ``(1,2,0,1,1)`` is encoded as
2779
+ `1\ast 3^4 + 2\ast 3^3 + 0\ast 3^2 + 1\ast 3^1 + 1\ast 3^0 = 139`.
2780
+
2781
+ For smaller graphs, the labels that are the tuples are informative,
2782
+ but slow down creation of the graph. Likewise computing layout
2783
+ information also incurs a significant speed penalty. For maximum
2784
+ speed, turn off labels and layout and decode the
2785
+ vertices explicitly as needed. The
2786
+ :meth:`sage.rings.integer.Integer.digits`
2787
+ with the ``padsto`` option is a quick way to do this, though you
2788
+ may want to reverse the list that is output.
2789
+
2790
+ .. SEEALSO::
2791
+
2792
+ - :meth:`~sage.graphs.generators.families.GeneralizedSierpinskiGraph`
2793
+
2794
+ PLOTTING:
2795
+
2796
+ The layout computed when ``positions = True`` will
2797
+ look especially good for the three-peg case, when the graph is known
2798
+ to be planar. Except for two small cases on 4 pegs, the graph is
2799
+ otherwise not planar, and likely there is a better way to layout
2800
+ the vertices.
2801
+
2802
+ EXAMPLES:
2803
+
2804
+ A classic puzzle uses 3 pegs. We solve the 5 disk puzzle using
2805
+ integer labels and report the minimum number of moves required.
2806
+ Note that `3^5-1` is the state where all 5 disks
2807
+ are on peg 2. ::
2808
+
2809
+ sage: H = graphs.HanoiTowerGraph(3, 5, labels=False, positions=False)
2810
+ sage: H.distance(0, 3^5-1)
2811
+ 31
2812
+
2813
+ A slightly larger instance. ::
2814
+
2815
+ sage: H = graphs.HanoiTowerGraph(4, 6, labels=False, positions=False)
2816
+ sage: H.num_verts()
2817
+ 4096
2818
+ sage: H.distance(0, 4^6-1)
2819
+ 17
2820
+
2821
+ For a small graph, labels and layout information can be useful.
2822
+ Here we explicitly list a solution as a list of states. ::
2823
+
2824
+ sage: H = graphs.HanoiTowerGraph(3, 3, labels=True, positions=True)
2825
+ sage: H.shortest_path((0,0,0), (1,1,1))
2826
+ [(0, 0, 0), (0, 0, 1), (0, 2, 1), (0, 2, 2), (1, 2, 2), (1, 2, 0), (1, 1, 0), (1, 1, 1)]
2827
+
2828
+ Some facts about this graph with `p` pegs and `d` disks:
2829
+
2830
+ - only automorphisms are the "obvious" ones -- renumber the pegs
2831
+ - chromatic number is less than or equal to `p`
2832
+ - independence number is `p^{d-1}`
2833
+
2834
+ ::
2835
+
2836
+ sage: H = graphs.HanoiTowerGraph(3, 4, labels=False, positions=False)
2837
+ sage: H.automorphism_group().is_isomorphic(SymmetricGroup(3)) # needs sage.groups
2838
+ True
2839
+ sage: H.chromatic_number() # needs cliquer
2840
+ 3
2841
+ sage: len(H.independent_set()) == 3^(4-1)
2842
+ True
2843
+
2844
+ TESTS:
2845
+
2846
+ It is an error to have just one peg (or less). ::
2847
+
2848
+ sage: graphs.HanoiTowerGraph(1, 5)
2849
+ Traceback (most recent call last):
2850
+ ...
2851
+ ValueError: Pegs for Tower of Hanoi graph should be two or greater (not 1)
2852
+
2853
+ It is an error to have zero disks (or less). ::
2854
+
2855
+ sage: graphs.HanoiTowerGraph(2, 0)
2856
+ Traceback (most recent call last):
2857
+ ...
2858
+ ValueError: Disks for Tower of Hanoi graph should be one or greater (not 0)
2859
+
2860
+ AUTHOR:
2861
+
2862
+ - Rob Beezer, (2009-12-26), with assistance from Su Doree
2863
+ """
2864
+ # sanitize input
2865
+ from sage.rings.integer import Integer
2866
+ pegs = Integer(pegs)
2867
+ if pegs < 2:
2868
+ raise ValueError("Pegs for Tower of Hanoi graph should be two or greater (not %d)" % pegs)
2869
+ disks = Integer(disks)
2870
+ if disks < 1:
2871
+ raise ValueError("Disks for Tower of Hanoi graph should be one or greater (not %d)" % disks)
2872
+
2873
+ # Each state of the puzzle is a tuple with length
2874
+ # equal to the number of disks, ordered by largest disk first
2875
+ # The entry of the tuple is the peg where that disk resides
2876
+ # Since disks on a given peg must go down in size as we go
2877
+ # up the peg, this totally describes the puzzle
2878
+ # We encode these tuples as integers with a base equal to
2879
+ # the number of pegs, and low-order digits to the right
2880
+
2881
+ # complete graph on number of pegs when just a single disk
2882
+ edges = [[i, j] for i in range(pegs) for j in range(i + 1, pegs)]
2883
+
2884
+ nverts = 1
2885
+ for d in range(2, disks+1):
2886
+ prevedges = edges # remember subgraph to build from
2887
+ nverts = pegs*nverts # pegs^(d-1)
2888
+ edges = []
2889
+
2890
+ # Take an edge, change its two states in the same way by adding
2891
+ # a large disk to the bottom of the same peg in each state
2892
+ # This is accomplished by adding a multiple of pegs^(d-1)
2893
+ for p in range(pegs):
2894
+ largedisk = p*nverts
2895
+ for anedge in prevedges:
2896
+ edges.append([anedge[0] + largedisk, anedge[1] + largedisk])
2897
+
2898
+ # Two new states may only differ in the large disk
2899
+ # being the only disk on two different pegs, thus
2900
+ # otherwise being a common state with one less disk
2901
+ # We construct all such pairs of new states and add as edges
2902
+ from sage.combinat.subset import Subsets
2903
+ for state in range(nverts):
2904
+ emptypegs = list(range(pegs))
2905
+ reduced_state = state
2906
+ for i in range(d-1):
2907
+ apeg = reduced_state % pegs
2908
+ if apeg in emptypegs:
2909
+ emptypegs.remove(apeg)
2910
+ reduced_state = reduced_state//pegs
2911
+ for freea, freeb in Subsets(emptypegs, 2):
2912
+ edges.append([freea*nverts + state, freeb*nverts + state])
2913
+
2914
+ H = Graph({}, loops=False, multiedges=False)
2915
+ H.add_edges(edges)
2916
+
2917
+ # Making labels and/or computing positions can take a long time,
2918
+ # relative to just constructing the edges on integer vertices.
2919
+ # We try to minimize coercion overhead, but need Sage
2920
+ # Integers in order to use digits() for labels.
2921
+ # Getting the digits with custom code was no faster.
2922
+ # Layouts are circular (symmetric on the number of pegs)
2923
+ # radiating outward to the number of disks (radius)
2924
+ # Algorithm uses some combination of alternate
2925
+ # clockwise/counterclockwise placements, which
2926
+ # works well for three pegs (planar layout)
2927
+ #
2928
+ if labels or positions:
2929
+ mapping = {}
2930
+ pos = {}
2931
+ a = Integer(-1)
2932
+ one = Integer(1)
2933
+ if positions:
2934
+ radius_multiplier = 1 + 1/sin(pi/pegs)
2935
+ sine = []
2936
+ cosine = []
2937
+ for i in range(pegs):
2938
+ angle = 2*i*pi/float(pegs)
2939
+ sine.append(sin(angle))
2940
+ cosine.append(cos(angle))
2941
+ for i in range(pegs**disks):
2942
+ a += one
2943
+ state = a.digits(base=pegs, padto=disks)
2944
+ if labels:
2945
+ state.reverse()
2946
+ mapping[i] = tuple(state)
2947
+ state.reverse()
2948
+ if positions:
2949
+ locx = 0.0
2950
+ locy = 0.0
2951
+ radius = 1.0
2952
+ parity = -1.0
2953
+ for index in range(disks):
2954
+ p = state[index]
2955
+ radius *= radius_multiplier
2956
+ parity *= -1.0
2957
+ locx_temp = cosine[p]*locx - parity*sine[p]*locy + radius*cosine[p]
2958
+ locy_temp = parity*sine[p]*locx + cosine[p]*locy - radius*parity*sine[p]
2959
+ locx = locx_temp
2960
+ locy = locy_temp
2961
+ pos[i] = (locx, locy)
2962
+ # set positions, then relabel (not vice versa)
2963
+ if positions:
2964
+ H.set_pos(pos)
2965
+ if labels:
2966
+ H.relabel(mapping)
2967
+
2968
+ return H
2969
+
2970
+
2971
+ def line_graph_forbidden_subgraphs():
2972
+ r"""
2973
+ Return the 9 forbidden subgraphs of a line graph.
2974
+
2975
+ See the :wikipedia:`Line_graph` for more information.
2976
+
2977
+ The graphs are returned in the ordering given by the Wikipedia
2978
+ drawing, read from left to right and from top to bottom.
2979
+
2980
+ EXAMPLES::
2981
+
2982
+ sage: graphs.line_graph_forbidden_subgraphs()
2983
+ [Claw graph: Graph on 4 vertices,
2984
+ Graph on 6 vertices,
2985
+ Graph on 6 vertices,
2986
+ Graph on 5 vertices,
2987
+ Graph on 6 vertices,
2988
+ Graph on 6 vertices,
2989
+ Graph on 6 vertices,
2990
+ Graph on 6 vertices,
2991
+ Graph on 5 vertices]
2992
+ """
2993
+ from sage.graphs.graph import Graph
2994
+ from sage.graphs.generators.basic import ClawGraph
2995
+ graphs = [ClawGraph()]
2996
+
2997
+ graphs.append(Graph({
2998
+ 0: [1, 2, 3],
2999
+ 1: [2, 3],
3000
+ 4: [2],
3001
+ 5: [3]
3002
+ }))
3003
+
3004
+ graphs.append(Graph({
3005
+ 0: [1, 2, 3, 4],
3006
+ 1: [2, 3, 4],
3007
+ 3: [4],
3008
+ 2: [5]
3009
+ }))
3010
+
3011
+ graphs.append(Graph({
3012
+ 0: [1, 2, 3],
3013
+ 1: [2, 3],
3014
+ 4: [2, 3]
3015
+ }))
3016
+
3017
+ graphs.append(Graph({
3018
+ 0: [1, 2, 3],
3019
+ 1: [2, 3],
3020
+ 4: [2],
3021
+ 5: [3, 4]
3022
+ }))
3023
+
3024
+ graphs.append(Graph({
3025
+ 0: [1, 2, 3, 4],
3026
+ 1: [2, 3, 4],
3027
+ 3: [4],
3028
+ 5: [2, 0, 1]
3029
+ }))
3030
+
3031
+ graphs.append(Graph({
3032
+ 5: [0, 1, 2, 3, 4],
3033
+ 0: [1, 4],
3034
+ 2: [1, 3],
3035
+ 3: [4]
3036
+ }))
3037
+
3038
+ graphs.append(Graph({
3039
+ 1: [0, 2, 3, 4],
3040
+ 3: [0, 4],
3041
+ 2: [4, 5],
3042
+ 4: [5]
3043
+ }))
3044
+
3045
+ graphs.append(Graph({
3046
+ 0: [1, 2, 3],
3047
+ 1: [2, 3, 4],
3048
+ 2: [3, 4],
3049
+ 3: [4]
3050
+ }))
3051
+
3052
+ return graphs
3053
+
3054
+
3055
+ def petersen_family(generate=False):
3056
+ r"""
3057
+ Return the Petersen family.
3058
+
3059
+ The Petersen family is a collection of 7 graphs which are the forbidden
3060
+ minors of the linklessly embeddable graphs. For more information see the
3061
+ :wikipedia:`Petersen_family`.
3062
+
3063
+ INPUT:
3064
+
3065
+ - ``generate`` -- boolean; whether to generate the family from the
3066
+ `\Delta-Y` transformations. When set to ``False`` (default) a hardcoded
3067
+ version of the graphs (with a prettier layout) is returned.
3068
+
3069
+ EXAMPLES::
3070
+
3071
+ sage: graphs.petersen_family()
3072
+ [Petersen graph: Graph on 10 vertices,
3073
+ Complete graph: Graph on 6 vertices,
3074
+ Multipartite Graph with set sizes [3, 3, 1]: Graph on 7 vertices,
3075
+ Graph on 8 vertices,
3076
+ Graph on 9 vertices,
3077
+ Graph on 7 vertices,
3078
+ Graph on 8 vertices]
3079
+
3080
+ The two different inputs generate the same graphs::
3081
+
3082
+ sage: F1 = graphs.petersen_family(generate=False)
3083
+ sage: F2 = graphs.petersen_family(generate=True) # needs sage.modules
3084
+ sage: F1 = [g.canonical_label().graph6_string() for g in F1]
3085
+ sage: F2 = [g.canonical_label().graph6_string() for g in F2] # needs sage.modules
3086
+ sage: set(F1) == set(F2) # needs sage.modules
3087
+ True
3088
+ """
3089
+ from sage.graphs.generators.smallgraphs import PetersenGraph
3090
+ if not generate:
3091
+ from sage.graphs.generators.basic import CompleteGraph, \
3092
+ CompleteBipartiteGraph, CompleteMultipartiteGraph
3093
+ l = [PetersenGraph(), CompleteGraph(6),
3094
+ CompleteMultipartiteGraph([3, 3, 1])]
3095
+ g = CompleteBipartiteGraph(4, 4)
3096
+ g.delete_edge(0, 4)
3097
+ g.name("")
3098
+ l.append(g)
3099
+ g = Graph('HKN?Yeb')
3100
+ g._circle_embedding([1, 2, 4, 3, 0, 5])
3101
+ g._circle_embedding([6, 7, 8], radius=.6, shift=1.25)
3102
+ l.append(g)
3103
+ g = Graph('Fs\\zw')
3104
+ g._circle_embedding([1, 2, 3])
3105
+ g._circle_embedding([4, 5, 6], radius=.7)
3106
+ g._pos[0] = (0, 0)
3107
+ l.append(g)
3108
+ g = Graph('GYQ[p{')
3109
+ g._circle_embedding([1, 4, 6, 0, 5, 7, 3], shift=0.25)
3110
+ g._pos[2] = (0, 0)
3111
+ l.append(g)
3112
+ return l
3113
+
3114
+ def DeltaYTrans(G, triangle):
3115
+ """
3116
+ Apply a Delta-Y transformation to a given triangle of G.
3117
+ """
3118
+ a, b, c = triangle
3119
+ G = G.copy()
3120
+ G.delete_edges([(a, b), (b, c), (c, a)])
3121
+ v = G.order()
3122
+ G.add_edges([(a, v), (b, v), (c, v)])
3123
+ return G.canonical_label()
3124
+
3125
+ def YDeltaTrans(G, v):
3126
+ """
3127
+ Apply a Y-Delta transformation to a given vertex v of G.
3128
+ """
3129
+ G = G.copy()
3130
+ a, b, c = G.neighbors(v)
3131
+ G.delete_vertex(v)
3132
+ G.add_cycle([a, b, c])
3133
+ return G.canonical_label()
3134
+
3135
+ # We start from the Petersen Graph, and apply Y-Delta transform
3136
+ # for as long as we generate new graphs.
3137
+ P = PetersenGraph()
3138
+
3139
+ l = set([])
3140
+ l_new = [P.canonical_label().graph6_string()]
3141
+
3142
+ while l_new:
3143
+ g = l_new.pop(0)
3144
+ if g in l:
3145
+ continue
3146
+ l.add(g)
3147
+ g = Graph(g)
3148
+ # All possible Delta-Y transforms
3149
+ for t in g.subgraph_search_iterator(Graph({1: [2, 3], 2: [3]}), return_graphs=False):
3150
+ l_new.append(DeltaYTrans(g, t).graph6_string())
3151
+ # All possible Y-Delta transforms
3152
+ for v in g:
3153
+ if g.degree(v) == 3:
3154
+ l_new.append(YDeltaTrans(g, v).graph6_string())
3155
+
3156
+ return [Graph(x) for x in l]
3157
+
3158
+
3159
+ def SierpinskiGasketGraph(n):
3160
+ """
3161
+ Return the Sierpinski Gasket graph of generation `n`.
3162
+
3163
+ All vertices but 3 have valence 4.
3164
+
3165
+ INPUT:
3166
+
3167
+ - ``n`` -- integer
3168
+
3169
+ OUTPUT:
3170
+
3171
+ a graph `S_n` with `3 (3^{n-1}+1)/2` vertices and
3172
+ `3^n` edges, closely related to the famous Sierpinski triangle
3173
+ fractal.
3174
+
3175
+ All these graphs have a triangular shape, and three special
3176
+ vertices at top, bottom left and bottom right. These are the only
3177
+ vertices of valence 2, all the other ones having valence 4.
3178
+
3179
+ The graph `S_1` (generation `1`) is a triangle.
3180
+
3181
+ The graph `S_{n+1}` is obtained from the disjoint union of
3182
+ three copies A,B,C of `S_n` by identifying pairs of vertices:
3183
+ the top vertex of A with the bottom left vertex of B,
3184
+ the bottom right vertex of B with the top vertex of C,
3185
+ and the bottom left vertex of C with the bottom right vertex of A.
3186
+
3187
+ .. PLOT::
3188
+
3189
+ sphinx_plot(graphs.SierpinskiGasketGraph(4).plot(vertex_labels=False))
3190
+
3191
+ .. SEEALSO::
3192
+
3193
+ - :meth:`~sage.graphs.generators.families.HanoiTowerGraph`. There is
3194
+ another family of graphs called Sierpinski graphs, where all vertices
3195
+ but 3 have valence 3. They are available using
3196
+ ``graphs.HanoiTowerGraph(3, n)``.
3197
+ - :meth:`~sage.graphs.generators.families.GeneralizedSierpinskiGraph`
3198
+
3199
+ EXAMPLES::
3200
+
3201
+ sage: # needs sage.modules
3202
+ sage: s4 = graphs.SierpinskiGasketGraph(4); s4
3203
+ Graph on 42 vertices
3204
+ sage: s4.size()
3205
+ 81
3206
+ sage: s4.degree_histogram()
3207
+ [0, 0, 3, 0, 39]
3208
+ sage: s4.is_hamiltonian()
3209
+ True
3210
+
3211
+ REFERENCES:
3212
+
3213
+ [LLWC2011]_
3214
+ """
3215
+ from sage.modules.free_module_element import vector
3216
+ from sage.rings.rational_field import QQ
3217
+
3218
+ if n <= 0:
3219
+ raise ValueError('n should be at least 1')
3220
+
3221
+ def next_step(triangle_list):
3222
+ # compute the next subdivision
3223
+ resu = []
3224
+ for a, b, c in triangle_list:
3225
+ ab = (a + b) / 2
3226
+ bc = (b + c) / 2
3227
+ ac = (a + c) / 2
3228
+ resu += [(a, ab, ac), (ab, b, bc), (ac, bc, c)]
3229
+ return resu
3230
+
3231
+ tri_list = [list(vector(QQ, u) for u in [(0, 0), (0, 1), (1, 0)])]
3232
+ for k in range(n - 1):
3233
+ tri_list = next_step(tri_list)
3234
+ dg = Graph()
3235
+ dg.add_edges([(tuple(a), tuple(b)) for a, b, c in tri_list])
3236
+ dg.add_edges([(tuple(b), tuple(c)) for a, b, c in tri_list])
3237
+ dg.add_edges([(tuple(c), tuple(a)) for a, b, c in tri_list])
3238
+ dg.set_pos({(x, y): (x + y / 2, y * 3 / 4)
3239
+ for (x, y) in dg})
3240
+ dg.relabel()
3241
+ return dg
3242
+
3243
+
3244
+ def GeneralizedSierpinskiGraph(G, k, stretch=None):
3245
+ r"""
3246
+ Return the generalized Sierpinski graph of `G` of dimension `k`.
3247
+
3248
+ Generalized Sierpinski graphs have been introduced in [GKP2011]_ to
3249
+ generalize the notion of Sierpinski graphs [KM1997]_.
3250
+
3251
+ Given a graph `G = (V, E)` of order `n` and a parameter `k`, the generalized
3252
+ Sierpinski graph of `G` of dimension `k`, denoted by `S(G, k)`, can be
3253
+ constructed recursively from `G` as follows. `S(G, 1)` is isomorphic to
3254
+ `G`. To construct `S(G, k)` for `k > 1`, copy `n` times `S(G, k - 1)`, once
3255
+ per vertex `u \in V`, and add `u` at the beginning of the labels of each
3256
+ vertex in the copy of `S(G, k - 1)` corresponding to vertex `u`. Then for
3257
+ any edge `\{u, v\} \in E`, add an edge between vertex `(u, v, \ldots, v)`
3258
+ and vertex `(v, u, \ldots, u)`.
3259
+
3260
+ INPUT:
3261
+
3262
+ - ``G`` -- a sage Graph
3263
+
3264
+ - ``k`` -- integer; the dimension
3265
+
3266
+ - ``stretch`` -- integer (default: ``None``); stretching factor used to
3267
+ determine the positions of the vertices of the output graph. By default
3268
+ (``None``), this value is set to twice the maximum Euclidean distance
3269
+ between the vertices of `G`. This parameter is used only when the vertices
3270
+ of `G` have positions.
3271
+
3272
+ .. SEEALSO::
3273
+
3274
+ - :meth:`~sage.graphs.generators.families.SierpinskiGasketGraph`
3275
+ - :meth:`~sage.graphs.generators.families.HanoiTowerGraph`
3276
+
3277
+ EXAMPLES:
3278
+
3279
+ The generalized Sierpinski graph of dimension 1 of any graph `G`
3280
+ is isomorphic to `G`::
3281
+
3282
+ sage: G = graphs.RandomGNP(10, .5)
3283
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, 1)
3284
+ sage: S.is_isomorphic(G)
3285
+ True
3286
+
3287
+ When `G` is a clique of order 3, the generalized Sierpinski graphs
3288
+ of `G` are isomorphic to Hanoi Tower graphs::
3289
+
3290
+ sage: k = randint(1, 5)
3291
+ sage: S = graphs.GeneralizedSierpinskiGraph(graphs.CompleteGraph(3), k) # needs sage.modules
3292
+ sage: H = graphs.HanoiTowerGraph(3, k)
3293
+ sage: S.is_isomorphic(H) # needs sage.modules
3294
+ True
3295
+
3296
+ The generalized Sierpinski graph of dimension `k` of any graph `G` with `n`
3297
+ vertices and `m` edges has `n^k` vertices and `m\sum_{i=0}^{k-1}n^i` edges::
3298
+
3299
+ sage: # needs sage.modules
3300
+ sage: n = randint(2, 6)
3301
+ sage: k = randint(1, 5)
3302
+ sage: G = graphs.RandomGNP(n, .5)
3303
+ sage: m = G.size()
3304
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, k)
3305
+ sage: S.order() == n**k
3306
+ True
3307
+ sage: S.size() == m*sum([n**i for i in range(k)])
3308
+ True
3309
+ sage: G = graphs.CompleteGraph(n)
3310
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, k)
3311
+ sage: S.order() == n**k
3312
+ True
3313
+ sage: S.size() == (n*(n - 1)/2)*sum([n**i for i in range(k)])
3314
+ True
3315
+
3316
+ The positions of the vertices of the output graph are determined from the
3317
+ positions of the vertices of `G`, if any::
3318
+
3319
+ sage: G = graphs.HouseGraph()
3320
+ sage: G.get_pos() is not None
3321
+ True
3322
+ sage: H = graphs.GeneralizedSierpinskiGraph(G, 2) # needs sage.symbolic
3323
+ sage: H.get_pos() is not None # needs sage.symbolic
3324
+ True
3325
+ sage: G = Graph([(0, 1)])
3326
+ sage: G.get_pos() is not None
3327
+ False
3328
+ sage: H = graphs.GeneralizedSierpinskiGraph(G, 2) # needs sage.symbolic
3329
+ sage: H.get_pos() is not None # needs sage.symbolic
3330
+ False
3331
+
3332
+ .. PLOT::
3333
+
3334
+ sphinx_plot(graphs.GeneralizedSierpinskiGraph(graphs.HouseGraph(), 2).plot(vertex_labels=False))
3335
+
3336
+ TESTS::
3337
+
3338
+ sage: # needs sage.modules
3339
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(), 3)
3340
+ Generalized Sierpinski Graph of Graph on 0 vertices of dimension 3: Graph on 0 vertices
3341
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(1), 3).vertices(sort=False)
3342
+ [(0, 0, 0)]
3343
+ sage: G = graphs.GeneralizedSierpinskiGraph(Graph(2), 3)
3344
+ sage: G.order(), G.size()
3345
+ (8, 0)
3346
+ sage: graphs.GeneralizedSierpinskiGraph("foo", 1)
3347
+ Traceback (most recent call last):
3348
+ ...
3349
+ ValueError: parameter G must be a Graph
3350
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(), 0)
3351
+ Traceback (most recent call last):
3352
+ ...
3353
+ ValueError: parameter k must be >= 1
3354
+ """
3355
+ if not isinstance(G, Graph):
3356
+ raise ValueError("parameter G must be a Graph")
3357
+ if k < 1:
3358
+ raise ValueError("parameter k must be >= 1")
3359
+ loops = G.allows_loops()
3360
+ multiedges = G.allows_multiple_edges()
3361
+
3362
+ def rec(H, kk):
3363
+ if kk == 1:
3364
+ return H
3365
+ I = Graph(loops=loops, multiedges=multiedges)
3366
+ # add one copy of H per vertex of G
3367
+ for i in G:
3368
+ J = H.relabel(perm={u: (i,) + u for u in H}, inplace=False)
3369
+ I.add_vertices(J)
3370
+ I.add_edges(J.edge_iterator(labels=False, sort_vertices=False))
3371
+ # For each edge {u, v} of G, add edge {(u, v, ..., v), (v, u, ..., u)}
3372
+ l = len(next(H.vertex_iterator()))
3373
+ for u, v in G.edges(sort=True, labels=False):
3374
+ I.add_edge((u,) + (v,)*l, (v,) + (u,)*l)
3375
+ return rec(I, kk - 1)
3376
+
3377
+ H = G.relabel(perm={u: (u,) for u in G}, inplace=False)
3378
+ if H and k > 1:
3379
+ H = rec(H, k)
3380
+ H.name("Generalized Sierpinski Graph of {} of dimension {}".format(G, k))
3381
+
3382
+ # If the vertices of G have positions, we set the positions of vertices of H
3383
+ pos = G.get_pos()
3384
+ if pos:
3385
+ if stretch is None:
3386
+ # Find the geometric diameter
3387
+ from sage.modules.free_module_element import vector
3388
+ L = [vector(p) for p in pos.values()]
3389
+ stretch = 2 * max((u - v).norm() for u, v in combinations(L, 2))
3390
+
3391
+ H.set_pos({u: (sum(pos[x][0]*stretch**(k-i) for i, x in enumerate(u)),
3392
+ sum(pos[y][1]*stretch**(k-i) for i, y in enumerate(u)))
3393
+ for u in H})
3394
+ return H
3395
+
3396
+
3397
+ def WheelGraph(n):
3398
+ """
3399
+ Return a Wheel graph with `n` nodes.
3400
+
3401
+ A Wheel graph is a basic structure where one node is connected to all other
3402
+ nodes and those (outer) nodes are connected cyclically.
3403
+
3404
+ PLOTTING: Upon construction, the position dictionary is filled to override
3405
+ the spring-layout algorithm. By convention, each wheel graph will be
3406
+ displayed with the first (0) node in the center, the second node at the top,
3407
+ and the rest following in a counterclockwise manner.
3408
+
3409
+ With the wheel graph, we see that it doesn't take a very large `n` at all
3410
+ for the spring-layout to give a counter-intuitive display. (See Graphics
3411
+ Array examples below).
3412
+
3413
+ EXAMPLES:
3414
+
3415
+ We view many wheel graphs with a Sage Graphics Array, first with this
3416
+ constructor (i.e., the position dictionary filled)::
3417
+
3418
+ sage: # needs sage.plot
3419
+ sage: g = []
3420
+ sage: j = []
3421
+ sage: for i in range(9):
3422
+ ....: k = graphs.WheelGraph(i+3)
3423
+ ....: g.append(k)
3424
+ ...
3425
+ sage: for i in range(3):
3426
+ ....: n = []
3427
+ ....: for m in range(3):
3428
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
3429
+ ....: j.append(n)
3430
+ ...
3431
+ sage: G = graphics_array(j)
3432
+ sage: G.show() # long time
3433
+
3434
+ Next, using the spring-layout algorithm::
3435
+
3436
+ sage: # needs networkx sage.plot
3437
+ sage: import networkx
3438
+ sage: g = []
3439
+ sage: j = []
3440
+ sage: for i in range(9):
3441
+ ....: spr = networkx.wheel_graph(i+3)
3442
+ ....: k = Graph(spr)
3443
+ ....: g.append(k)
3444
+ ...
3445
+ sage: for i in range(3):
3446
+ ....: n = []
3447
+ ....: for m in range(3):
3448
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
3449
+ ....: j.append(n)
3450
+ ...
3451
+ sage: G = graphics_array(j)
3452
+ sage: G.show() # long time
3453
+
3454
+ Compare the plotting::
3455
+
3456
+ sage: # needs networkx sage.plot
3457
+ sage: n = networkx.wheel_graph(23)
3458
+ sage: spring23 = Graph(n)
3459
+ sage: posdict23 = graphs.WheelGraph(23)
3460
+ sage: spring23.show() # long time
3461
+ sage: posdict23.show() # long time
3462
+ """
3463
+ from sage.graphs.generators.basic import CycleGraph
3464
+ if n < 4:
3465
+ G = CycleGraph(n)
3466
+ else:
3467
+ G = CycleGraph(n-1)
3468
+ G.relabel(perm=list(range(1, n)), inplace=True)
3469
+ G.add_edges([(0, i) for i in range(1, n)])
3470
+ G._pos[0] = (0, 0)
3471
+ G.name("Wheel graph")
3472
+ return G
3473
+
3474
+
3475
+ def WindmillGraph(k, n):
3476
+ r"""
3477
+ Return the Windmill graph `Wd(k, n)`.
3478
+
3479
+ The windmill graph `Wd(k, n)` is an undirected graph constructed for `k \geq
3480
+ 2` and `n \geq 2` by joining `n` copies of the complete graph `K_k` at a
3481
+ shared vertex. It has `(k-1)n+1` vertices and `nk(k-1)/2` edges, girth 3 (if
3482
+ `k > 2`), radius 1 and diameter 2. It has vertex connectivity 1 because its
3483
+ central vertex is an articulation point; however, like the complete graphs
3484
+ from which it is formed, it is `(k-1)`-edge-connected. It is trivially
3485
+ perfect and a block graph.
3486
+
3487
+ .. SEEALSO::
3488
+
3489
+ - :wikipedia:`Windmill_graph`
3490
+ - :meth:`GraphGenerators.StarGraph`
3491
+ - :meth:`GraphGenerators.FriendshipGraph`
3492
+
3493
+ EXAMPLES:
3494
+
3495
+ The Windmill graph `Wd(2, n)` is a star graph::
3496
+
3497
+ sage: n = 5
3498
+ sage: W = graphs.WindmillGraph(2, n)
3499
+ sage: W.is_isomorphic( graphs.StarGraph(n) )
3500
+ True
3501
+
3502
+ The Windmill graph `Wd(3, n)` is the Friendship graph `F_n`::
3503
+
3504
+ sage: n = 5
3505
+ sage: W = graphs.WindmillGraph(3, n)
3506
+ sage: W.is_isomorphic( graphs.FriendshipGraph(n) )
3507
+ True
3508
+
3509
+ The Windmill graph `Wd(3, 2)` is the Butterfly graph::
3510
+
3511
+ sage: W = graphs.WindmillGraph(3, 2)
3512
+ sage: W.is_isomorphic( graphs.ButterflyGraph() )
3513
+ True
3514
+
3515
+ The Windmill graph `Wd(k, n)` has chromatic number `k`::
3516
+
3517
+ sage: n,k = 5,6
3518
+ sage: W = graphs.WindmillGraph(k, n)
3519
+ sage: W.chromatic_number() == k # needs cliquer
3520
+ True
3521
+
3522
+ TESTS:
3523
+
3524
+ Giving too small parameters::
3525
+
3526
+ sage: graphs.WindmillGraph(1, 2)
3527
+ Traceback (most recent call last):
3528
+ ...
3529
+ ValueError: parameters k and n must be >= 2
3530
+ sage: graphs.WindmillGraph(2, 1)
3531
+ Traceback (most recent call last):
3532
+ ...
3533
+ ValueError: parameters k and n must be >= 2
3534
+ """
3535
+ if k < 2 or n < 2:
3536
+ raise ValueError('parameters k and n must be >= 2')
3537
+
3538
+ if k == 2:
3539
+ from sage.graphs.generators.basic import StarGraph
3540
+ G = StarGraph(n)
3541
+ else:
3542
+ sector = 2*pi/n
3543
+ slide = 1/sin(sector/4)
3544
+
3545
+ pos_dict = {}
3546
+ for i in range(0, k):
3547
+ x = float(cos(i*pi/(k-2)))
3548
+ y = float(sin(i*pi/(k-2))) + slide
3549
+ pos_dict[i] = (x, y)
3550
+
3551
+ G = Graph()
3552
+ pos = {0: [0, 0]}
3553
+ for i in range(n):
3554
+ V = list(range(i*(k - 1) + 1, (i + 1)*(k - 1) + 1))
3555
+ G.add_clique([0]+V)
3556
+ for j, v in enumerate(V):
3557
+ x, y = pos_dict[j]
3558
+ xv = x*cos(i*sector) - y*sin(i*sector)
3559
+ yv = x*sin(i*sector) + y*cos(i*sector)
3560
+ pos[v] = [xv, yv]
3561
+
3562
+ G.set_pos(pos)
3563
+
3564
+ G.name("Windmill graph Wd({}, {})".format(k, n))
3565
+ return G
3566
+
3567
+
3568
+ def trees(vertices):
3569
+ r"""
3570
+ Return a generator of the distinct trees on a fixed number of vertices.
3571
+
3572
+ INPUT:
3573
+
3574
+ - ``vertices`` -- the size of the trees created
3575
+
3576
+ OUTPUT:
3577
+
3578
+ A generator which creates an exhaustive, duplicate-free listing
3579
+ of the connected free (unlabeled) trees with ``vertices`` number
3580
+ of vertices. A tree is a graph with no cycles.
3581
+
3582
+ ALGORITHM:
3583
+
3584
+ Uses an algorithm that generates each new tree
3585
+ in constant time. See the documentation for, and implementation
3586
+ of, the :mod:`sage.graphs.trees` module, including a citation.
3587
+
3588
+ EXAMPLES:
3589
+
3590
+ We create an iterator, then loop over its elements. ::
3591
+
3592
+ sage: tree_iterator = graphs.trees(7)
3593
+ sage: for T in tree_iterator:
3594
+ ....: print(T.degree_sequence())
3595
+ [2, 2, 2, 2, 2, 1, 1]
3596
+ [3, 2, 2, 2, 1, 1, 1]
3597
+ [3, 2, 2, 2, 1, 1, 1]
3598
+ [4, 2, 2, 1, 1, 1, 1]
3599
+ [3, 3, 2, 1, 1, 1, 1]
3600
+ [3, 3, 2, 1, 1, 1, 1]
3601
+ [4, 3, 1, 1, 1, 1, 1]
3602
+ [3, 2, 2, 2, 1, 1, 1]
3603
+ [4, 2, 2, 1, 1, 1, 1]
3604
+ [5, 2, 1, 1, 1, 1, 1]
3605
+ [6, 1, 1, 1, 1, 1, 1]
3606
+
3607
+ The number of trees on the first few vertex counts.
3608
+ This is sequence A000055 in Sloane's OEIS. ::
3609
+
3610
+ sage: [len(list(graphs.trees(i))) for i in range(0, 15)]
3611
+ [1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
3612
+ """
3613
+ from sage.graphs.trees import TreeIterator
3614
+ return iter(TreeIterator(vertices))
3615
+
3616
+
3617
+ def nauty_gentreeg(options='', debug=False):
3618
+ r"""
3619
+ Return a generator which creates non-isomorphic trees from nauty's gentreeg
3620
+ program.
3621
+
3622
+ INPUT:
3623
+
3624
+ - ``options`` -- string (default: ``""``); a string passed to ``gentreeg``
3625
+ as if it was run at a system command line. At a minimum, you *must* pass
3626
+ the number of vertices you desire. Sage expects the graphs to be in
3627
+ nauty's "sparse6" format, do not set an option to change this default or
3628
+ results will be unpredictable.
3629
+
3630
+ - ``debug`` -- boolean (default: ``False``); if ``True`` the first line of
3631
+ ``gentreeg``'s output to standard error is captured and the first call to
3632
+ the generator's ``next()`` function will return this line as a string. A
3633
+ line leading with ">A" indicates a successful initiation of the program
3634
+ with some information on the arguments, while a line beginning with ">E"
3635
+ indicates an error with the input.
3636
+
3637
+ The possible options, obtained as output of ``gentreeg -help``::
3638
+
3639
+ n : the number of vertices. Must be in range 1..128
3640
+ res/mod : only generate subset res out of subsets 0..mod-1
3641
+ -D<int> : an upper bound for the maximum degree
3642
+ -Z<int>:<int> : bounds on the diameter
3643
+ -q : suppress auxiliary output
3644
+
3645
+ Options which cause ``gentreeg`` to use an output format different than the
3646
+ sparse6 format are not listed above (-p, -l, -u) as they will confuse the
3647
+ creation of a Sage graph. The res/mod option can be useful when using the
3648
+ output in a routine run several times in parallel.
3649
+
3650
+ OUTPUT:
3651
+
3652
+ A generator which will produce the graphs as Sage graphs. These will be
3653
+ simple graphs: no loops, no multiple edges, no directed edges.
3654
+
3655
+ .. SEEALSO::
3656
+
3657
+ :meth:`trees` -- another generator of trees
3658
+
3659
+ EXAMPLES:
3660
+
3661
+ The generator can be used to construct trees for testing, one at a time
3662
+ (usually inside a loop). Or it can be used to create an entire list all at
3663
+ once if there is sufficient memory to contain it::
3664
+
3665
+ sage: # needs nauty
3666
+ sage: gen = graphs.nauty_gentreeg("4")
3667
+ sage: next(gen)
3668
+ Graph on 4 vertices
3669
+ sage: next(gen)
3670
+ Graph on 4 vertices
3671
+ sage: next(gen)
3672
+ Traceback (most recent call last):
3673
+ ...
3674
+ StopIteration
3675
+
3676
+ The number of trees on the first few vertex counts. This agrees with
3677
+ :oeis:`A000055`::
3678
+
3679
+ sage: [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(1, 15)] # needs nauty
3680
+ [1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
3681
+
3682
+ The ``debug`` switch can be used to examine ``gentreeg``'s reaction to the
3683
+ input in the ``options`` string. We illustrate success. (A failure will be
3684
+ a string beginning with ">E".) Passing the "-q" switch to ``gentreeg`` will
3685
+ suppress the indicator of a successful initiation, and so the first returned
3686
+ value might be an empty string if ``debug`` is ``True``::
3687
+
3688
+ sage: # needs nauty
3689
+ sage: gen = graphs.nauty_gentreeg("4", debug=True)
3690
+ sage: print(next(gen))
3691
+ >A ...gentreeg ...
3692
+ sage: gen = graphs.nauty_gentreeg("4 -q", debug=True)
3693
+ sage: next(gen)
3694
+ ''
3695
+
3696
+ TESTS:
3697
+
3698
+ The number `n` of vertices must be in range 1..128::
3699
+
3700
+ sage: # needs nauty
3701
+ sage: list(graphs.nauty_gentreeg("0", debug=False))
3702
+ Traceback (most recent call last):
3703
+ ...
3704
+ ValueError: wrong format of parameter options
3705
+ sage: list(graphs.nauty_gentreeg("0", debug=True))
3706
+ ['>E gentreeg: n must be in the range 1..128\n']
3707
+ sage: list(graphs.nauty_gentreeg("200", debug=True))
3708
+ ['>E gentreeg: n must be in the range 1..128\n']
3709
+
3710
+ Wrong input::
3711
+
3712
+ sage: # needs nauty
3713
+ sage: list(graphs.nauty_gentreeg("3 -x", debug=False))
3714
+ Traceback (most recent call last):
3715
+ ...
3716
+ ValueError: wrong format of parameter options
3717
+ sage: list(graphs.nauty_gentreeg("3 -x", debug=True))
3718
+ ['>E Usage: ...gentreeg [-D#] [-Z#:#] [-ulps] [-q] n... [res/mod] ...
3719
+ sage: list(graphs.nauty_gentreeg("3", debug=True))
3720
+ ['>A ...gentreeg ...\n', Graph on 3 vertices]
3721
+ """
3722
+ import shlex
3723
+ from sage.features.nauty import NautyExecutable
3724
+ gen_path = NautyExecutable("gentreeg").absolute_filename()
3725
+ sp = subprocess.Popen(shlex.quote(gen_path) + " {0}".format(options), shell=True,
3726
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3727
+ stderr=subprocess.PIPE, close_fds=True,
3728
+ encoding='latin-1')
3729
+ msg = sp.stderr.readline()
3730
+ if debug:
3731
+ yield msg
3732
+ elif msg.startswith('>E'):
3733
+ raise ValueError('wrong format of parameter options')
3734
+ gen = sp.stdout
3735
+ while True:
3736
+ try:
3737
+ s = next(gen)
3738
+ except StopIteration:
3739
+ # Exhausted list of graphs from nauty geng
3740
+ return
3741
+ G = Graph(s[:-1], format='sparse6', loops=False, multiedges=False)
3742
+ yield G
3743
+
3744
+
3745
+ def RingedTree(k, vertex_labels=True):
3746
+ r"""
3747
+ Return the ringed tree on k-levels.
3748
+
3749
+ A ringed tree of level `k` is a binary tree with `k` levels (counting
3750
+ the root as a level), in which all vertices at the same level are connected
3751
+ by a ring.
3752
+
3753
+ More precisely, in each layer of the binary tree (i.e. a layer is the set of
3754
+ vertices `[2^i...2^{i+1}-1]`) two vertices `u,v` are adjacent if `u=v+1` or
3755
+ if `u=2^i` and `v=2^{i+1}-1`.
3756
+
3757
+ Ringed trees are defined in [CFHM2013]_.
3758
+
3759
+ INPUT:
3760
+
3761
+ - ``k`` -- the number of levels of the ringed tree
3762
+
3763
+ - ``vertex_labels`` -- boolean; whether to label vertices as binary words
3764
+ (default) or as integers
3765
+
3766
+ EXAMPLES::
3767
+
3768
+ sage: # needs networkx
3769
+ sage: G = graphs.RingedTree(5)
3770
+ sage: P = G.plot(vertex_labels=False, vertex_size=10) # needs sage.plot
3771
+ sage: P.show() # long time # needs sage.plot
3772
+ sage: G.vertices(sort=True)
3773
+ ['', '0', '00', '000', '0000', '0001', '001', '0010', '0011', '01',
3774
+ '010', '0100', '0101', '011', '0110', '0111', '1', '10', '100',
3775
+ '1000', '1001', '101', '1010', '1011', '11', '110', '1100', '1101',
3776
+ '111', '1110', '1111']
3777
+
3778
+ TESTS::
3779
+
3780
+ sage: G = graphs.RingedTree(-1)
3781
+ Traceback (most recent call last):
3782
+ ...
3783
+ ValueError: The number of levels must be >= 1.
3784
+ sage: G = graphs.RingedTree(5, vertex_labels=False) # needs networkx
3785
+ sage: G.vertices(sort=True) # needs networkx
3786
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
3787
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
3788
+ """
3789
+ if k < 1:
3790
+ raise ValueError('The number of levels must be >= 1.')
3791
+
3792
+ # Creating the Balanced tree, which contains most edges already
3793
+ g = BalancedTree(2, k - 1)
3794
+ g.name('Ringed Tree on ' + str(k) + ' levels')
3795
+
3796
+ # We consider edges layer by layer
3797
+ for i in range(1, k):
3798
+ vertices = list(range(2**(i) - 1, 2**(i + 1) - 1))
3799
+
3800
+ # Add the missing edges
3801
+ g.add_cycle(vertices)
3802
+
3803
+ # And set the vertices' positions
3804
+ radius = i if i <= 1 else 1.5**i
3805
+ shift = -2**(i - 2) + .5 if i > 1 else 0
3806
+ g._circle_embedding(vertices, radius=radius, shift=shift)
3807
+
3808
+ # Specific position for the central vertex
3809
+ g._pos[0] = (0, 0.2)
3810
+
3811
+ # Relabel vertices as binary words
3812
+ if not vertex_labels:
3813
+ return g
3814
+
3815
+ vertices = ['']
3816
+ for i in range(k - 1):
3817
+ for j in range(2**(i) - 1, 2**(i + 1) - 1):
3818
+ v = vertices[j]
3819
+ vertices.append(v + '0')
3820
+ vertices.append(v + '1')
3821
+
3822
+ g.relabel(vertices)
3823
+
3824
+ return g
3825
+
3826
+
3827
+ def MathonPseudocyclicMergingGraph(M, t):
3828
+ r"""
3829
+ Mathon's merging of classes in a pseudo-cyclic 3-class association scheme.
3830
+
3831
+ Construct strongly regular graphs from p.97 of [BL1984]_.
3832
+
3833
+ INPUT:
3834
+
3835
+ - ``M`` -- the list of matrices in a pseudo-cyclic 3-class association scheme;
3836
+ the identity matrix must be the first entry
3837
+
3838
+ - ``t`` -- integer; the number of the graph, from 0 to 2
3839
+
3840
+ .. SEEALSO::
3841
+
3842
+ - :func:`~sage.graphs.strongly_regular_db.is_muzychuk_S6`
3843
+
3844
+ TESTS::
3845
+
3846
+ sage: from sage.graphs.generators.families import MathonPseudocyclicMergingGraph as mer
3847
+ sage: from sage.graphs.generators.smallgraphs import _EllipticLinesProjectivePlaneScheme as ES
3848
+
3849
+ sage: # long time, needs sage.libs.gap
3850
+ sage: G = mer(ES(3), 0)
3851
+ sage: G.is_strongly_regular(parameters=True)
3852
+ (784, 243, 82, 72)
3853
+ sage: G = mer(ES(3), 1)
3854
+ sage: G.is_strongly_regular(parameters=True)
3855
+ (784, 270, 98, 90)
3856
+ sage: G = mer(ES(3), 2)
3857
+ sage: G.is_strongly_regular(parameters=True)
3858
+ (784, 297, 116, 110)
3859
+ sage: G = mer(ES(2), 2)
3860
+ Traceback (most recent call last):
3861
+ ...
3862
+ AssertionError...
3863
+
3864
+ sage: # needs sage.libs.gap
3865
+ sage: M = ES(3)
3866
+ sage: M = [M[1],M[0],M[2],M[3]]
3867
+ sage: G = mer(M, 2)
3868
+ Traceback (most recent call last):
3869
+ ...
3870
+ AssertionError...
3871
+ """
3872
+ from sage.graphs.graph import Graph
3873
+ from sage.matrix.constructor import identity_matrix
3874
+ assert len(M) == 4
3875
+ assert M[0] == identity_matrix(M[0].nrows())
3876
+ A = sum(x.tensor_product(x) for x in M[1:])
3877
+ if t > 0:
3878
+ A += sum(x.tensor_product(M[0]) for x in M[1:])
3879
+ if t > 1:
3880
+ A += sum(M[0].tensor_product(x) for x in M[1:])
3881
+ return Graph(A)
3882
+
3883
+
3884
+ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None):
3885
+ r"""
3886
+ Return a strongly regular graph on `(4t+1)(4t-1)^2` vertices from
3887
+ [Mat1978]_.
3888
+
3889
+ Let `4t-1` be a prime power, and `4t+1` be such that there exists
3890
+ a strongly regular graph `G` with parameters `(4t+1,2t,t-1,t)`. In
3891
+ particular, `4t+1` must be a sum of two squares [Mat1978]_. With
3892
+ this input, Mathon [Mat1978]_ gives a construction of a strongly regular
3893
+ graph with parameters `(4 \mu + 1, 2 \mu, \mu-1, \mu)`, where
3894
+ `\mu = t(4t(4t-1)-1)`. The construction is optionally parametrised by an
3895
+ a skew-symmetric Latin square of order `4t+1`, with entries in
3896
+ `-2t,...,-1,0,1,...,2t`.
3897
+
3898
+ Our implementation follows a description given in [ST1981]_.
3899
+
3900
+ INPUT:
3901
+
3902
+ - ``t`` -- positive integer
3903
+
3904
+ - ``G`` -- if ``None`` (default), try to construct the necessary graph
3905
+ with parameters `(4t+1,2t,t-1,t)`, otherwise use the user-supplied one,
3906
+ with vertices labelled from `0` to `4t`.
3907
+
3908
+ - ``L`` -- if ``None`` (default), construct a necessary skew Latin square,
3909
+ otherwise use the user-supplied one. Here non-isomorphic Latin squares
3910
+ -- one constructed from `Z/9Z`, and the other from `(Z/3Z)^2` --
3911
+ lead to non-isomorphic graphs.
3912
+
3913
+ .. SEEALSO::
3914
+
3915
+ - :func:`~sage.graphs.strongly_regular_db.is_mathon_PC_srg`
3916
+
3917
+ EXAMPLES:
3918
+
3919
+ Using default ``G`` and ``L``. ::
3920
+
3921
+ sage: from sage.graphs.generators.families import MathonPseudocyclicStronglyRegularGraph
3922
+ sage: G = MathonPseudocyclicStronglyRegularGraph(1); G # needs database_graphs sage.modules sage.rings.finite_rings
3923
+ Mathon's PC SRG on 45 vertices: Graph on 45 vertices
3924
+ sage: G.is_strongly_regular(parameters=True) # needs database_graphs sage.modules sage.rings.finite_rings
3925
+ (45, 22, 10, 11)
3926
+
3927
+ Supplying ``G`` and ``L`` (constructed from the automorphism group
3928
+ of ``G``). The entries of L can't be tested directly because
3929
+ there's some unpredictability in the way that GAP chooses a
3930
+ representative in ``NormalSubgroups()``, the function that
3931
+ underlies our own
3932
+ :meth:`~sage.groups.perm_gps.permgroup.PermutationGroup_generic.normal_subgroups`
3933
+ method::
3934
+
3935
+ sage: # needs sage.groups sage.libs.gap sage.rings.finite_rings
3936
+ sage: G = graphs.PaleyGraph(9)
3937
+ sage: a = G.automorphism_group(partition=[sorted(G)])
3938
+ sage: it = (x for x in a.normal_subgroups() if x.order() == 9)
3939
+ sage: subg = next(iter(it))
3940
+ sage: r = [matrix(libgap.PermutationMat(libgap(z), 9).sage())
3941
+ ....: for z in subg]
3942
+ sage: ff = list(map(lambda y: (y[0]-1,y[1]-1),
3943
+ ....: Permutation(map(lambda x: 1+r.index(x^-1), r)).cycle_tuples()[1:]))
3944
+ sage: L = sum(i*(r[a]-r[b]) for i,(a,b) in zip(range(1,len(ff)+1), ff))
3945
+ sage: G.relabel(range(9))
3946
+ sage: G3x3 = graphs.MathonPseudocyclicStronglyRegularGraph(2, G=G, L=L)
3947
+ sage: G3x3.is_strongly_regular(parameters=True)
3948
+ (441, 220, 109, 110)
3949
+ sage: G3x3.automorphism_group(algorithm='bliss').order() # optional - bliss
3950
+ 27
3951
+ sage: G9 = graphs.MathonPseudocyclicStronglyRegularGraph(2)
3952
+ sage: G9.is_strongly_regular(parameters=True)
3953
+ (441, 220, 109, 110)
3954
+ sage: G9.automorphism_group(algorithm='bliss').order() # optional - bliss
3955
+ 9
3956
+
3957
+ TESTS::
3958
+
3959
+ sage: graphs.MathonPseudocyclicStronglyRegularGraph(5) # needs sage.modules
3960
+ Traceback (most recent call last):
3961
+ ...
3962
+ ValueError: 21 must be a sum of two squares!...
3963
+ """
3964
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF
3965
+ from sage.rings.integer_ring import ZZ
3966
+ from sage.matrix.constructor import matrix, block_matrix, \
3967
+ ones_matrix, identity_matrix
3968
+ from sage.arith.misc import two_squares
3969
+ p = 4*t + 1
3970
+ try:
3971
+ x = two_squares(p)
3972
+ except ValueError:
3973
+ raise ValueError(str(p)+" must be a sum of two squares!")
3974
+ if G is None:
3975
+ from sage.graphs.strongly_regular_db import strongly_regular_graph as SRG
3976
+ G = SRG(p, 2*t, t - 1)
3977
+ G.relabel(range(p))
3978
+ if L is None:
3979
+ from sage.matrix.constructor import circulant
3980
+ L = circulant(list(range(2 * t + 1)) + list(range(-2 * t, 0)))
3981
+ q = 4*t - 1
3982
+ K = GF(q, prefix='x')
3983
+ K_pairs = set(frozenset([x, -x]) for x in K)
3984
+ K_pairs.discard(frozenset([0]))
3985
+ a = [None]*(q-1) # order the non-0 elements of K as required
3986
+ for i, (x, y) in enumerate(K_pairs):
3987
+ a[i] = x
3988
+ a[-i-1] = y
3989
+ a.append(K(0)) # and append the 0 of K at the end
3990
+ P = [matrix(ZZ, q, q, lambda i, j: 1 if a[j] == a[i] + b else 0)
3991
+ for b in a]
3992
+ g = K.primitive_element()
3993
+ F = sum(P[a.index(g**(2*i))] for i in range(1, 2*t))
3994
+ E = matrix(ZZ, q, q, lambda i, j: 0 if (a[j] - a[0]).is_square() else 1)
3995
+
3996
+ def B(m):
3997
+ I = identity_matrix(q)
3998
+ J = ones_matrix(q)
3999
+ if m == 0:
4000
+ def f(i, j):
4001
+ if i == j:
4002
+ return 0 * I
4003
+ elif (a[j] - a[i]).is_square():
4004
+ return I + F
4005
+ else:
4006
+ return J - F
4007
+ elif m < 2*t:
4008
+ def f(i, j):
4009
+ return F * P[a.index(g**(2*m) * (a[i] + a[j]))]
4010
+ elif m == 2*t:
4011
+ def f(i, j):
4012
+ return E * P[i]
4013
+ return block_matrix(q, q, [f(i, j) for i in range(q) for j in range(q)])
4014
+
4015
+ def Acon(i, j):
4016
+ J = ones_matrix(q**2)
4017
+ if i == j:
4018
+ return B(0)
4019
+ if L[i, j] > 0:
4020
+ if G.has_edge(i, j):
4021
+ return B(L[i, j])
4022
+ return J - B(L[i, j])
4023
+ if G.has_edge(i, j):
4024
+ return B(-L[i, j]).T
4025
+ return J - B(-L[i, j]).T
4026
+
4027
+ A = Graph(block_matrix(p, p, [Acon(i, j) for i in range(p) for j in range(p)]))
4028
+ A.name("Mathon's PC SRG on " + str(p*q**2) + " vertices")
4029
+ A.relabel()
4030
+ return A
4031
+
4032
+
4033
+ def TuranGraph(n, r):
4034
+ r"""
4035
+ Return the Turan graph with parameters `n, r`.
4036
+
4037
+ Turan graphs are complete multipartite graphs with `n` vertices and `r`
4038
+ subsets, denoted `T(n,r)`, with the property that the sizes of the subsets
4039
+ are as close to equal as possible. The graph `T(n,r)` will have `n \pmod r`
4040
+ subsets of size `\lfloor n/r \rfloor` and `r - (n \pmod r)` subsets of size
4041
+ `\lceil n/r \rceil`. See the :wikipedia:`Turan_graph` for more information.
4042
+
4043
+ INPUT:
4044
+
4045
+ - ``n`` -- integer; the number of vertices in the graph
4046
+
4047
+ - ``r`` -- integer; the number of partitions of the graph
4048
+
4049
+ EXAMPLES:
4050
+
4051
+ The Turan graph is a complete multipartite graph::
4052
+
4053
+ sage: g = graphs.TuranGraph(13, 4)
4054
+ sage: k = graphs.CompleteMultipartiteGraph([3,3,3,4])
4055
+ sage: g.is_isomorphic(k)
4056
+ True
4057
+
4058
+ The Turan graph `T(n,r)` has `\frac{(r-1)(n^2-s^2)}{2r} + \frac{s(s-1)}{2}`
4059
+ edges, where `s = n \mod r` (:issue:`34249`)::
4060
+
4061
+ sage: n = 12
4062
+ sage: r = 8
4063
+ sage: g = graphs.TuranGraph(n, r)
4064
+ sage: def count(n, r):
4065
+ ....: s = n % r
4066
+ ....: return (r - 1) * (n**2 - s**2) / (2*r) + s*(s - 1)/2
4067
+ sage: g.size() == count(n, r)
4068
+ True
4069
+ sage: n = randint(3, 100)
4070
+ sage: r = randint(2, n - 1)
4071
+ sage: g = graphs.TuranGraph(n, r)
4072
+ sage: g.size() == count(n, r)
4073
+ True
4074
+
4075
+ TESTS::
4076
+
4077
+ sage: g = graphs.TuranGraph(3,6)
4078
+ Traceback (most recent call last):
4079
+ ...
4080
+ ValueError: input parameters must satisfy "1 < r < n"
4081
+ """
4082
+ if n < 1 or n < r or r < 1:
4083
+ raise ValueError('input parameters must satisfy "1 < r < n"')
4084
+
4085
+ from sage.graphs.generators.basic import CompleteMultipartiteGraph
4086
+
4087
+ p = n // r
4088
+ s = n % r
4089
+ vertex_sets = [p]*(r - s) + [p + 1]*s
4090
+
4091
+ g = CompleteMultipartiteGraph(vertex_sets)
4092
+ g.name('Turan Graph with n: {}, r: {}'.format(n, r))
4093
+
4094
+ return g
4095
+
4096
+
4097
+ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False):
4098
+ r"""
4099
+ Return a strongly regular graph of S6 type from [Muz2007]_ on
4100
+ `n^d((n^d-1)/(n-1)+1)` vertices.
4101
+
4102
+ The construction depends upon a number of parameters, two of them, `n` and
4103
+ `d`, mandatory, and `\Phi` and `\Sigma` mappings defined in [Muz2007]_.
4104
+ These graphs have parameters `(mn^d, n^{d-1}(m-1) - 1,\mu - 2,\mu)`, where
4105
+ `\mu=\frac{n^{d-1}-1}{n-1}n^{d-1}` and `m:=\frac{n^d-1}{n-1}+1`.
4106
+
4107
+ Some details on `\Phi` and `\Sigma` are as follows. Let `L` be the
4108
+ complete graph on `M:=\{0,..., m-1\}` with the matching
4109
+ `\{(2i,2i+1) | i=0,...,m/2\}` removed.
4110
+ Then one arbitrarily chooses injections `\Phi_i`
4111
+ from the edges of `L` on `i \in M` into sets of parallel classes of affine
4112
+ `d`-dimensional designs; our implementation uses the designs of hyperplanes
4113
+ in `d`-dimensional affine geometries over `GF(n)`. Finally, for each edge
4114
+ `ij` of `L` one arbitrarily chooses bijections `\Sigma_{ij}` between
4115
+ `\Phi_i` and `\Phi_j`. More details, in particular how these choices lead
4116
+ to non-isomorphic graphs, are in [Muz2007]_.
4117
+
4118
+ INPUT:
4119
+
4120
+ - ``n`` -- integer; a prime power
4121
+
4122
+ - ``d`` -- integer; must be odd if `n` is odd
4123
+
4124
+ - ``Phi`` is an optional parameter of the construction; it must be either
4125
+
4126
+ - ``'fixed'`` -- this will generate fixed default `\Phi_i`, for `i \in M`, or
4127
+
4128
+ - ``'random'`` -- `\Phi_i` are generated at random, or
4129
+
4130
+ - A dictionary describing the functions `\Phi_i`; for `i \in M`,
4131
+ Phi[(i, T)] in `M`, for each edge T of `L` on `i`.
4132
+ Also, each `\Phi_i` must be injective.
4133
+
4134
+ - ``Sigma`` is an optional parameter of the construction; it must be either
4135
+
4136
+ - ``'fixed'`` -- this will generate a fixed default `\Sigma`, or
4137
+
4138
+ - ``'random'`` -- `\Sigma` is generated at random
4139
+
4140
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, print progress
4141
+ information
4142
+
4143
+ .. SEEALSO::
4144
+
4145
+ - :func:`~sage.graphs.strongly_regular_db.is_muzychuk_S6`
4146
+
4147
+ .. TODO::
4148
+
4149
+ Implement the possibility to explicitly supply the parameter `\Sigma`
4150
+ of the construction.
4151
+
4152
+ EXAMPLES::
4153
+
4154
+ sage: # needs sage.combinat sage.modules sage.rings.finite_rings
4155
+ sage: graphs.MuzychukS6Graph(3, 3).is_strongly_regular(parameters=True)
4156
+ (378, 116, 34, 36)
4157
+ sage: phi = {(2,(0,2)):0, (1,(1,3)):1, (0,(0,3)):1, (2,(1,2)):1,
4158
+ ....: (1,(1,2)):0, (0,(0,2)):0, (3,(0,3)):0, (3,(1,3)):1}
4159
+ sage: graphs.MuzychukS6Graph(2, 2, Phi=phi).is_strongly_regular(parameters=True)
4160
+ (16, 5, 0, 2)
4161
+
4162
+ TESTS::
4163
+
4164
+ sage: # needs sage.modules
4165
+ sage: graphs.MuzychukS6Graph(2,2,Phi='random',Sigma='random').is_strongly_regular(parameters=True) # needs sage.rings.finite_rings
4166
+ (16, 5, 0, 2)
4167
+ sage: graphs.MuzychukS6Graph(3,3,Phi='random',Sigma='random').is_strongly_regular(parameters=True) # needs sage.rings.finite_rings
4168
+ (378, 116, 34, 36)
4169
+ sage: graphs.MuzychukS6Graph(3,2)
4170
+ Traceback (most recent call last):
4171
+ ...
4172
+ AssertionError: n must be even or d must be odd
4173
+ sage: graphs.MuzychukS6Graph(6,2)
4174
+ Traceback (most recent call last):
4175
+ ...
4176
+ AssertionError: n must be a prime power
4177
+ sage: graphs.MuzychukS6Graph(3,1)
4178
+ Traceback (most recent call last):
4179
+ ...
4180
+ AssertionError: d must be at least 2
4181
+ sage: graphs.MuzychukS6Graph(3,3,Phi=42) # needs sage.rings.finite_rings
4182
+ Traceback (most recent call last):
4183
+ ...
4184
+ AssertionError: Phi must be a dictionary or 'random' or 'fixed'
4185
+ sage: graphs.MuzychukS6Graph(3,3,Sigma=42) # needs sage.rings.finite_rings
4186
+ Traceback (most recent call last):
4187
+ ...
4188
+ ValueError: Sigma must be 'random' or 'fixed'
4189
+ """
4190
+ # TO DO: optimise
4191
+ # add option to return phi, sigma? generate phi, sigma from seed? (int say?)
4192
+
4193
+ from sage.combinat.designs.block_design import ProjectiveGeometryDesign
4194
+ from sage.misc.prandom import randrange
4195
+ from sage.misc.functional import is_even
4196
+ from sage.arith.misc import is_prime_power
4197
+ from sage.graphs.generators.basic import CompleteGraph
4198
+ from sage.rings.finite_rings.finite_field_constructor import GF
4199
+ from sage.matrix.special import ones_matrix
4200
+ from sage.matrix.constructor import matrix
4201
+ from sage.rings.rational_field import QQ
4202
+ from sage.rings.integer_ring import ZZ
4203
+ from time import time
4204
+
4205
+ assert d > 1, 'd must be at least 2'
4206
+ assert is_even(n * (d-1)), 'n must be even or d must be odd'
4207
+ assert is_prime_power(n), 'n must be a prime power'
4208
+ t = time()
4209
+
4210
+ # build L, L_i and the design
4211
+ m = int((n**d - 1)/(n - 1) + 1) # from m = p + 1, p = (n^d-1) / (n-1)
4212
+ L = CompleteGraph(m)
4213
+ L.delete_edges([(2 * x, 2 * x + 1) for x in range(m // 2)])
4214
+ L_i = [L.edges_incident(x, labels=False) for x in range(m)]
4215
+ Design = ProjectiveGeometryDesign(d, d-1, GF(n, 'a'), point_coordinates=False)
4216
+ projBlocks = Design.blocks()
4217
+ atInf = projBlocks[-1]
4218
+ Blocks = [[x for x in block if x not in atInf] for block in projBlocks[:-1]]
4219
+ if verbose:
4220
+ print('finished preamble at %f (+%f)' % (time() - t, time() - t))
4221
+ t1 = time()
4222
+
4223
+ # sort the hyperplanes into parallel classes
4224
+ ParClasses = [Blocks]
4225
+ while ParClasses[0]:
4226
+ nextHyp = ParClasses[0].pop()
4227
+ for C in ParClasses[1:]:
4228
+ listC = sum(C, [])
4229
+ for x in nextHyp:
4230
+ if x in listC:
4231
+ break
4232
+ else:
4233
+ C.append(nextHyp)
4234
+ break
4235
+ else:
4236
+ ParClasses.append([nextHyp])
4237
+ del ParClasses[0]
4238
+ if verbose:
4239
+ print('finished ParClasses at %f (+%f)' % (time() - t, time() - t1))
4240
+ t1 = time()
4241
+
4242
+ # build E^C_j
4243
+ E = {}
4244
+ v = ZZ(n**d)
4245
+ k = ZZ(n**(d-1))
4246
+ ones = ones_matrix(v)
4247
+ ones_v = ones/v
4248
+ for C in ParClasses:
4249
+ EC = matrix(QQ, v)
4250
+ for line in C:
4251
+ for i, j in combinations(line, 2):
4252
+ EC[i, j] = EC[j, i] = 1/k
4253
+ EC -= ones_v
4254
+ E[tuple(C[0])] = EC
4255
+ if verbose:
4256
+ print('finished E at %f (+%f)' % (time() - t, time() - t1))
4257
+ t1 = time()
4258
+
4259
+ # handle Phi
4260
+ if Phi == 'random':
4261
+ Phi = {}
4262
+ for x in range(m):
4263
+ temp = list(range(len(ParClasses)))
4264
+ for line in L_i[x]:
4265
+ rand = randrange(0, len(temp))
4266
+ Phi[(x, line)] = temp.pop(rand)
4267
+ elif Phi == 'fixed':
4268
+ Phi = {(x, line): val for x in range(m) for val, line in enumerate(L_i[x])}
4269
+ else:
4270
+ assert isinstance(Phi, dict), \
4271
+ "Phi must be a dictionary or 'random' or 'fixed'"
4272
+ assert set(Phi.keys()) == set([(x, line) for x in range(m) for line in L_i[x]]), \
4273
+ 'each Phi_i must have domain L_i'
4274
+ for x in range(m):
4275
+ assert m - 2 == len(set([val for (key, val) in Phi.items() if key[0] == x])), \
4276
+ 'each phi_i must be injective'
4277
+ for val in Phi.values():
4278
+ assert val in range(m - 1), \
4279
+ 'codomain should be {0,..., (n^d - 1)/(n - 1) - 1}'
4280
+ phi = {(x, line): ParClasses[Phi[(x, line)]] for x in range(m) for line in L_i[x]}
4281
+ if verbose:
4282
+ print('finished phi at %f (+%f)' % (time() - t, time() - t1))
4283
+ t1 = time()
4284
+
4285
+ # handle sigma
4286
+ sigma = {}
4287
+ if Sigma == 'random':
4288
+ for x in range(m):
4289
+ for line in L_i[x]:
4290
+ [i, j] = line
4291
+ temp = phi[(j, line)][:]
4292
+ for hyp in phi[(i, line)]:
4293
+ rand = randrange(0, len(temp))
4294
+ sigma[(i, j, tuple(hyp))] = temp[rand]
4295
+ sigma[(j, i, tuple(temp[rand]))] = hyp
4296
+ del temp[rand]
4297
+ elif Sigma == 'fixed':
4298
+ for x in range(m):
4299
+ for line in L_i[x]:
4300
+ [i, j] = line
4301
+ temp = phi[(j, line)][:]
4302
+ for hyp in phi[(i, line)]:
4303
+ val = temp.pop()
4304
+ sigma[(i, j, tuple(hyp))] = val
4305
+ sigma[(j, i, tuple(val))] = hyp
4306
+ else:
4307
+ raise ValueError("Sigma must be 'random' or 'fixed'")
4308
+ if verbose:
4309
+ print('finished sigma at %f (+%f)' % (time() - t, time() - t1))
4310
+ t1 = time()
4311
+
4312
+ # build V
4313
+ edges = [] # how many? *m^2*n^2
4314
+ for (i, j) in L.edges(sort=True, labels=False):
4315
+ for hyp in phi[(i, (i, j))]:
4316
+ for x in hyp:
4317
+ newEdges = [((i, x), (j, y))
4318
+ for y in sigma[(i, j, tuple(hyp))]]
4319
+ edges.extend(newEdges)
4320
+ if verbose:
4321
+ print('finished edges at %f (+%f)' % (time() - t, time() - t1))
4322
+ t1 = time()
4323
+ V = Graph(edges)
4324
+ if verbose:
4325
+ print('finished V at %f (+%f)' % (time() - t, time() - t1))
4326
+ t1 = time()
4327
+
4328
+ # build D_i, F_i and A_i
4329
+ D_i = [0]*m
4330
+ for x in range(m):
4331
+ D_i[x] = sum([E[tuple(phi[x, line][0])] for line in L_i[x]])
4332
+ F_i = [1 - D_i[x] - ones_v for x in range(m)]
4333
+ # as the sum of (1/v)*J_\Omega_i, D_i, F_i is identity
4334
+ A_i = [(v-k)*ones_v - k*F_i[x] for x in range(m)]
4335
+ # we know A_i = k''*(1/v)*J_\Omega_i + r''*D_i + s''*F_i,
4336
+ # and (k'', s'', r'') = (v - k, 0, -k)
4337
+ if verbose:
4338
+ print('finished D, F and A at %f (+%f)' % (time() - t, time() - t1))
4339
+ t1 = time()
4340
+
4341
+ # add the edges of the graph of B to V
4342
+ for i in range(m):
4343
+ V.add_edges([((i, x), (i, y)) for x in range(v)
4344
+ for y in range(v) if not A_i[i][(x, y)]])
4345
+
4346
+ V.name('Muzychuk S6 graph with parameters ('+str(n)+','+str(d)+')')
4347
+ if verbose:
4348
+ print('finished at %f (+%f)' % ((time() - t), time() - t1))
4349
+ return V
4350
+
4351
+
4352
+ def CubeConnectedCycle(d):
4353
+ r"""
4354
+ Return the cube-connected cycle of dimension `d`.
4355
+
4356
+ The cube-connected cycle of order `d` is the `d`-dimensional hypercube
4357
+ with each of its vertices replaced by a cycle of length `d`. This graph has
4358
+ order `d \times 2^d`.
4359
+ The construction is as follows:
4360
+ Construct vertex `(x,y)` for `0 \leq x < 2^d`, `0 \leq y < d`.
4361
+ For each vertex, `(x,y)`, add an edge between it and `(x, (y-1) \mod d))`,
4362
+ `(x,(y+1) \mod d)`, and `(x \oplus 2^y, y)`, where `\oplus` is the bitwise
4363
+ xor operator.
4364
+
4365
+ For `d=1` and `2`, the cube-connected cycle graph contains self-loops or
4366
+ multiple edges between a pair of vertices, but for all other `d`, it is
4367
+ simple.
4368
+
4369
+ INPUT:
4370
+
4371
+ - ``d`` -- positive integer; the dimension of the desired hypercube as well
4372
+ as the length of the cycle to be placed at each vertex of the
4373
+ `d`-dimensional hypercube
4374
+
4375
+ EXAMPLES:
4376
+
4377
+ The order of the graph is `d \times 2^d` ::
4378
+
4379
+ sage: d = 3
4380
+ sage: g = graphs.CubeConnectedCycle(d)
4381
+ sage: len(g) == d*2**d
4382
+ True
4383
+
4384
+ The diameter of cube-connected cycles for `d > 3` is
4385
+ `2d + \lfloor \frac{d}{2} \rfloor - 2` ::
4386
+
4387
+ sage: d = 4
4388
+ sage: g = graphs.CubeConnectedCycle(d)
4389
+ sage: g.diameter() == 2*d+d//2-2
4390
+ True
4391
+
4392
+ All vertices have degree `3` when `d > 1` ::
4393
+
4394
+ sage: g = graphs.CubeConnectedCycle(5)
4395
+ sage: all(g.degree(v) == 3 for v in g)
4396
+ True
4397
+
4398
+ TESTS::
4399
+
4400
+ sage: g = graphs.CubeConnectedCycle(0)
4401
+ Traceback (most recent call last):
4402
+ ...
4403
+ ValueError: the dimension d must be greater than 0
4404
+ """
4405
+ if d < 1:
4406
+ raise ValueError('the dimension d must be greater than 0')
4407
+
4408
+ G = Graph(name="Cube-Connected Cycle of dimension {}".format(d))
4409
+
4410
+ if d == 1:
4411
+ G.allow_loops(True)
4412
+ # only d = 1 requires loops
4413
+ G.add_edges([((0, 0), (0, 1)), ((0, 0), (0, 0)), ((0, 1), (0, 1))])
4414
+ return G
4415
+
4416
+ if d == 2:
4417
+ # only d = 2 require multiple edges
4418
+ G.allow_multiple_edges(True)
4419
+ G.add_edges([((0, 0), (0, 1)), ((0, 0), (0, 1)), ((0, 0), (1, 0)),
4420
+ ((0, 1), (2, 1)), ((1, 0), (1, 1)), ((1, 0), (1, 1)),
4421
+ ((1, 1), (3, 1)), ((2, 0), (2, 1)), ((2, 0), (2, 1)),
4422
+ ((2, 0), (3, 0)), ((3, 0), (3, 1)), ((3, 0), (3, 1))])
4423
+ return G
4424
+
4425
+ for x in range(1 << d):
4426
+ G.add_cycle([(x, y) for y in range(d)])
4427
+
4428
+ for x, y in G:
4429
+ G.add_edge((x, y), (x ^ (1 << y), y))
4430
+
4431
+ return G
4432
+
4433
+
4434
+ def StaircaseGraph(n):
4435
+ r"""
4436
+ Return a staircase graph with `2n` nodes
4437
+
4438
+ For `n \geq 3`, the staircase graph of order `2n` is the graph obtained
4439
+ from the ladder graph of order `2n - 2`, i.e., ``graphs.LadderGraph(n - 1)``
4440
+ by introducing two new nodes `2n - 2` and `2n - 1`, and then joining the
4441
+ node `2n - 2` with `0` and `n - 1`, the node `2n - 1` with `n - 2` and
4442
+ `2n - 3`, and the nodes `2n - 2` and `2n - 1` with each other.
4443
+
4444
+ Note that ``graphs.StaircaseGraph(4)`` is also known as the ``Bicorn
4445
+ graph``. It is the only brick that has a unique `b`-invariant edge.
4446
+
4447
+ PLOTTING:
4448
+
4449
+ Upon construction, the position dictionary is filled to override
4450
+ the spring-layout algorithm. By convention, each staircase graph will be
4451
+ displayed horizontally, with the first `n - 1` nodes displayed from left to
4452
+ right on the top horizontal line, the second `n - 1` nodes displayed from
4453
+ left to right on the middle horizontal line, and the last two nodes
4454
+ displayed at the bottom two corners.
4455
+
4456
+ INPUT:
4457
+
4458
+ - ``n`` -- an integer at least 3; number of nodes is `2n`
4459
+
4460
+ OUTPUT:
4461
+
4462
+ - ``G`` -- a staircase graph of order `2n`; note that a
4463
+ :class:`ValueError` is returned if `n < 3`
4464
+
4465
+ EXAMPLES:
4466
+
4467
+ Construct and show a staircase graph with 10 nodes::
4468
+
4469
+ sage: g = graphs.StaircaseGraph(5)
4470
+ sage: g.show() # long time # needs sage.plot
4471
+
4472
+ Construct and show the Bicorn graph. Note that the edge `(1, 4)` is the
4473
+ unique `b`-invariant edge::
4474
+
4475
+ sage: bicornGraph = graphs.StaircaseGraph(4)
4476
+ sage: bicornGraph.show() # long time # needs sage.plot
4477
+
4478
+ Create several staircase graphs in a Sage graphics array::
4479
+
4480
+ sage: # needs sage.plots
4481
+ sage: g = []
4482
+ sage: j = []
4483
+ sage: for i in range(9):
4484
+ ....: k = graphs.StaircaseGraph(i+3)
4485
+ ....: g.append(k)
4486
+ sage: for i in range(3):
4487
+ ....: n = []
4488
+ ....: for m in range(3):
4489
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4490
+ ....: j.append(n)
4491
+ sage: G = graphics_array(j)
4492
+ sage: G.show() # long time
4493
+
4494
+ TESTS:
4495
+
4496
+ The input parameter must be an integer that is at least 3::
4497
+
4498
+ sage: G = graphs.StaircaseGraph(2)
4499
+ Traceback (most recent call last):
4500
+ ...
4501
+ ValueError: parameter n must be at least 3
4502
+
4503
+ REFERENCES:
4504
+
4505
+ - [LM2024]_
4506
+
4507
+ .. SEEALSO::
4508
+
4509
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.LadderGraph`
4510
+
4511
+ AUTHORS:
4512
+
4513
+ - Janmenjaya Panda (2024-06-09)
4514
+ """
4515
+ if n < 3:
4516
+ raise ValueError("parameter n must be at least 3")
4517
+
4518
+ pos_dict = {
4519
+ 0: (0, 1),
4520
+ n - 2: (n, 1),
4521
+ 2*n - 2: (0, -1),
4522
+ 2*n - 1: (n, -1)
4523
+ }
4524
+
4525
+ edges = [
4526
+ (0, n - 1),
4527
+ (0, 2*n - 2),
4528
+ (n - 2, 2*n - 3),
4529
+ (n - 2, 2*n - 1),
4530
+ (n - 1, 2*n - 2),
4531
+ (2*n - 3, 2*n - 1),
4532
+ (2*n - 2, 2*n - 1)
4533
+ ]
4534
+
4535
+ for v in range(1, n - 2):
4536
+ pos_dict[v] = (v + 1, 1)
4537
+ edges.append((v, v + n - 1))
4538
+
4539
+ for v in range(n - 1, 2*n - 2):
4540
+ pos_dict[v] = (v - n + 2, 0)
4541
+
4542
+ G = Graph(2 * n, pos=pos_dict, name="Staircase graph")
4543
+ G.add_edges(edges)
4544
+ G.add_path(list(range(n - 1)))
4545
+ G.add_path(list(range(n - 1, 2*n - 2)))
4546
+ return G
4547
+
4548
+
4549
+ def BiwheelGraph(n):
4550
+ r"""
4551
+ Return a biwheel graph with `2n` nodes
4552
+
4553
+ For `n \geq 4`, the biwheel graph of order `2n` is the planar
4554
+ bipartite graph obtained from the cycle graph of order `2n - 2`, i.e.,
4555
+ ``graphs.CycleGraph(2*n - 2)`` (called the `rim` of the biwheel graph) by
4556
+ introducing two new nodes `2n - 2` and `2n - 1` (called the *hubs* of the
4557
+ biwheel graph), and then joining the node `2n - 2` with the odd indexed
4558
+ nodes up to `2n - 3` and joining the node `2n - 1` with the even indexed
4559
+ nodes up to `2n - 4`.
4560
+
4561
+ PLOTTING:
4562
+
4563
+ Upon construction, the position dictionary is filled to override
4564
+ the spring-layout algorithm. By convention, each biwheel graph will be
4565
+ displayed with the first (0) node at the right if `n` is even or
4566
+ otherwise at an angle `\pi / (2n - 2)` with respect to the origin, with the
4567
+ rest of the nodes up to `2n - 3` following in a counterclockwise manner.
4568
+ Note that the last two nodes, i.e., the hubs `2n - 2` and `2n - 1` will
4569
+ be displayed at the coordinates `(-1/3, 0)` and `(1/3, 0)` respectively.
4570
+
4571
+ INPUT:
4572
+
4573
+ - ``n`` -- an integer at least 4; number of nodes is `2n`
4574
+
4575
+ OUTPUT:
4576
+
4577
+ - ``G`` -- a biwheel graph of order `2n`; note that a
4578
+ :class:`ValueError` is returned if `n < 4`
4579
+
4580
+ EXAMPLES:
4581
+
4582
+ Construct and show a biwheel graph with 10 nodes::
4583
+
4584
+ sage: g = graphs.BiwheelGraph(5)
4585
+ sage: g.show() # long time # needs sage.plot
4586
+ sage: g.is_planar() # needs planarity
4587
+ True
4588
+ sage: g.is_bipartite()
4589
+ True
4590
+
4591
+ Create several biwheel graphs in a Sage graphics array::
4592
+
4593
+ sage: # needs sage.plots
4594
+ sage: g = []
4595
+ sage: j = []
4596
+ sage: for i in range(9):
4597
+ ....: k = graphs.BiwheelGraph(i+4)
4598
+ ....: g.append(k)
4599
+ sage: for i in range(3):
4600
+ ....: n = []
4601
+ ....: for m in range(3):
4602
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4603
+ ....: j.append(n)
4604
+ sage: G = graphics_array(j)
4605
+ sage: G.show() # long time
4606
+
4607
+ TESTS:
4608
+
4609
+ The input parameter must be an integer that is at least 4::
4610
+
4611
+ sage: G = graphs.BiwheelGraph(3)
4612
+ Traceback (most recent call last):
4613
+ ...
4614
+ ValueError: parameter n must be at least 4
4615
+
4616
+ REFERENCES:
4617
+
4618
+ - [LM2024]_
4619
+
4620
+ .. SEEALSO::
4621
+
4622
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.WheelGraph`,
4623
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.TruncatedBiwheelGraph`
4624
+
4625
+ AUTHORS:
4626
+
4627
+ - Janmenjaya Panda (2024-06-09)
4628
+ """
4629
+ if n < 4:
4630
+ raise ValueError("parameter n must be at least 4")
4631
+
4632
+ angle_param = 0
4633
+
4634
+ if n % 2:
4635
+ from math import pi
4636
+ angle_param = pi / (2*n - 2)
4637
+
4638
+ G = Graph(2 * n, name="Biwheel graph")
4639
+ pos_dict = G._circle_embedding(list(range(2*n - 2)), angle=angle_param, return_dict=True)
4640
+ edges = []
4641
+
4642
+ from sage.rings.rational_field import QQ
4643
+ pos_dict[2*n - 2] = (-QQ((1, 3)), 0)
4644
+ pos_dict[2*n - 1] = (QQ((1, 3)), 0)
4645
+
4646
+ for i in range(2*n - 2):
4647
+ if i % 2 == 0:
4648
+ edges += [(i, 2*n - 1)]
4649
+ else:
4650
+ edges += [(i, 2*n - 2)]
4651
+
4652
+ G.set_pos(pos_dict)
4653
+ G.add_cycle(list(range(2*n - 2)))
4654
+ G.add_edges(edges)
4655
+ return G
4656
+
4657
+
4658
+ def TruncatedBiwheelGraph(n):
4659
+ r"""
4660
+ Return a truncated biwheel graph with `2n` nodes
4661
+
4662
+ For `n \geq 3`, the truncated biwheel graph of order `2n` is the graph
4663
+ obtained from the path graph of order `2n - 2`, i.e.,
4664
+ ``graphs.PathGraph(2*n - 2)`` by introducing two new nodes `2n - 2` and
4665
+ `2n - 1`, and then joining the node `2n - 2` with the odd indexed nodes
4666
+ up to `2n - 3`, joining the node `2n - 1` with the even indexed nodes up to
4667
+ `2n - 4` and adding the edges `(0, 2n - 2)` and `(2n - 3, 2n - 1)`.
4668
+
4669
+ PLOTTING:
4670
+
4671
+ Upon construction, the position dictionary is filled to override the
4672
+ spring-layout algorithm. By convention, each truncated biwheel graph will
4673
+ be displayed horizontally, with the first `2n - 2` nodes displayed from
4674
+ left to right on the middle horizontal line and the nodes `2n - 2` and
4675
+ `2n - 1` displayed at the top and the bottom central positions
4676
+ respectively.
4677
+
4678
+ INPUT:
4679
+
4680
+ - ``n`` -- an integer at least 3; number of nodes is `2n`
4681
+
4682
+ OUTPUT:
4683
+
4684
+ - ``G`` -- a truncated biwheel graph of order `2n`; note that a
4685
+ :class:`ValueError` is returned if `n < 3`
4686
+
4687
+ EXAMPLES:
4688
+
4689
+ Construct and show a truncated biwheel graph with 10 nodes::
4690
+
4691
+ sage: g = graphs.TruncatedBiwheelGraph(5)
4692
+ sage: g.show() # long time # needs sage.plot
4693
+
4694
+ Create several truncated biwheel graphs in a Sage graphics array::
4695
+
4696
+ sage: # needs sage.plots
4697
+ sage: g = []
4698
+ sage: j = []
4699
+ sage: for i in range(9):
4700
+ ....: k = graphs.TruncatedBiwheelGraph(i+3)
4701
+ ....: g.append(k)
4702
+ sage: for i in range(3):
4703
+ ....: n = []
4704
+ ....: for m in range(3):
4705
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4706
+ ....: j.append(n)
4707
+ sage: G = graphics_array(j)
4708
+ sage: G.show() # long time
4709
+
4710
+ TESTS:
4711
+
4712
+ The input parameter must be an integer that is at least 3::
4713
+
4714
+ sage: G = graphs.TruncatedBiwheelGraph(2)
4715
+ Traceback (most recent call last):
4716
+ ...
4717
+ ValueError: parameter n must be at least 3
4718
+
4719
+ REFERENCES:
4720
+
4721
+ - [LM2024]_
4722
+
4723
+ .. SEEALSO::
4724
+
4725
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.WheelGraph`,
4726
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.BiwheelGraph`
4727
+
4728
+ AUTHORS:
4729
+
4730
+ - Janmenjaya Panda (2024-06-09)
4731
+ """
4732
+ if n < 3:
4733
+ raise ValueError("parameter n must be at least 3")
4734
+
4735
+ pos_dict = {2*n - 2: (0, n), 2*n - 1: (0, -n)}
4736
+ edges = [(0, 2*n - 2), (2*n - 3, 2*n - 1)]
4737
+
4738
+ for v in range(2*n - 2):
4739
+ pos_dict[v] = (2*(v-n) + 3, 0)
4740
+ if v % 2 == 0:
4741
+ edges += [(v, 2*n - 1)]
4742
+ else:
4743
+ edges += [(v, 2*n - 2)]
4744
+
4745
+ G = Graph(2 * n, pos=pos_dict, name="Truncated biwheel graph")
4746
+ G.add_path(list(range(2*n - 2)))
4747
+ G.add_edges(edges)
4748
+
4749
+ return G