passagemath-graphs 10.6.1rc1__cp310-cp310-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 (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.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 +2723 -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 +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -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 +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-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-310-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-310-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 +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-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/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,4759 @@
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=f"Johnson graph with parameters {n},{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=f"Kneser graph with parameters {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=f"Egawa Graph with parameters {p},{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=f"Hamming Graph with parameters {n},{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=f"Circulant graph ({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=f"{n}-Cube")
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=f"{n}-Cube", 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=f"Dorogovtsev-Goltsev-Mendes Graph, {n}-th generation")
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=f"Fibonacci-Tree-{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
+ TESTS:
1716
+
1717
+ Check that the name of the graph is correct::
1718
+
1719
+ sage: graphs.GeneralizedPetersenGraph(7, 2).name()
1720
+ 'Generalized Petersen graph (n=7,k=2)'
1721
+
1722
+ AUTHORS:
1723
+
1724
+ - Anders Jonsson (2009-10-15)
1725
+ """
1726
+ if n < 3:
1727
+ raise ValueError("n must be larger than 2")
1728
+ if k < 1 or k > (n - 1) // 2:
1729
+ raise ValueError("k must be in 1<= k <=floor((n-1)/2)")
1730
+ G = Graph(2 * n, name=f"Generalized Petersen graph (n={n},k={k})")
1731
+ for i in range(n):
1732
+ G.add_edge(i, (i+1) % n)
1733
+ G.add_edge(i, i+n)
1734
+ G.add_edge(i+n, n + (i+k) % n)
1735
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1736
+ G._circle_embedding(list(range(n, 2*n)), radius=.5, angle=pi/2)
1737
+ return G
1738
+
1739
+
1740
+ def IGraph(n, j, k):
1741
+ r"""
1742
+ Return an I-graph with `2n` nodes.
1743
+
1744
+ The I-Graph family as been proposed in [BCMS1988]_ as a generalization of
1745
+ the generalized Petersen graphs. The variables `n`, `j`, `k` are integers
1746
+ such that `n > 2` and `0 < j, k \leq \lfloor (n - 1) / 2 \rfloor`.
1747
+ When `j = 1` the resulting graph is isomorphic to the generalized Petersen
1748
+ graph with the same `n` and `k`.
1749
+
1750
+ INPUT:
1751
+
1752
+ - ``n`` -- the number of nodes is `2 * n`
1753
+
1754
+ - ``j`` -- integer such that `0 < j \leq \lfloor (n-1) / 2 \rfloor`
1755
+ determining how outer vertices are connected
1756
+
1757
+ - ``k`` -- integer such that `0 < k \leq \lfloor (n-1) / 2 \rfloor`
1758
+ determining how inner vertices are connected
1759
+
1760
+ PLOTTING: Upon construction, the position dictionary is filled to override
1761
+ the spring-layout algorithm. By convention, the I-graphs are displayed as an
1762
+ inner and outer cycle pair, with the first n nodes drawn on the outer
1763
+ circle. The first (0) node is drawn at the top of the outer-circle, moving
1764
+ counterclockwise after that. The inner circle is drawn with the (n)th node
1765
+ at the top, then counterclockwise as well.
1766
+
1767
+ EXAMPLES:
1768
+
1769
+ When `j = 1` the resulting graph will be isomorphic to a generalized
1770
+ Petersen graph::
1771
+
1772
+ sage: g = graphs.IGraph(7,1,2)
1773
+ sage: g2 = graphs.GeneralizedPetersenGraph(7,2)
1774
+ sage: g.is_isomorphic(g2)
1775
+ True
1776
+
1777
+ The IGraph with parameters `(n, j, k)` is isomorphic to the IGraph with
1778
+ parameters `(n, k, j)`::
1779
+
1780
+ sage: g = graphs.IGraph(7, 2, 3)
1781
+ sage: h = graphs.IGraph(7, 3, 2)
1782
+ sage: g.is_isomorphic(h)
1783
+ True
1784
+
1785
+ TESTS::
1786
+
1787
+ sage: graphs.IGraph(1, 1, 1)
1788
+ Traceback (most recent call last):
1789
+ ...
1790
+ ValueError: n must be larger than 2
1791
+ sage: graphs.IGraph(3, 0, 1)
1792
+ Traceback (most recent call last):
1793
+ ...
1794
+ ValueError: j must be in 1 <= j <= floor((n - 1) / 2)
1795
+ sage: graphs.IGraph(3, 33, 1)
1796
+ Traceback (most recent call last):
1797
+ ...
1798
+ ValueError: j must be in 1 <= j <= floor((n - 1) / 2)
1799
+ sage: graphs.IGraph(3, 1, 0)
1800
+ Traceback (most recent call last):
1801
+ ...
1802
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1803
+ sage: graphs.IGraph(3, 1, 3)
1804
+ Traceback (most recent call last):
1805
+ ...
1806
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1807
+ """
1808
+ if n < 3:
1809
+ raise ValueError("n must be larger than 2")
1810
+ if j < 1 or j > (n - 1) // 2:
1811
+ raise ValueError("j must be in 1 <= j <= floor((n - 1) / 2)")
1812
+ if k < 1 or k > (n - 1) // 2:
1813
+ raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)")
1814
+
1815
+ G = Graph(2 * n, name=f"I-graph (n={n}, j={j}, k={k})")
1816
+ for i in range(n):
1817
+ G.add_edge(i, (i + j) % n)
1818
+ G.add_edge(i, i + n)
1819
+ G.add_edge(i + n, n + (i + k) % n)
1820
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1821
+ G._circle_embedding(list(range(n, 2 * n)), radius=.5, angle=pi/2)
1822
+ return G
1823
+
1824
+
1825
+ def DoubleGeneralizedPetersenGraph(n, k):
1826
+ r"""
1827
+ Return a double generalized Petersen graph with `4n` nodes.
1828
+
1829
+ The double generalized Petersen graphs is a family of graphs proposed in
1830
+ [ZF2012]_ as a variant of generalized Petersen graphs. The variables `n`,
1831
+ `k` are integers such that `n > 2` and `0 < k \leq \lfloor (n-1) / 2
1832
+ \rfloor`.
1833
+
1834
+ INPUT:
1835
+
1836
+ - ``n`` -- the number of nodes is `4 * n`
1837
+
1838
+ - ``k`` -- integer such that `0 < k \leq \lfloor (n-1) / 2 \rfloor`
1839
+ determining how vertices on second and third inner rims are connected
1840
+
1841
+ PLOTTING: Upon construction, the position dictionary is filled to override
1842
+ the spring-layout algorithm. By convention, the double generalized Petersen
1843
+ graphs are displayed as 4 concentric cycles, with the first n nodes drawn on
1844
+ the outer circle. The first (0) node is drawn at the top of the
1845
+ outer-circle, moving counterclockwise after that. The second circle is drawn
1846
+ with the (n)th node at the top, then counterclockwise as well. The tird
1847
+ cycle is drawn with the (2n)th node at the top, then counterclockwise. And
1848
+ the fourth cycle is drawn with the (3n)th node at the top, then again
1849
+ counterclockwise.
1850
+
1851
+ EXAMPLES:
1852
+
1853
+ When `n` is even the resulting graph will be isomorphic to a double
1854
+ generalized Petersen graph with `k' = n / 2 - k`::
1855
+
1856
+ sage: g = graphs.DoubleGeneralizedPetersenGraph(10, 2)
1857
+ sage: g2 = graphs.DoubleGeneralizedPetersenGraph(10, 3)
1858
+ sage: g.is_isomorphic(g2)
1859
+ True
1860
+
1861
+ TESTS::
1862
+
1863
+ sage: graphs.DoubleGeneralizedPetersenGraph(1, 1)
1864
+ Traceback (most recent call last):
1865
+ ...
1866
+ ValueError: n must be larger than 2
1867
+ sage: graphs.DoubleGeneralizedPetersenGraph(3, 0)
1868
+ Traceback (most recent call last):
1869
+ ...
1870
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1871
+ sage: graphs.DoubleGeneralizedPetersenGraph(3, 3)
1872
+ Traceback (most recent call last):
1873
+ ...
1874
+ ValueError: k must be in 1 <= k <= floor((n - 1) / 2)
1875
+ """
1876
+ if n < 3:
1877
+ raise ValueError("n must be larger than 2")
1878
+ if k < 1 or k > (n - 1) // 2:
1879
+ raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)")
1880
+
1881
+ G = Graph(4 * n, name=f"Double generalized Petersen graph (n={n}, k={k})")
1882
+ for i in range(n):
1883
+ G.add_edge(i, (i + 1) % n)
1884
+ G.add_edge(i + 3 * n, (i + 1) % n + 3 * n)
1885
+ G.add_edge(i, i + n)
1886
+ G.add_edge(i + 2 * n, i + 3 * n)
1887
+ G.add_edge(i + n, (i + k) % n + 2 * n)
1888
+ G.add_edge(i + 2 * n, (i + k) % n + n)
1889
+ G._circle_embedding(list(range(n)), radius=3, angle=pi/2)
1890
+ G._circle_embedding(list(range(n, 2 * n)), radius=2, angle=pi/2)
1891
+ G._circle_embedding(list(range(2 * n, 3 * n)), radius=1.5, angle=pi/2)
1892
+ G._circle_embedding(list(range(3 * n, 4 * n)), radius=0.5, angle=pi/2)
1893
+ return G
1894
+
1895
+
1896
+ def RoseWindowGraph(n, a, r):
1897
+ r"""
1898
+ Return a rose window graph with `2n` nodes.
1899
+
1900
+ The rose window graphs is a family of tetravalent graphs introduced in
1901
+ [Wilson2008]_. The parameters `n`, `a` and `r` are integers such that
1902
+ `n > 2`, `1 \leq a, r < n`, and `r \neq n / 2`.
1903
+
1904
+ INPUT:
1905
+
1906
+ - ``n`` -- the number of nodes is `2 * n`
1907
+
1908
+ - ``a`` -- integer such that `1 \leq a < n` determining a-spoke edges
1909
+
1910
+ - ``r`` -- integer such that `1 \leq r < n` and `r \neq n / 2` determining
1911
+ how inner vertices are connected
1912
+
1913
+ PLOTTING: Upon construction, the position dictionary is filled to override
1914
+ the spring-layout algorithm. By convention, the rose window graphs are
1915
+ displayed as an inner and outer cycle pair, with the first `n` nodes drawn
1916
+ on the outer circle. The first (0) node is drawn at the top of the
1917
+ outer-circle, moving counterclockwise after that. The inner circle is drawn
1918
+ with the (`n`)th node at the top, then counterclockwise as well. Vertices in
1919
+ the outer circle are connected in the circular manner, vertices in the inner
1920
+ circle are connected when their label have difference `r \pmod{n}`. Vertices
1921
+ on the outer rim are connected with the vertices on the inner rim when they
1922
+ are at the same position and when they are `a` apart.
1923
+
1924
+ EXAMPLES:
1925
+
1926
+ The vertices of a rose window graph have all degree 4::
1927
+
1928
+ sage: G = graphs.RoseWindowGraph(5, 1, 2)
1929
+ sage: all(G.degree(u) == 4 for u in G)
1930
+ True
1931
+
1932
+ The smallest rose window graph as parameters `(3, 2, 1)`::
1933
+
1934
+ sage: G = graphs.RoseWindowGraph(3, 2, 1)
1935
+ sage: all(G.degree(u) == 4 for u in G)
1936
+ True
1937
+
1938
+ TESTS::
1939
+
1940
+ sage: graphs.RoseWindowGraph(1, 1, 1)
1941
+ Traceback (most recent call last):
1942
+ ...
1943
+ ValueError: n must be larger than 2
1944
+ sage: graphs.RoseWindowGraph(6, 0, 2)
1945
+ Traceback (most recent call last):
1946
+ ...
1947
+ ValueError: a must be an integer such that 1 <= a < n
1948
+ sage: graphs.RoseWindowGraph(6, 6, 2)
1949
+ Traceback (most recent call last):
1950
+ ...
1951
+ ValueError: a must be an integer such that 1 <= a < n
1952
+ sage: graphs.RoseWindowGraph(6, 3, 0)
1953
+ Traceback (most recent call last):
1954
+ ...
1955
+ ValueError: r must be an integer such that 1 <= r < n
1956
+ sage: graphs.RoseWindowGraph(6, 3, 6)
1957
+ Traceback (most recent call last):
1958
+ ...
1959
+ ValueError: r must be an integer such that 1 <= r < n
1960
+ sage: graphs.RoseWindowGraph(6, 3, 3)
1961
+ Traceback (most recent call last):
1962
+ ...
1963
+ ValueError: r must be different than n / 2
1964
+ """
1965
+ if n < 3:
1966
+ raise ValueError("n must be larger than 2")
1967
+ if a < 1 or a >= n:
1968
+ raise ValueError("a must be an integer such that 1 <= a < n")
1969
+ if r < 1 or r >= n:
1970
+ raise ValueError("r must be an integer such that 1 <= r < n")
1971
+ if r == n / 2:
1972
+ raise ValueError("r must be different than n / 2")
1973
+
1974
+ G = Graph(2 * n, name=f"Rose window graph (n={n}, a={a}, r={r})")
1975
+ for i in range(n):
1976
+ G.add_edge(i, (i + 1) % n)
1977
+ G.add_edge(i, i + n)
1978
+ G.add_edge((i + a) % n, i + n)
1979
+ G.add_edge(i + n, (i + r) % n + n)
1980
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
1981
+ G._circle_embedding(list(range(n, 2 * n)), radius=0.5, angle=pi/2)
1982
+ return G
1983
+
1984
+
1985
+ def TabacjnGraph(n, a, b, r):
1986
+ r"""
1987
+ Return a Tabačjn graph with `2n` nodes.
1988
+
1989
+ The Tabačjn graphs is a family of pentavalent bicirculants graphs proposed
1990
+ in [AHKOS2014]_ as a generalization of generalized Petersen graphs. The
1991
+ parameters `n`, `a`, `b`, `r` are integers such that `n \geq 3`, `1 \leq a,
1992
+ b, r \leq n - 1`, with `a \neq b` and `r \neq n / 2`.
1993
+
1994
+ INPUT:
1995
+
1996
+ - ``n`` -- the number of nodes is `2 * n`
1997
+
1998
+ - ``a`` -- integer such that `0 < a < n` and `a \neq b`, that determines
1999
+ a-spoke edges
2000
+
2001
+ - ``b`` -- integer such that `0 < b < n` and `b \neq a`, that determines
2002
+ b-spoke edges
2003
+
2004
+ - ``r`` -- integer such that `0 < r < n` and `r \neq n/2` determining how
2005
+ inner vertices are connected
2006
+
2007
+ PLOTTING: Upon construction, the position dictionary is filled to override
2008
+ the spring-layout algorithm. By convention, the rose window graphs are
2009
+ displayed as an inner and outer cycle pair, with the first `n` nodes drawn
2010
+ on the outer circle. The first (0) node is drawn at the top of the
2011
+ outer-circle, moving counterclockwise after that. The inner circle is drawn
2012
+ with the (`n`)th node at the top, then counterclockwise as well. Vertices in
2013
+ the outer circle are connected in the circular manner, vertices in the inner
2014
+ circle are connected when their label have difference `r \pmod{n}`. Vertices
2015
+ on the outer rim are connected with the vertices on the inner rim when they
2016
+ are at the same position and when they are `a` and `b` apart.
2017
+
2018
+ EXAMPLES::
2019
+
2020
+ sage: G = graphs.TabacjnGraph(3, 1, 2, 1)
2021
+ sage: G.degree()
2022
+ [5, 5, 5, 5, 5, 5]
2023
+ sage: G.is_isomorphic(graphs.CompleteGraph(6))
2024
+ True
2025
+ sage: G = graphs.TabacjnGraph(6, 1, 5, 2)
2026
+ sage: I = graphs.IcosahedralGraph()
2027
+ sage: G.is_isomorphic(I)
2028
+ True
2029
+
2030
+ TESTS::
2031
+
2032
+ sage: graphs.TabacjnGraph(1, 1, 1, 1)
2033
+ Traceback (most recent call last):
2034
+ ...
2035
+ ValueError: n must be larger than 2
2036
+ sage: graphs.TabacjnGraph(3, 0, 1, 1)
2037
+ Traceback (most recent call last):
2038
+ ...
2039
+ ValueError: a must be an integer such that 1 <= a < n
2040
+ sage: graphs.TabacjnGraph(3, 3, 1, 1)
2041
+ Traceback (most recent call last):
2042
+ ...
2043
+ ValueError: a must be an integer such that 1 <= a < n
2044
+ sage: graphs.TabacjnGraph(3, 1, 0, 1)
2045
+ Traceback (most recent call last):
2046
+ ...
2047
+ ValueError: b must be an integer such that 1 <= b < n
2048
+ sage: graphs.TabacjnGraph(3, 1, 3, 1)
2049
+ Traceback (most recent call last):
2050
+ ...
2051
+ ValueError: b must be an integer such that 1 <= b < n
2052
+ sage: graphs.TabacjnGraph(3, 1, 1, 1)
2053
+ Traceback (most recent call last):
2054
+ ...
2055
+ ValueError: a must be different than b
2056
+ sage: graphs.TabacjnGraph(3, 1, 2, 0)
2057
+ Traceback (most recent call last):
2058
+ ...
2059
+ ValueError: r must be an integer such that 1 <= r < n
2060
+ sage: graphs.TabacjnGraph(3, 1, 2, 3)
2061
+ Traceback (most recent call last):
2062
+ ...
2063
+ ValueError: r must be an integer such that 1 <= r < n
2064
+ sage: graphs.TabacjnGraph(4, 1, 2, 2)
2065
+ Traceback (most recent call last):
2066
+ ...
2067
+ ValueError: r must be different than n / 2
2068
+ """
2069
+ if n < 3:
2070
+ raise ValueError("n must be larger than 2")
2071
+ if a < 1 or a >= n:
2072
+ raise ValueError("a must be an integer such that 1 <= a < n")
2073
+ if b < 1 or b >= n:
2074
+ raise ValueError("b must be an integer such that 1 <= b < n")
2075
+ if a == b:
2076
+ raise ValueError("a must be different than b")
2077
+ if r < 1 or r >= n:
2078
+ raise ValueError("r must be an integer such that 1 <= r < n")
2079
+ if r == n/2:
2080
+ raise ValueError("r must be different than n / 2")
2081
+
2082
+ G = Graph(2 * n, name=f"Tabačjn graph (n={n}, a={a}, b={b}, r={r})")
2083
+ for i in range(n):
2084
+ G.add_edge(i, (i + 1) % n)
2085
+ G.add_edge(i, i + n)
2086
+ G.add_edge(i + n, n + (i + r) % n)
2087
+ G.add_edge(i, (i + a) % n + n)
2088
+ G.add_edge(i, (i + b) % n + n)
2089
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
2090
+ G._circle_embedding(list(range(n, 2 * n)), radius=0.5, angle=pi/2)
2091
+ return G
2092
+
2093
+
2094
+ def HararyGraph(k, n):
2095
+ r"""
2096
+ Return the Harary graph on `n` vertices and connectivity `k`, where
2097
+ `2 \leq k < n`.
2098
+
2099
+ A `k`-connected graph `G` on `n` vertices requires the minimum degree
2100
+ `\delta(G)\geq k`, so the minimum number of edges `G` should have is
2101
+ `\lceil kn/2\rceil`. Harary graphs achieve this lower bound, that is,
2102
+ Harary graphs are minimal `k`-connected graphs on `n` vertices.
2103
+
2104
+ The construction provided uses the method CirculantGraph. For more
2105
+ details, see the book [West2001]_ or the `MathWorld article on
2106
+ Harary graphs <http://mathworld.wolfram.com/HararyGraph.html>`_.
2107
+
2108
+ EXAMPLES:
2109
+
2110
+ Harary graphs `H_{k,n}`::
2111
+
2112
+ sage: h = graphs.HararyGraph(5,9); h
2113
+ Harary graph 5, 9: Graph on 9 vertices
2114
+ sage: h.order()
2115
+ 9
2116
+ sage: h.size()
2117
+ 23
2118
+ sage: h.vertex_connectivity() # needs sage.numerical.mip
2119
+ 5
2120
+
2121
+ TESTS:
2122
+
2123
+ Connectivity of some Harary graphs::
2124
+
2125
+ sage: n = 10
2126
+ sage: for k in range(2,n): # needs sage.numerical.mip
2127
+ ....: g = graphs.HararyGraph(k,n)
2128
+ ....: if k != g.vertex_connectivity():
2129
+ ....: print("Connectivity of Harary graphs not satisfied.")
2130
+ """
2131
+ if k < 2:
2132
+ raise ValueError("Connectivity parameter k should be at least 2.")
2133
+ if k >= n:
2134
+ raise ValueError("Number of vertices n should be greater than k.")
2135
+
2136
+ if k % 2 == 0:
2137
+ G = CirculantGraph(n, list(range(1, k//2 + 1)))
2138
+ else:
2139
+ if n % 2 == 0:
2140
+ G = CirculantGraph(n, list(range(1, (k - 1)//2 + 1)))
2141
+ for i in range(n):
2142
+ G.add_edge(i, (i + n//2) % n)
2143
+ else:
2144
+ G = HararyGraph(k - 1, n)
2145
+ for i in range((n - 1)//2 + 1):
2146
+ G.add_edge(i, (i + (n - 1)//2) % n)
2147
+ G.name('Harary graph {0}, {1}'.format(k, n))
2148
+ return G
2149
+
2150
+
2151
+ def HyperStarGraph(n, k):
2152
+ r"""
2153
+ Return the hyper-star graph `HS(n, k)`.
2154
+
2155
+ The vertices of the hyper-star graph are the set of binary strings of length
2156
+ `n` which contain `k` 1s. Two vertices, `u` and `v`, are adjacent only if
2157
+ `u` can be obtained from `v` by swapping the first bit with a different
2158
+ symbol in another position. For instance, vertex ``'011100'`` of `HS(6, 3)`
2159
+ is adjacent to vertices ``'101100'``, ``'110100'`` and ``'111000'``.
2160
+ See [LKOL2002]_ for more details.
2161
+
2162
+ INPUT:
2163
+
2164
+ - ``n`` -- nonnegative integer; length of the binary strings
2165
+
2166
+ - ``k`` -- nonnegative integer; number of 1s per binary string
2167
+
2168
+ EXAMPLES::
2169
+
2170
+ sage: g = graphs.HyperStarGraph(6,3)
2171
+ sage: sorted(g.neighbors('011100'))
2172
+ ['101100', '110100', '111000']
2173
+ sage: g.plot() # long time # needs sage.plot
2174
+ Graphics object consisting of 51 graphics primitives
2175
+
2176
+ TESTS::
2177
+
2178
+ sage: graphs.HyperStarGraph(-1, 1)
2179
+ Traceback (most recent call last):
2180
+ ...
2181
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2182
+ sage: graphs.HyperStarGraph(1, -1)
2183
+ Traceback (most recent call last):
2184
+ ...
2185
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2186
+ sage: graphs.HyperStarGraph(1, 2)
2187
+ Traceback (most recent call last):
2188
+ ...
2189
+ ValueError: parameters n and k must be nonnegative integers satisfying n >= k >= 0
2190
+
2191
+ AUTHORS:
2192
+
2193
+ - Michael Yurko (2009-09-01)
2194
+ """
2195
+ if n < 0 or k < 0 or k > n:
2196
+ raise ValueError("parameters n and k must be nonnegative integers "
2197
+ "satisfying n >= k >= 0")
2198
+ if not n:
2199
+ adj = {}
2200
+ elif not k:
2201
+ adj = {'0'*n: []}
2202
+ elif k == n:
2203
+ adj = {'1'*n: []}
2204
+ else:
2205
+ from sage.data_structures.bitset import Bitset
2206
+ adj = dict()
2207
+ # We consider the strings of n bits with k 1s and starting with a 0
2208
+ for c in combinations(range(1, n), k):
2209
+ u = str(Bitset(c, capacity=n))
2210
+ L = []
2211
+ c = list(c)
2212
+ # The neighbors of u are all the strings obtained by swapping a 1
2213
+ # with the first bit (0)
2214
+ for i in range(k):
2215
+ one = c[i]
2216
+ c[i] = 0
2217
+ L.append(str(Bitset(c, capacity=n)))
2218
+ c[i] = one
2219
+ adj[u] = L
2220
+
2221
+ return Graph(adj, format='dict_of_lists', name=f"HS({n},{k})")
2222
+
2223
+
2224
+ def LCFGraph(n, shift_list, repeats):
2225
+ r"""
2226
+ Return the cubic graph specified in LCF notation.
2227
+
2228
+ LCF (Lederberg-Coxeter-Fruchte) notation is a concise way of describing
2229
+ cubic Hamiltonian graphs. The way a graph is constructed is as
2230
+ follows. Since there is a Hamiltonian cycle, we first create a cycle on `n`
2231
+ nodes. The variable ``shift_list`` = `[s_0, s_1, ..., s_{k-1}]` describes
2232
+ edges to be created by the following scheme: for each `i \in \{0, 1, \dots,
2233
+ k-1\}`, connect vertex `i` to vertex `(i + s_i) \pmod{n}`. Then, ``repeats``
2234
+ specifies the number of times to repeat this process, where on the `j`-th
2235
+ repeat we connect vertex `(i + j k) \pmod{n}` to vertex `(i + j k + s_i)
2236
+ \pmod{n}`.
2237
+
2238
+ For more details, see the :wikipedia:`LCF_notation` and [Fru1977]_,
2239
+ [Gru2003]_ pp. 357-365, and [Led1965]_.
2240
+
2241
+ INPUT:
2242
+
2243
+ - ``n`` -- the number of nodes
2244
+
2245
+ - ``shift_list`` -- a list of integer shifts mod `n`
2246
+
2247
+ - ``repeats`` -- the number of times to repeat the process
2248
+
2249
+ EXAMPLES::
2250
+
2251
+ sage: G = graphs.LCFGraph(4, [2,-2], 2) # needs networkx
2252
+ sage: G.is_isomorphic(graphs.TetrahedralGraph()) # needs networkx
2253
+ True
2254
+
2255
+ ::
2256
+
2257
+ sage: G = graphs.LCFGraph(20, [10,7,4,-4,-7,10,-4,7,-7,4], 2) # needs networkx
2258
+ sage: G.is_isomorphic(graphs.DodecahedralGraph()) # needs networkx
2259
+ True
2260
+
2261
+ ::
2262
+
2263
+ sage: G = graphs.LCFGraph(14, [5,-5], 7) # needs networkx
2264
+ sage: G.is_isomorphic(graphs.HeawoodGraph()) # needs networkx
2265
+ True
2266
+
2267
+ The largest cubic nonplanar graph of diameter three::
2268
+
2269
+ sage: # needs networkx
2270
+ sage: G = graphs.LCFGraph(20, [-10,-7,-5,4,7,-10,-7,-4,5,7,
2271
+ ....: -10,-7,6,-5,7,-10,-7,5,-6,7], 1)
2272
+ sage: G.degree()
2273
+ [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
2274
+ sage: G.diameter()
2275
+ 3
2276
+ sage: G.show() # long time # needs sage.plot
2277
+
2278
+ PLOTTING: LCF Graphs are plotted as an `n`-cycle with edges in the
2279
+ middle, as described above.
2280
+ """
2281
+ import networkx
2282
+ G = Graph(networkx.LCF_graph(n, shift_list, repeats), name="LCF Graph")
2283
+ G._circle_embedding(list(range(n)), radius=1, angle=pi/2)
2284
+ return G
2285
+
2286
+
2287
+ def MycielskiGraph(k=1, relabel=True):
2288
+ r"""
2289
+ Return the `k`-th Mycielski Graph.
2290
+
2291
+ The graph `M_k` is triangle-free and has chromatic number
2292
+ equal to `k`. These graphs show, constructively, that there
2293
+ are triangle-free graphs with arbitrarily high chromatic
2294
+ number.
2295
+
2296
+ The Mycielski graphs are built recursively starting with
2297
+ `M_0`, an empty graph; `M_1`, a single vertex graph; and `M_2`
2298
+ is the graph `K_2`. `M_{k+1}` is then built from `M_k`
2299
+ as follows:
2300
+
2301
+ If the vertices of `M_k` are `v_1,\ldots,v_n`, then the
2302
+ vertices of `M_{k+1}` are
2303
+ `v_1,\ldots,v_n,w_1,\ldots,w_n,z`. Vertices `v_1,\ldots,v_n`
2304
+ induce a copy of `M_k`. Vertices `w_1,\ldots,w_n` are an
2305
+ independent set. Vertex `z` is adjacent to all the
2306
+ `w_i`-vertices. Finally, vertex `w_i` is adjacent to vertex
2307
+ `v_j` iff `v_i` is adjacent to `v_j`.
2308
+
2309
+ For more details, see the :wikipedia:`Mycielskian`.
2310
+
2311
+ INPUT:
2312
+
2313
+ - ``k`` -- number of steps in the construction process
2314
+
2315
+ - ``relabel`` -- relabel the vertices so their names are the integers
2316
+ ``range(n)`` where ``n`` is the number of vertices in the graph
2317
+
2318
+ EXAMPLES:
2319
+
2320
+ The Mycielski graph `M_k` is triangle-free and has chromatic
2321
+ number equal to `k`. ::
2322
+
2323
+ sage: g = graphs.MycielskiGraph(5)
2324
+ sage: g.is_triangle_free()
2325
+ True
2326
+ sage: g.chromatic_number() # needs cliquer
2327
+ 5
2328
+
2329
+ The graphs `M_4` is (isomorphic to) the Grotzsch graph. ::
2330
+
2331
+ sage: g = graphs.MycielskiGraph(4)
2332
+ sage: g.is_isomorphic(graphs.GrotzschGraph())
2333
+ True
2334
+ """
2335
+ g = Graph()
2336
+ g.name("Mycielski Graph " + str(k))
2337
+
2338
+ if k < 0:
2339
+ raise ValueError("parameter k must be a nonnegative integer")
2340
+
2341
+ if k == 0:
2342
+ return g
2343
+
2344
+ if k == 1:
2345
+ g.add_vertex(0)
2346
+ return g
2347
+
2348
+ if k == 2:
2349
+ g.add_edge(0, 1)
2350
+ return g
2351
+
2352
+ g0 = MycielskiGraph(k - 1)
2353
+ g = MycielskiStep(g0)
2354
+ g.name("Mycielski Graph " + str(k))
2355
+ if relabel:
2356
+ g.relabel()
2357
+
2358
+ return g
2359
+
2360
+
2361
+ def MycielskiStep(g):
2362
+ r"""
2363
+ Perform one iteration of the Mycielski construction.
2364
+
2365
+ See the documentation for ``MycielskiGraph`` which uses this
2366
+ method. We expose it to all users in case they may find it
2367
+ useful.
2368
+
2369
+ EXAMPLE. One iteration of the Mycielski step applied to the
2370
+ 5-cycle yields a graph isomorphic to the Grotzsch graph ::
2371
+
2372
+ sage: g = graphs.CycleGraph(5)
2373
+ sage: h = graphs.MycielskiStep(g)
2374
+ sage: h.is_isomorphic(graphs.GrotzschGraph())
2375
+ True
2376
+ """
2377
+ # Make a copy of the input graph g
2378
+ gg = copy(g)
2379
+
2380
+ # rename a vertex v of gg as (1,v)
2381
+ renamer = {v: (1, v) for v in g}
2382
+ gg.relabel(renamer)
2383
+
2384
+ # add the w vertices to gg as (2,v)
2385
+ wlist = [(2, v) for v in g]
2386
+ gg.add_vertices(wlist)
2387
+
2388
+ # add the z vertex as (0,0)
2389
+ gg.add_vertex((0, 0))
2390
+
2391
+ # add the edges from z to w_i
2392
+ gg.add_edges([((0, 0), (2, v)) for v in g])
2393
+
2394
+ # make the v_i w_j edges
2395
+ for v in g:
2396
+ gg.add_edges([((1, v), (2, vv)) for vv in g.neighbors(v)])
2397
+
2398
+ return gg
2399
+
2400
+
2401
+ def NKStarGraph(n, k):
2402
+ r"""
2403
+ Return the `(n,k)`-star graph.
2404
+
2405
+ The vertices of the `(n,k)`-star graph are the set of all arrangements of
2406
+ `n` symbols into labels of length `k`. There are two adjacency rules for the
2407
+ `(n,k)`-star graph. First, two vertices are adjacent if one can be obtained
2408
+ from the other by swapping the first symbol with another symbol. Second, two
2409
+ vertices are adjacent if one can be obtained from the other by swapping the
2410
+ first symbol with an external symbol (a symbol not used in the original
2411
+ label).
2412
+
2413
+ INPUT:
2414
+
2415
+ - ``n`` -- integer; number of symbols
2416
+
2417
+ - ``k`` -- integer; length of the labels of the vertices
2418
+
2419
+ EXAMPLES::
2420
+
2421
+ sage: g = graphs.NKStarGraph(4,2)
2422
+ sage: g.plot() # long time # needs sage.plot
2423
+ Graphics object consisting of 31 graphics primitives
2424
+
2425
+ REFERENCES:
2426
+
2427
+ [CC1995]_
2428
+
2429
+ AUTHORS:
2430
+
2431
+ - Michael Yurko (2009-09-01)
2432
+ """
2433
+ from sage.combinat.permutation import Arrangements
2434
+ # set from which to permute
2435
+ set = [str(i) for i in range(1, n + 1)]
2436
+ # create dict
2437
+ d = {}
2438
+ for v in Arrangements(set, k):
2439
+ v = list(v) # So we can easily mutate it
2440
+ tmp_dict = {}
2441
+ # add edges of dimension i
2442
+ for i in range(1, k):
2443
+ # swap 0th and ith element
2444
+ v[0], v[i] = v[i], v[0]
2445
+ # convert to str and add to list
2446
+ vert = "".join(v)
2447
+ tmp_dict[vert] = None
2448
+ # swap back
2449
+ v[0], v[i] = v[i], v[0]
2450
+ # add other edges
2451
+ tmp_bit = v[0]
2452
+ for i in set:
2453
+ # check if external
2454
+ if i not in v:
2455
+ v[0] = i
2456
+ # add edge
2457
+ vert = "".join(v)
2458
+ tmp_dict[vert] = None
2459
+ v[0] = tmp_bit
2460
+ d["".join(v)] = tmp_dict
2461
+ return Graph(d, name=f"({n},{k})-star")
2462
+
2463
+
2464
+ def NStarGraph(n):
2465
+ r"""
2466
+ Return the `n`-star graph.
2467
+
2468
+ The vertices of the `n`-star graph are the set of permutations on `n`
2469
+ symbols. There is an edge between two vertices if their labels differ
2470
+ only in the first and one other position.
2471
+
2472
+ INPUT:
2473
+
2474
+ - ``n`` -- integer; number of symbols
2475
+
2476
+ EXAMPLES::
2477
+
2478
+ sage: g = graphs.NStarGraph(4)
2479
+ sage: g.plot() # long time # needs sage.plot
2480
+ Graphics object consisting of 61 graphics primitives
2481
+
2482
+ REFERENCES:
2483
+
2484
+ [AHK1994]_
2485
+
2486
+ AUTHORS:
2487
+
2488
+ - Michael Yurko (2009-09-01)
2489
+ """
2490
+ from sage.combinat.permutation import Permutations
2491
+ # set from which to permute
2492
+ set = [str(i) for i in range(1, n + 1)]
2493
+ # create dictionary of lists
2494
+ # vertices are adjacent if the first element is swapped with the ith element
2495
+ d = {}
2496
+ for v in Permutations(set):
2497
+ v = list(v) # So we can easily mutate it
2498
+ tmp_dict = {}
2499
+ for i in range(1, n):
2500
+ if v[0] != v[i]:
2501
+ # swap 0th and ith element
2502
+ v[0], v[i] = v[i], v[0]
2503
+ # convert to str and add to list
2504
+ vert = "".join(v)
2505
+ tmp_dict[vert] = None
2506
+ # swap back
2507
+ v[0], v[i] = v[i], v[0]
2508
+ d["".join(v)] = tmp_dict
2509
+ return Graph(d, name=f"{n}-star")
2510
+
2511
+
2512
+ def OddGraph(n):
2513
+ r"""
2514
+ Return the Odd Graph with parameter `n`.
2515
+
2516
+ The Odd Graph with parameter `n` is defined as the
2517
+ Kneser Graph with parameters `2n-1,n-1`.
2518
+ Equivalently, the Odd Graph is the graph whose vertices
2519
+ are the `n-1`-subsets of `[0,1,\dots,2(n-1)]`, and such
2520
+ that two vertices are adjacent if their corresponding sets
2521
+ are disjoint.
2522
+
2523
+ For example, the Petersen Graph can be defined
2524
+ as the Odd Graph with parameter `3`.
2525
+
2526
+ EXAMPLES::
2527
+
2528
+ sage: OG = graphs.OddGraph(3)
2529
+ sage: sorted(OG.vertex_iterator(), key=str)
2530
+ [{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5},
2531
+ {3, 4}, {3, 5}, {4, 5}]
2532
+ sage: P = graphs.PetersenGraph()
2533
+ sage: P.is_isomorphic(OG)
2534
+ True
2535
+
2536
+ TESTS::
2537
+
2538
+ sage: KG = graphs.OddGraph(1)
2539
+ Traceback (most recent call last):
2540
+ ...
2541
+ ValueError: Parameter n should be an integer strictly greater than 1
2542
+ """
2543
+ if n <= 1:
2544
+ raise ValueError("Parameter n should be an integer strictly greater than 1")
2545
+ g = KneserGraph(2*n - 1, n - 1)
2546
+ g.name("Odd Graph with parameter %s" % n)
2547
+ return g
2548
+
2549
+
2550
+ def PaleyGraph(q):
2551
+ r"""
2552
+ Paley graph with `q` vertices.
2553
+
2554
+ Parameter `q` must be the power of a prime number and congruent
2555
+ to 1 mod 4.
2556
+
2557
+ EXAMPLES::
2558
+
2559
+ sage: G = graphs.PaleyGraph(9); G # needs sage.rings.finite_rings
2560
+ Paley graph with parameter 9: Graph on 9 vertices
2561
+ sage: G.is_regular() # needs sage.rings.finite_rings
2562
+ True
2563
+
2564
+ A Paley graph is always self-complementary::
2565
+
2566
+ sage: G.is_self_complementary() # needs sage.rings.finite_rings
2567
+ True
2568
+
2569
+ TESTS:
2570
+
2571
+ Wrong parameter::
2572
+
2573
+ sage: graphs.PaleyGraph(6)
2574
+ Traceback (most recent call last):
2575
+ ...
2576
+ ValueError: parameter q must be a prime power
2577
+ sage: graphs.PaleyGraph(3)
2578
+ Traceback (most recent call last):
2579
+ ...
2580
+ ValueError: parameter q must be congruent to 1 mod 4
2581
+ """
2582
+ from sage.rings.finite_rings.integer_mod import mod
2583
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
2584
+ from sage.arith.misc import is_prime_power
2585
+ if not is_prime_power(q):
2586
+ raise ValueError("parameter q must be a prime power")
2587
+ if not mod(q, 4) == 1:
2588
+ raise ValueError("parameter q must be congruent to 1 mod 4")
2589
+ g = Graph([FiniteField(q, 'a'), lambda i, j: (i - j).is_square()],
2590
+ loops=False, name=f"Paley graph with parameter {q}")
2591
+ return g
2592
+
2593
+
2594
+ def PasechnikGraph(n):
2595
+ r"""
2596
+ Pasechnik strongly regular graph on `(4n-1)^2` vertices.
2597
+
2598
+ A strongly regular graph with parameters of the orthogonal array graph
2599
+ :func:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`,
2600
+ also known as pseudo Latin squares graph `L_{2n-1}(4n-1)`, constructed from
2601
+ a skew Hadamard matrix of order `4n` following [Pas1992]_.
2602
+
2603
+ .. SEEALSO::
2604
+
2605
+ - :func:`~sage.graphs.strongly_regular_db.is_orthogonal_array_block_graph`
2606
+
2607
+ EXAMPLES::
2608
+
2609
+ sage: graphs.PasechnikGraph(4).is_strongly_regular(parameters=True) # needs sage.combinat sage.modules
2610
+ (225, 98, 43, 42)
2611
+ sage: graphs.PasechnikGraph(5).is_strongly_regular(parameters=True) # long time, needs sage.combinat sage.modules
2612
+ (361, 162, 73, 72)
2613
+ sage: graphs.PasechnikGraph(9).is_strongly_regular(parameters=True) # not tested
2614
+ (1225, 578, 273, 272)
2615
+
2616
+ TESTS::
2617
+
2618
+ sage: graphs.PasechnikGraph(0)
2619
+ Traceback (most recent call last):
2620
+ ...
2621
+ ValueError: parameter n must be >= 1
2622
+ """
2623
+ if n < 1:
2624
+ raise ValueError("parameter n must be >= 1")
2625
+ from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix
2626
+ from sage.matrix.constructor import identity_matrix
2627
+ H = skew_hadamard_matrix(4 * n)
2628
+ M = H[1:].T[1:] - identity_matrix(4 * n - 1)
2629
+ G = Graph(M.tensor_product(M.T), format='seidel_adjacency_matrix')
2630
+ G.relabel()
2631
+ G.name("Pasechnik Graph_{}".format(n))
2632
+ return G
2633
+
2634
+
2635
+ def SquaredSkewHadamardMatrixGraph(n):
2636
+ r"""
2637
+ Pseudo-`OA(2n,4n-1)`-graph from a skew Hadamard matrix of order `4n`.
2638
+
2639
+ A strongly regular graph with parameters of the orthogonal array graph
2640
+ :func:`~sage.graphs.graph_generators.GraphGenerators.OrthogonalArrayBlockGraph`,
2641
+ also known as pseudo Latin squares graph `L_{2n}(4n-1)`, constructed from a
2642
+ skew Hadamard matrix of order `4n`, due to Goethals and Seidel, see
2643
+ [BL1984]_.
2644
+
2645
+ .. SEEALSO::
2646
+
2647
+ - :func:`~sage.graphs.strongly_regular_db.is_orthogonal_array_block_graph`
2648
+
2649
+ EXAMPLES::
2650
+
2651
+ sage: # needs sage.combinat sage.modules
2652
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(4)
2653
+ sage: G.is_strongly_regular(parameters=True)
2654
+ (225, 112, 55, 56)
2655
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(5)
2656
+ sage: G.is_strongly_regular(parameters=True) # long time
2657
+ (361, 180, 89, 90)
2658
+ sage: G = graphs.SquaredSkewHadamardMatrixGraph(9)
2659
+ sage: G.is_strongly_regular(parameters=True) # not tested
2660
+ (1225, 612, 305, 306)
2661
+
2662
+ TESTS::
2663
+
2664
+ sage: graphs.SquaredSkewHadamardMatrixGraph(0)
2665
+ Traceback (most recent call last):
2666
+ ...
2667
+ ValueError: parameter n must be >= 1
2668
+ """
2669
+ if n < 1:
2670
+ raise ValueError("parameter n must be >= 1")
2671
+ from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix
2672
+ from sage.matrix.constructor import identity_matrix, matrix
2673
+ idm = identity_matrix(4 * n - 1)
2674
+ e = matrix([1] * (4 * n - 1))
2675
+ H = skew_hadamard_matrix(4 * n)
2676
+ M = H[1:].T[1:] - idm
2677
+ s = M.tensor_product(M.T) - idm.tensor_product(e.T * e - idm)
2678
+ G = Graph(s, format='seidel_adjacency_matrix')
2679
+ G.relabel()
2680
+ G.name("skewhad^2_{}".format(n))
2681
+ return G
2682
+
2683
+
2684
+ def SwitchedSquaredSkewHadamardMatrixGraph(n):
2685
+ r"""
2686
+ A strongly regular graph in Seidel switching class of
2687
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.SquaredSkewHadamardMatrixGraph`.
2688
+
2689
+ A strongly regular graph in the :meth:`Seidel switching
2690
+ <Graph.seidel_switching>` class of the disjoint union of a 1-vertex graph
2691
+ and the one produced by :func:`Pseudo-L_{2n}(4n-1)
2692
+ <sage.graphs.graph_generators.GraphGenerators.SquaredSkewHadamardMatrixGraph>`
2693
+
2694
+ In this case, the other possible parameter set of a strongly regular graph
2695
+ in the Seidel switching class of the latter graph (see [BH2012]_) coincides
2696
+ with the set of parameters of the complement of the graph returned by this
2697
+ function.
2698
+
2699
+ .. SEEALSO::
2700
+
2701
+ - :func:`~sage.graphs.strongly_regular_db.is_switch_skewhad`
2702
+
2703
+ EXAMPLES::
2704
+
2705
+ sage: # needs sage.combinat sage.modules
2706
+ sage: g = graphs.SwitchedSquaredSkewHadamardMatrixGraph(4)
2707
+ sage: g.is_strongly_regular(parameters=True)
2708
+ (226, 105, 48, 49)
2709
+ sage: from sage.combinat.designs.twographs import twograph_descendant
2710
+ sage: twograph_descendant(g, 0).is_strongly_regular(parameters=True)
2711
+ (225, 112, 55, 56)
2712
+ sage: gc = g.complement()
2713
+ sage: twograph_descendant(gc, 0).is_strongly_regular(parameters=True)
2714
+ (225, 112, 55, 56)
2715
+
2716
+ TESTS::
2717
+
2718
+ sage: graphs.SwitchedSquaredSkewHadamardMatrixGraph(0)
2719
+ Traceback (most recent call last):
2720
+ ...
2721
+ ValueError: parameter n must be >= 1
2722
+ """
2723
+ G = SquaredSkewHadamardMatrixGraph(n).complement()
2724
+ G.add_vertex((4 * n - 1)**2)
2725
+ G.seidel_switching(list(range((4 * n - 1) * (2 * n - 1))))
2726
+ G.name("switch skewhad^2+*_" + str(n))
2727
+ return G
2728
+
2729
+
2730
+ def HanoiTowerGraph(pegs, disks, labels=True, positions=True):
2731
+ r"""
2732
+ Return the graph whose vertices are the states of the
2733
+ Tower of Hanoi puzzle, with edges representing legal moves between states.
2734
+
2735
+ INPUT:
2736
+
2737
+ - ``pegs`` -- the number of pegs in the puzzle, 2 or greater
2738
+ - ``disks`` -- the number of disks in the puzzle, 1 or greater
2739
+ - ``labels`` -- (default: ``True``) if ``True`` the graph contains
2740
+ more meaningful labels, see explanation below. For large instances,
2741
+ turn off labels for much faster creation of the graph.
2742
+ - ``positions`` -- (default: ``True``) if ``True`` the graph contains
2743
+ layout information. This creates a planar layout for the case
2744
+ of three pegs. For large instances, turn off layout information
2745
+ for much faster creation of the graph.
2746
+
2747
+ OUTPUT:
2748
+
2749
+ The Tower of Hanoi puzzle has a certain number of identical pegs
2750
+ and a certain number of disks, each of a different radius.
2751
+ Initially the disks are all on a single peg, arranged
2752
+ in order of their radii, with the largest on the bottom.
2753
+
2754
+ The goal of the puzzle is to move the disks to any other peg,
2755
+ arranged in the same order. The one constraint is that the
2756
+ disks resident on any one peg must always be arranged with larger
2757
+ radii lower down.
2758
+
2759
+ The vertices of this graph represent all the possible states
2760
+ of this puzzle. Each state of the puzzle is a tuple with length
2761
+ equal to the number of disks, ordered by largest disk first.
2762
+ The entry of the tuple is the peg where that disk resides.
2763
+ Since disks on a given peg must go down in size as we go
2764
+ up the peg, this totally describes the state of the puzzle.
2765
+
2766
+ For example ``(2,0,0)`` means the large disk is on peg 2, the
2767
+ medium disk is on peg 0, and the small disk is on peg 0
2768
+ (and we know the small disk must be above the medium disk).
2769
+ We encode these tuples as integers with a base equal to
2770
+ the number of pegs, and low-order digits to the right.
2771
+
2772
+ Two vertices are adjacent if we can change the puzzle from
2773
+ one state to the other by moving a single disk. For example,
2774
+ ``(2,0,0)`` is adjacent to ``(2,0,1)`` since we can move
2775
+ the small disk off peg 0 and onto (the empty) peg 1.
2776
+ So the solution to a 3-disk puzzle (with at least
2777
+ two pegs) can be expressed by the shortest path between
2778
+ ``(0,0,0)`` and ``(1,1,1)``. For more on this representation
2779
+ of the graph, or its properties, see [AD2010]_.
2780
+
2781
+ For greatest speed we create graphs with integer vertices,
2782
+ where we encode the tuples as integers with a base equal
2783
+ to the number of pegs, and low-order digits to the right.
2784
+ So for example, in a 3-peg puzzle with 5 disks, the
2785
+ state ``(1,2,0,1,1)`` is encoded as
2786
+ `1\ast 3^4 + 2\ast 3^3 + 0\ast 3^2 + 1\ast 3^1 + 1\ast 3^0 = 139`.
2787
+
2788
+ For smaller graphs, the labels that are the tuples are informative,
2789
+ but slow down creation of the graph. Likewise computing layout
2790
+ information also incurs a significant speed penalty. For maximum
2791
+ speed, turn off labels and layout and decode the
2792
+ vertices explicitly as needed. The
2793
+ :meth:`sage.rings.integer.Integer.digits`
2794
+ with the ``padsto`` option is a quick way to do this, though you
2795
+ may want to reverse the list that is output.
2796
+
2797
+ .. SEEALSO::
2798
+
2799
+ - :meth:`~sage.graphs.generators.families.GeneralizedSierpinskiGraph`
2800
+
2801
+ PLOTTING:
2802
+
2803
+ The layout computed when ``positions = True`` will
2804
+ look especially good for the three-peg case, when the graph is known
2805
+ to be planar. Except for two small cases on 4 pegs, the graph is
2806
+ otherwise not planar, and likely there is a better way to layout
2807
+ the vertices.
2808
+
2809
+ EXAMPLES:
2810
+
2811
+ A classic puzzle uses 3 pegs. We solve the 5 disk puzzle using
2812
+ integer labels and report the minimum number of moves required.
2813
+ Note that `3^5-1` is the state where all 5 disks
2814
+ are on peg 2. ::
2815
+
2816
+ sage: H = graphs.HanoiTowerGraph(3, 5, labels=False, positions=False)
2817
+ sage: H.distance(0, 3^5-1)
2818
+ 31
2819
+
2820
+ A slightly larger instance. ::
2821
+
2822
+ sage: H = graphs.HanoiTowerGraph(4, 6, labels=False, positions=False)
2823
+ sage: H.num_verts()
2824
+ 4096
2825
+ sage: H.distance(0, 4^6-1)
2826
+ 17
2827
+
2828
+ For a small graph, labels and layout information can be useful.
2829
+ Here we explicitly list a solution as a list of states. ::
2830
+
2831
+ sage: H = graphs.HanoiTowerGraph(3, 3, labels=True, positions=True)
2832
+ sage: H.shortest_path((0,0,0), (1,1,1))
2833
+ [(0, 0, 0), (0, 0, 1), (0, 2, 1), (0, 2, 2), (1, 2, 2), (1, 2, 0), (1, 1, 0), (1, 1, 1)]
2834
+
2835
+ Some facts about this graph with `p` pegs and `d` disks:
2836
+
2837
+ - only automorphisms are the "obvious" ones -- renumber the pegs
2838
+ - chromatic number is less than or equal to `p`
2839
+ - independence number is `p^{d-1}`
2840
+
2841
+ ::
2842
+
2843
+ sage: H = graphs.HanoiTowerGraph(3, 4, labels=False, positions=False)
2844
+ sage: H.automorphism_group().is_isomorphic(SymmetricGroup(3)) # needs sage.groups
2845
+ True
2846
+ sage: H.chromatic_number() # needs cliquer
2847
+ 3
2848
+ sage: len(H.independent_set()) == 3^(4-1)
2849
+ True
2850
+
2851
+ TESTS:
2852
+
2853
+ It is an error to have just one peg (or less). ::
2854
+
2855
+ sage: graphs.HanoiTowerGraph(1, 5)
2856
+ Traceback (most recent call last):
2857
+ ...
2858
+ ValueError: Pegs for Tower of Hanoi graph should be two or greater (not 1)
2859
+
2860
+ It is an error to have zero disks (or less). ::
2861
+
2862
+ sage: graphs.HanoiTowerGraph(2, 0)
2863
+ Traceback (most recent call last):
2864
+ ...
2865
+ ValueError: Disks for Tower of Hanoi graph should be one or greater (not 0)
2866
+
2867
+ AUTHOR:
2868
+
2869
+ - Rob Beezer, (2009-12-26), with assistance from Su Doree
2870
+ """
2871
+ # sanitize input
2872
+ from sage.rings.integer import Integer
2873
+ pegs = Integer(pegs)
2874
+ if pegs < 2:
2875
+ raise ValueError("Pegs for Tower of Hanoi graph should be two or greater (not %d)" % pegs)
2876
+ disks = Integer(disks)
2877
+ if disks < 1:
2878
+ raise ValueError("Disks for Tower of Hanoi graph should be one or greater (not %d)" % disks)
2879
+
2880
+ # Each state of the puzzle is a tuple with length
2881
+ # equal to the number of disks, ordered by largest disk first
2882
+ # The entry of the tuple is the peg where that disk resides
2883
+ # Since disks on a given peg must go down in size as we go
2884
+ # up the peg, this totally describes the puzzle
2885
+ # We encode these tuples as integers with a base equal to
2886
+ # the number of pegs, and low-order digits to the right
2887
+
2888
+ # complete graph on number of pegs when just a single disk
2889
+ edges = [[i, j] for i in range(pegs) for j in range(i + 1, pegs)]
2890
+
2891
+ nverts = 1
2892
+ for d in range(2, disks+1):
2893
+ prevedges = edges # remember subgraph to build from
2894
+ nverts = pegs*nverts # pegs^(d-1)
2895
+ edges = []
2896
+
2897
+ # Take an edge, change its two states in the same way by adding
2898
+ # a large disk to the bottom of the same peg in each state
2899
+ # This is accomplished by adding a multiple of pegs^(d-1)
2900
+ for p in range(pegs):
2901
+ largedisk = p*nverts
2902
+ for anedge in prevedges:
2903
+ edges.append([anedge[0] + largedisk, anedge[1] + largedisk])
2904
+
2905
+ # Two new states may only differ in the large disk
2906
+ # being the only disk on two different pegs, thus
2907
+ # otherwise being a common state with one less disk
2908
+ # We construct all such pairs of new states and add as edges
2909
+ from sage.combinat.subset import Subsets
2910
+ for state in range(nverts):
2911
+ emptypegs = list(range(pegs))
2912
+ reduced_state = state
2913
+ for i in range(d-1):
2914
+ apeg = reduced_state % pegs
2915
+ if apeg in emptypegs:
2916
+ emptypegs.remove(apeg)
2917
+ reduced_state = reduced_state//pegs
2918
+ for freea, freeb in Subsets(emptypegs, 2):
2919
+ edges.append([freea*nverts + state, freeb*nverts + state])
2920
+
2921
+ H = Graph({}, loops=False, multiedges=False)
2922
+ H.add_edges(edges)
2923
+
2924
+ # Making labels and/or computing positions can take a long time,
2925
+ # relative to just constructing the edges on integer vertices.
2926
+ # We try to minimize coercion overhead, but need Sage
2927
+ # Integers in order to use digits() for labels.
2928
+ # Getting the digits with custom code was no faster.
2929
+ # Layouts are circular (symmetric on the number of pegs)
2930
+ # radiating outward to the number of disks (radius)
2931
+ # Algorithm uses some combination of alternate
2932
+ # clockwise/counterclockwise placements, which
2933
+ # works well for three pegs (planar layout)
2934
+ #
2935
+ if labels or positions:
2936
+ mapping = {}
2937
+ pos = {}
2938
+ a = Integer(-1)
2939
+ one = Integer(1)
2940
+ if positions:
2941
+ radius_multiplier = 1 + 1/sin(pi/pegs)
2942
+ sine = []
2943
+ cosine = []
2944
+ for i in range(pegs):
2945
+ angle = 2*i*pi/float(pegs)
2946
+ sine.append(sin(angle))
2947
+ cosine.append(cos(angle))
2948
+ for i in range(pegs**disks):
2949
+ a += one
2950
+ state = a.digits(base=pegs, padto=disks)
2951
+ if labels:
2952
+ state.reverse()
2953
+ mapping[i] = tuple(state)
2954
+ state.reverse()
2955
+ if positions:
2956
+ locx = 0.0
2957
+ locy = 0.0
2958
+ radius = 1.0
2959
+ parity = -1.0
2960
+ for index in range(disks):
2961
+ p = state[index]
2962
+ radius *= radius_multiplier
2963
+ parity *= -1.0
2964
+ locx_temp = cosine[p]*locx - parity*sine[p]*locy + radius*cosine[p]
2965
+ locy_temp = parity*sine[p]*locx + cosine[p]*locy - radius*parity*sine[p]
2966
+ locx = locx_temp
2967
+ locy = locy_temp
2968
+ pos[i] = (locx, locy)
2969
+ # set positions, then relabel (not vice versa)
2970
+ if positions:
2971
+ H.set_pos(pos)
2972
+ if labels:
2973
+ H.relabel(mapping)
2974
+
2975
+ return H
2976
+
2977
+
2978
+ def line_graph_forbidden_subgraphs():
2979
+ r"""
2980
+ Return the 9 forbidden subgraphs of a line graph.
2981
+
2982
+ See the :wikipedia:`Line_graph` for more information.
2983
+
2984
+ The graphs are returned in the ordering given by the Wikipedia
2985
+ drawing, read from left to right and from top to bottom.
2986
+
2987
+ EXAMPLES::
2988
+
2989
+ sage: graphs.line_graph_forbidden_subgraphs()
2990
+ [Claw graph: Graph on 4 vertices,
2991
+ Graph on 6 vertices,
2992
+ Graph on 6 vertices,
2993
+ Graph on 5 vertices,
2994
+ Graph on 6 vertices,
2995
+ Graph on 6 vertices,
2996
+ Graph on 6 vertices,
2997
+ Graph on 6 vertices,
2998
+ Graph on 5 vertices]
2999
+ """
3000
+ from sage.graphs.graph import Graph
3001
+ from sage.graphs.generators.basic import ClawGraph
3002
+ graphs = [ClawGraph()]
3003
+
3004
+ graphs.append(Graph({
3005
+ 0: [1, 2, 3],
3006
+ 1: [2, 3],
3007
+ 4: [2],
3008
+ 5: [3]
3009
+ }))
3010
+
3011
+ graphs.append(Graph({
3012
+ 0: [1, 2, 3, 4],
3013
+ 1: [2, 3, 4],
3014
+ 3: [4],
3015
+ 2: [5]
3016
+ }))
3017
+
3018
+ graphs.append(Graph({
3019
+ 0: [1, 2, 3],
3020
+ 1: [2, 3],
3021
+ 4: [2, 3]
3022
+ }))
3023
+
3024
+ graphs.append(Graph({
3025
+ 0: [1, 2, 3],
3026
+ 1: [2, 3],
3027
+ 4: [2],
3028
+ 5: [3, 4]
3029
+ }))
3030
+
3031
+ graphs.append(Graph({
3032
+ 0: [1, 2, 3, 4],
3033
+ 1: [2, 3, 4],
3034
+ 3: [4],
3035
+ 5: [2, 0, 1]
3036
+ }))
3037
+
3038
+ graphs.append(Graph({
3039
+ 5: [0, 1, 2, 3, 4],
3040
+ 0: [1, 4],
3041
+ 2: [1, 3],
3042
+ 3: [4]
3043
+ }))
3044
+
3045
+ graphs.append(Graph({
3046
+ 1: [0, 2, 3, 4],
3047
+ 3: [0, 4],
3048
+ 2: [4, 5],
3049
+ 4: [5]
3050
+ }))
3051
+
3052
+ graphs.append(Graph({
3053
+ 0: [1, 2, 3],
3054
+ 1: [2, 3, 4],
3055
+ 2: [3, 4],
3056
+ 3: [4]
3057
+ }))
3058
+
3059
+ return graphs
3060
+
3061
+
3062
+ def petersen_family(generate=False):
3063
+ r"""
3064
+ Return the Petersen family.
3065
+
3066
+ The Petersen family is a collection of 7 graphs which are the forbidden
3067
+ minors of the linklessly embeddable graphs. For more information see the
3068
+ :wikipedia:`Petersen_family`.
3069
+
3070
+ INPUT:
3071
+
3072
+ - ``generate`` -- boolean; whether to generate the family from the
3073
+ `\Delta-Y` transformations. When set to ``False`` (default) a hardcoded
3074
+ version of the graphs (with a prettier layout) is returned.
3075
+
3076
+ EXAMPLES::
3077
+
3078
+ sage: graphs.petersen_family()
3079
+ [Petersen graph: Graph on 10 vertices,
3080
+ Complete graph: Graph on 6 vertices,
3081
+ Multipartite Graph with set sizes [3, 3, 1]: Graph on 7 vertices,
3082
+ Graph on 8 vertices,
3083
+ Graph on 9 vertices,
3084
+ Graph on 7 vertices,
3085
+ Graph on 8 vertices]
3086
+
3087
+ The two different inputs generate the same graphs::
3088
+
3089
+ sage: F1 = graphs.petersen_family(generate=False)
3090
+ sage: F2 = graphs.petersen_family(generate=True) # needs sage.modules
3091
+ sage: F1 = [g.canonical_label().graph6_string() for g in F1]
3092
+ sage: F2 = [g.canonical_label().graph6_string() for g in F2] # needs sage.modules
3093
+ sage: set(F1) == set(F2) # needs sage.modules
3094
+ True
3095
+ """
3096
+ from sage.graphs.generators.smallgraphs import PetersenGraph
3097
+ if not generate:
3098
+ from sage.graphs.generators.basic import CompleteGraph, \
3099
+ CompleteBipartiteGraph, CompleteMultipartiteGraph
3100
+ l = [PetersenGraph(), CompleteGraph(6),
3101
+ CompleteMultipartiteGraph([3, 3, 1])]
3102
+ g = CompleteBipartiteGraph(4, 4)
3103
+ g.delete_edge(0, 4)
3104
+ g.name("")
3105
+ l.append(g)
3106
+ g = Graph('HKN?Yeb')
3107
+ g._circle_embedding([1, 2, 4, 3, 0, 5])
3108
+ g._circle_embedding([6, 7, 8], radius=.6, shift=1.25)
3109
+ l.append(g)
3110
+ g = Graph('Fs\\zw')
3111
+ g._circle_embedding([1, 2, 3])
3112
+ g._circle_embedding([4, 5, 6], radius=.7)
3113
+ g._pos[0] = (0, 0)
3114
+ l.append(g)
3115
+ g = Graph('GYQ[p{')
3116
+ g._circle_embedding([1, 4, 6, 0, 5, 7, 3], shift=0.25)
3117
+ g._pos[2] = (0, 0)
3118
+ l.append(g)
3119
+ return l
3120
+
3121
+ def DeltaYTrans(G, triangle):
3122
+ """
3123
+ Apply a Delta-Y transformation to a given triangle of G.
3124
+ """
3125
+ a, b, c = triangle
3126
+ G = G.copy()
3127
+ G.delete_edges([(a, b), (b, c), (c, a)])
3128
+ v = G.order()
3129
+ G.add_edges([(a, v), (b, v), (c, v)])
3130
+ return G.canonical_label()
3131
+
3132
+ def YDeltaTrans(G, v):
3133
+ """
3134
+ Apply a Y-Delta transformation to a given vertex v of G.
3135
+ """
3136
+ G = G.copy()
3137
+ a, b, c = G.neighbors(v)
3138
+ G.delete_vertex(v)
3139
+ G.add_cycle([a, b, c])
3140
+ return G.canonical_label()
3141
+
3142
+ # We start from the Petersen Graph, and apply Y-Delta transform
3143
+ # for as long as we generate new graphs.
3144
+ P = PetersenGraph()
3145
+
3146
+ l = set()
3147
+ l_new = [P.canonical_label().graph6_string()]
3148
+
3149
+ while l_new:
3150
+ g = l_new.pop(0)
3151
+ if g in l:
3152
+ continue
3153
+ l.add(g)
3154
+ g = Graph(g)
3155
+ # All possible Delta-Y transforms
3156
+ for t in g.subgraph_search_iterator(Graph({1: [2, 3], 2: [3]}), return_graphs=False):
3157
+ l_new.append(DeltaYTrans(g, t).graph6_string())
3158
+ # All possible Y-Delta transforms
3159
+ for v in g:
3160
+ if g.degree(v) == 3:
3161
+ l_new.append(YDeltaTrans(g, v).graph6_string())
3162
+
3163
+ return [Graph(x) for x in l]
3164
+
3165
+
3166
+ def SierpinskiGasketGraph(n):
3167
+ """
3168
+ Return the Sierpinski Gasket graph of generation `n`.
3169
+
3170
+ All vertices but 3 have valence 4.
3171
+
3172
+ INPUT:
3173
+
3174
+ - ``n`` -- integer
3175
+
3176
+ OUTPUT:
3177
+
3178
+ a graph `S_n` with `3 (3^{n-1}+1)/2` vertices and
3179
+ `3^n` edges, closely related to the famous Sierpinski triangle
3180
+ fractal.
3181
+
3182
+ All these graphs have a triangular shape, and three special
3183
+ vertices at top, bottom left and bottom right. These are the only
3184
+ vertices of valence 2, all the other ones having valence 4.
3185
+
3186
+ The graph `S_1` (generation `1`) is a triangle.
3187
+
3188
+ The graph `S_{n+1}` is obtained from the disjoint union of
3189
+ three copies A,B,C of `S_n` by identifying pairs of vertices:
3190
+ the top vertex of A with the bottom left vertex of B,
3191
+ the bottom right vertex of B with the top vertex of C,
3192
+ and the bottom left vertex of C with the bottom right vertex of A.
3193
+
3194
+ .. PLOT::
3195
+
3196
+ sphinx_plot(graphs.SierpinskiGasketGraph(4).plot(vertex_labels=False))
3197
+
3198
+ .. SEEALSO::
3199
+
3200
+ - :meth:`~sage.graphs.generators.families.HanoiTowerGraph`. There is
3201
+ another family of graphs called Sierpinski graphs, where all vertices
3202
+ but 3 have valence 3. They are available using
3203
+ ``graphs.HanoiTowerGraph(3, n)``.
3204
+ - :meth:`~sage.graphs.generators.families.GeneralizedSierpinskiGraph`
3205
+
3206
+ EXAMPLES::
3207
+
3208
+ sage: # needs sage.modules
3209
+ sage: s4 = graphs.SierpinskiGasketGraph(4); s4
3210
+ Graph on 42 vertices
3211
+ sage: s4.size()
3212
+ 81
3213
+ sage: s4.degree_histogram()
3214
+ [0, 0, 3, 0, 39]
3215
+ sage: s4.is_hamiltonian()
3216
+ True
3217
+
3218
+ REFERENCES:
3219
+
3220
+ [LLWC2011]_
3221
+ """
3222
+ from sage.modules.free_module_element import vector
3223
+ from sage.rings.rational_field import QQ
3224
+
3225
+ if n <= 0:
3226
+ raise ValueError('n should be at least 1')
3227
+
3228
+ def next_step(triangle_list):
3229
+ # compute the next subdivision
3230
+ resu = []
3231
+ for a, b, c in triangle_list:
3232
+ ab = (a + b) / 2
3233
+ bc = (b + c) / 2
3234
+ ac = (a + c) / 2
3235
+ resu += [(a, ab, ac), (ab, b, bc), (ac, bc, c)]
3236
+ return resu
3237
+
3238
+ tri_list = [list(vector(QQ, u) for u in [(0, 0), (0, 1), (1, 0)])]
3239
+ for k in range(n - 1):
3240
+ tri_list = next_step(tri_list)
3241
+ dg = Graph()
3242
+ dg.add_edges([(tuple(a), tuple(b)) for a, b, c in tri_list])
3243
+ dg.add_edges([(tuple(b), tuple(c)) for a, b, c in tri_list])
3244
+ dg.add_edges([(tuple(c), tuple(a)) for a, b, c in tri_list])
3245
+ dg.set_pos({(x, y): (x + y / 2, y * 3 / 4)
3246
+ for x, y in dg})
3247
+ dg.relabel()
3248
+ return dg
3249
+
3250
+
3251
+ def GeneralizedSierpinskiGraph(G, k, stretch=None):
3252
+ r"""
3253
+ Return the generalized Sierpinski graph of `G` of dimension `k`.
3254
+
3255
+ Generalized Sierpinski graphs have been introduced in [GKP2011]_ to
3256
+ generalize the notion of Sierpinski graphs [KM1997]_.
3257
+
3258
+ Given a graph `G = (V, E)` of order `n` and a parameter `k`, the generalized
3259
+ Sierpinski graph of `G` of dimension `k`, denoted by `S(G, k)`, can be
3260
+ constructed recursively from `G` as follows. `S(G, 1)` is isomorphic to
3261
+ `G`. To construct `S(G, k)` for `k > 1`, copy `n` times `S(G, k - 1)`, once
3262
+ per vertex `u \in V`, and add `u` at the beginning of the labels of each
3263
+ vertex in the copy of `S(G, k - 1)` corresponding to vertex `u`. Then for
3264
+ any edge `\{u, v\} \in E`, add an edge between vertex `(u, v, \ldots, v)`
3265
+ and vertex `(v, u, \ldots, u)`.
3266
+
3267
+ INPUT:
3268
+
3269
+ - ``G`` -- a sage Graph
3270
+
3271
+ - ``k`` -- integer; the dimension
3272
+
3273
+ - ``stretch`` -- integer (default: ``None``); stretching factor used to
3274
+ determine the positions of the vertices of the output graph. By default
3275
+ (``None``), this value is set to twice the maximum Euclidean distance
3276
+ between the vertices of `G`. This parameter is used only when the vertices
3277
+ of `G` have positions.
3278
+
3279
+ .. SEEALSO::
3280
+
3281
+ - :meth:`~sage.graphs.generators.families.SierpinskiGasketGraph`
3282
+ - :meth:`~sage.graphs.generators.families.HanoiTowerGraph`
3283
+
3284
+ EXAMPLES:
3285
+
3286
+ The generalized Sierpinski graph of dimension 1 of any graph `G`
3287
+ is isomorphic to `G`::
3288
+
3289
+ sage: G = graphs.RandomGNP(10, .5)
3290
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, 1)
3291
+ sage: S.is_isomorphic(G)
3292
+ True
3293
+
3294
+ When `G` is a clique of order 3, the generalized Sierpinski graphs
3295
+ of `G` are isomorphic to Hanoi Tower graphs::
3296
+
3297
+ sage: k = randint(1, 5)
3298
+ sage: S = graphs.GeneralizedSierpinskiGraph(graphs.CompleteGraph(3), k) # needs sage.modules
3299
+ sage: H = graphs.HanoiTowerGraph(3, k)
3300
+ sage: S.is_isomorphic(H) # needs sage.modules
3301
+ True
3302
+
3303
+ The generalized Sierpinski graph of dimension `k` of any graph `G` with `n`
3304
+ vertices and `m` edges has `n^k` vertices and `m\sum_{i=0}^{k-1}n^i` edges::
3305
+
3306
+ sage: # needs sage.modules
3307
+ sage: n = randint(2, 6)
3308
+ sage: k = randint(1, 5)
3309
+ sage: G = graphs.RandomGNP(n, .5)
3310
+ sage: m = G.size()
3311
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, k)
3312
+ sage: S.order() == n**k
3313
+ True
3314
+ sage: S.size() == m*sum([n**i for i in range(k)])
3315
+ True
3316
+ sage: G = graphs.CompleteGraph(n)
3317
+ sage: S = graphs.GeneralizedSierpinskiGraph(G, k)
3318
+ sage: S.order() == n**k
3319
+ True
3320
+ sage: S.size() == (n*(n - 1)/2)*sum([n**i for i in range(k)])
3321
+ True
3322
+
3323
+ The positions of the vertices of the output graph are determined from the
3324
+ positions of the vertices of `G`, if any::
3325
+
3326
+ sage: G = graphs.HouseGraph()
3327
+ sage: G.get_pos() is not None
3328
+ True
3329
+ sage: H = graphs.GeneralizedSierpinskiGraph(G, 2) # needs sage.symbolic
3330
+ sage: H.get_pos() is not None # needs sage.symbolic
3331
+ True
3332
+ sage: G = Graph([(0, 1)])
3333
+ sage: G.get_pos() is not None
3334
+ False
3335
+ sage: H = graphs.GeneralizedSierpinskiGraph(G, 2) # needs sage.symbolic
3336
+ sage: H.get_pos() is not None # needs sage.symbolic
3337
+ False
3338
+
3339
+ .. PLOT::
3340
+
3341
+ sphinx_plot(graphs.GeneralizedSierpinskiGraph(graphs.HouseGraph(), 2).plot(vertex_labels=False))
3342
+
3343
+ TESTS::
3344
+
3345
+ sage: # needs sage.modules
3346
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(), 3)
3347
+ Generalized Sierpinski Graph of Graph on 0 vertices of dimension 3: Graph on 0 vertices
3348
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(1), 3).vertices(sort=False)
3349
+ [(0, 0, 0)]
3350
+ sage: G = graphs.GeneralizedSierpinskiGraph(Graph(2), 3)
3351
+ sage: G.order(), G.size()
3352
+ (8, 0)
3353
+ sage: graphs.GeneralizedSierpinskiGraph("foo", 1)
3354
+ Traceback (most recent call last):
3355
+ ...
3356
+ ValueError: parameter G must be a Graph
3357
+ sage: graphs.GeneralizedSierpinskiGraph(Graph(), 0)
3358
+ Traceback (most recent call last):
3359
+ ...
3360
+ ValueError: parameter k must be >= 1
3361
+ """
3362
+ if not isinstance(G, Graph):
3363
+ raise ValueError("parameter G must be a Graph")
3364
+ if k < 1:
3365
+ raise ValueError("parameter k must be >= 1")
3366
+ loops = G.allows_loops()
3367
+ multiedges = G.allows_multiple_edges()
3368
+
3369
+ def rec(H, kk):
3370
+ if kk == 1:
3371
+ return H
3372
+ I = Graph(loops=loops, multiedges=multiedges)
3373
+ # add one copy of H per vertex of G
3374
+ for i in G:
3375
+ J = H.relabel(perm={u: (i,) + u for u in H}, inplace=False)
3376
+ I.add_vertices(J)
3377
+ I.add_edges(J.edge_iterator(labels=False, sort_vertices=False))
3378
+ # For each edge {u, v} of G, add edge {(u, v, ..., v), (v, u, ..., u)}
3379
+ l = len(next(H.vertex_iterator()))
3380
+ for u, v in G.edges(sort=True, labels=False):
3381
+ I.add_edge((u,) + (v,)*l, (v,) + (u,)*l)
3382
+ return rec(I, kk - 1)
3383
+
3384
+ H = G.relabel(perm={u: (u,) for u in G}, inplace=False)
3385
+ if H and k > 1:
3386
+ H = rec(H, k)
3387
+ H.name("Generalized Sierpinski Graph of {} of dimension {}".format(G, k))
3388
+
3389
+ # If the vertices of G have positions, we set the positions of vertices of H
3390
+ pos = G.get_pos()
3391
+ if pos:
3392
+ if stretch is None:
3393
+ # Find the geometric diameter
3394
+ from sage.modules.free_module_element import vector
3395
+ L = [vector(p) for p in pos.values()]
3396
+ stretch = 2 * max((u - v).norm() for u, v in combinations(L, 2))
3397
+
3398
+ H.set_pos({u: (sum(pos[x][0]*stretch**(k-i) for i, x in enumerate(u)),
3399
+ sum(pos[y][1]*stretch**(k-i) for i, y in enumerate(u)))
3400
+ for u in H})
3401
+ return H
3402
+
3403
+
3404
+ def WheelGraph(n):
3405
+ """
3406
+ Return a Wheel graph with `n` nodes.
3407
+
3408
+ A Wheel graph is a basic structure where one node is connected to all other
3409
+ nodes and those (outer) nodes are connected cyclically.
3410
+
3411
+ PLOTTING: Upon construction, the position dictionary is filled to override
3412
+ the spring-layout algorithm. By convention, each wheel graph will be
3413
+ displayed with the first (0) node in the center, the second node at the top,
3414
+ and the rest following in a counterclockwise manner.
3415
+
3416
+ With the wheel graph, we see that it doesn't take a very large `n` at all
3417
+ for the spring-layout to give a counter-intuitive display. (See Graphics
3418
+ Array examples below).
3419
+
3420
+ EXAMPLES:
3421
+
3422
+ We view many wheel graphs with a Sage Graphics Array, first with this
3423
+ constructor (i.e., the position dictionary filled)::
3424
+
3425
+ sage: # needs sage.plot
3426
+ sage: g = []
3427
+ sage: j = []
3428
+ sage: for i in range(9):
3429
+ ....: k = graphs.WheelGraph(i+3)
3430
+ ....: g.append(k)
3431
+ ...
3432
+ sage: for i in range(3):
3433
+ ....: n = []
3434
+ ....: for m in range(3):
3435
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
3436
+ ....: j.append(n)
3437
+ ...
3438
+ sage: G = graphics_array(j)
3439
+ sage: G.show() # long time
3440
+
3441
+ Next, using the spring-layout algorithm::
3442
+
3443
+ sage: # needs networkx sage.plot
3444
+ sage: import networkx
3445
+ sage: g = []
3446
+ sage: j = []
3447
+ sage: for i in range(9):
3448
+ ....: spr = networkx.wheel_graph(i+3)
3449
+ ....: k = Graph(spr)
3450
+ ....: g.append(k)
3451
+ ...
3452
+ sage: for i in range(3):
3453
+ ....: n = []
3454
+ ....: for m in range(3):
3455
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
3456
+ ....: j.append(n)
3457
+ ...
3458
+ sage: G = graphics_array(j)
3459
+ sage: G.show() # long time
3460
+
3461
+ Compare the plotting::
3462
+
3463
+ sage: # needs networkx sage.plot
3464
+ sage: n = networkx.wheel_graph(23)
3465
+ sage: spring23 = Graph(n)
3466
+ sage: posdict23 = graphs.WheelGraph(23)
3467
+ sage: spring23.show() # long time
3468
+ sage: posdict23.show() # long time
3469
+ """
3470
+ from sage.graphs.generators.basic import CycleGraph
3471
+ if n < 4:
3472
+ G = CycleGraph(n)
3473
+ else:
3474
+ G = CycleGraph(n-1)
3475
+ G.relabel(perm=list(range(1, n)), inplace=True)
3476
+ G.add_edges([(0, i) for i in range(1, n)])
3477
+ G._pos[0] = (0, 0)
3478
+ G.name("Wheel graph")
3479
+ return G
3480
+
3481
+
3482
+ def WindmillGraph(k, n):
3483
+ r"""
3484
+ Return the Windmill graph `Wd(k, n)`.
3485
+
3486
+ The windmill graph `Wd(k, n)` is an undirected graph constructed for `k \geq
3487
+ 2` and `n \geq 2` by joining `n` copies of the complete graph `K_k` at a
3488
+ shared vertex. It has `(k-1)n+1` vertices and `nk(k-1)/2` edges, girth 3 (if
3489
+ `k > 2`), radius 1 and diameter 2. It has vertex connectivity 1 because its
3490
+ central vertex is an articulation point; however, like the complete graphs
3491
+ from which it is formed, it is `(k-1)`-edge-connected. It is trivially
3492
+ perfect and a block graph.
3493
+
3494
+ .. SEEALSO::
3495
+
3496
+ - :wikipedia:`Windmill_graph`
3497
+ - :meth:`GraphGenerators.StarGraph`
3498
+ - :meth:`GraphGenerators.FriendshipGraph`
3499
+
3500
+ EXAMPLES:
3501
+
3502
+ The Windmill graph `Wd(2, n)` is a star graph::
3503
+
3504
+ sage: n = 5
3505
+ sage: W = graphs.WindmillGraph(2, n)
3506
+ sage: W.is_isomorphic( graphs.StarGraph(n) )
3507
+ True
3508
+
3509
+ The Windmill graph `Wd(3, n)` is the Friendship graph `F_n`::
3510
+
3511
+ sage: n = 5
3512
+ sage: W = graphs.WindmillGraph(3, n)
3513
+ sage: W.is_isomorphic( graphs.FriendshipGraph(n) )
3514
+ True
3515
+
3516
+ The Windmill graph `Wd(3, 2)` is the Butterfly graph::
3517
+
3518
+ sage: W = graphs.WindmillGraph(3, 2)
3519
+ sage: W.is_isomorphic( graphs.ButterflyGraph() )
3520
+ True
3521
+
3522
+ The Windmill graph `Wd(k, n)` has chromatic number `k`::
3523
+
3524
+ sage: n,k = 5,6
3525
+ sage: W = graphs.WindmillGraph(k, n)
3526
+ sage: W.chromatic_number() == k # needs cliquer
3527
+ True
3528
+
3529
+ TESTS:
3530
+
3531
+ Giving too small parameters::
3532
+
3533
+ sage: graphs.WindmillGraph(1, 2)
3534
+ Traceback (most recent call last):
3535
+ ...
3536
+ ValueError: parameters k and n must be >= 2
3537
+ sage: graphs.WindmillGraph(2, 1)
3538
+ Traceback (most recent call last):
3539
+ ...
3540
+ ValueError: parameters k and n must be >= 2
3541
+ """
3542
+ if k < 2 or n < 2:
3543
+ raise ValueError('parameters k and n must be >= 2')
3544
+
3545
+ if k == 2:
3546
+ from sage.graphs.generators.basic import StarGraph
3547
+ G = StarGraph(n)
3548
+ else:
3549
+ sector = 2*pi/n
3550
+ slide = 1/sin(sector/4)
3551
+
3552
+ pos_dict = {}
3553
+ for i in range(0, k):
3554
+ x = float(cos(i*pi/(k-2)))
3555
+ y = float(sin(i*pi/(k-2))) + slide
3556
+ pos_dict[i] = (x, y)
3557
+
3558
+ G = Graph()
3559
+ pos = {0: [0, 0]}
3560
+ for i in range(n):
3561
+ V = list(range(i*(k - 1) + 1, (i + 1)*(k - 1) + 1))
3562
+ G.add_clique([0]+V)
3563
+ for j, v in enumerate(V):
3564
+ x, y = pos_dict[j]
3565
+ xv = x*cos(i*sector) - y*sin(i*sector)
3566
+ yv = x*sin(i*sector) + y*cos(i*sector)
3567
+ pos[v] = [xv, yv]
3568
+
3569
+ G.set_pos(pos)
3570
+
3571
+ G.name("Windmill graph Wd({}, {})".format(k, n))
3572
+ return G
3573
+
3574
+
3575
+ def trees(vertices):
3576
+ r"""
3577
+ Return a generator of the distinct trees on a fixed number of vertices.
3578
+
3579
+ INPUT:
3580
+
3581
+ - ``vertices`` -- the size of the trees created
3582
+
3583
+ OUTPUT:
3584
+
3585
+ A generator which creates an exhaustive, duplicate-free listing
3586
+ of the connected free (unlabeled) trees with ``vertices`` number
3587
+ of vertices. A tree is a graph with no cycles.
3588
+
3589
+ ALGORITHM:
3590
+
3591
+ Uses an algorithm that generates each new tree
3592
+ in constant time. See the documentation for, and implementation
3593
+ of, the :mod:`sage.graphs.trees` module, including a citation.
3594
+
3595
+ EXAMPLES:
3596
+
3597
+ We create an iterator, then loop over its elements. ::
3598
+
3599
+ sage: tree_iterator = graphs.trees(7)
3600
+ sage: for T in tree_iterator:
3601
+ ....: print(T.degree_sequence())
3602
+ [2, 2, 2, 2, 2, 1, 1]
3603
+ [3, 2, 2, 2, 1, 1, 1]
3604
+ [3, 2, 2, 2, 1, 1, 1]
3605
+ [4, 2, 2, 1, 1, 1, 1]
3606
+ [3, 3, 2, 1, 1, 1, 1]
3607
+ [3, 3, 2, 1, 1, 1, 1]
3608
+ [4, 3, 1, 1, 1, 1, 1]
3609
+ [3, 2, 2, 2, 1, 1, 1]
3610
+ [4, 2, 2, 1, 1, 1, 1]
3611
+ [5, 2, 1, 1, 1, 1, 1]
3612
+ [6, 1, 1, 1, 1, 1, 1]
3613
+
3614
+ The number of trees on the first few vertex counts.
3615
+ This is sequence A000055 in Sloane's OEIS. ::
3616
+
3617
+ sage: [len(list(graphs.trees(i))) for i in range(0, 15)]
3618
+ [1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
3619
+ """
3620
+ from sage.graphs.trees import TreeIterator
3621
+ return iter(TreeIterator(vertices))
3622
+
3623
+
3624
+ def nauty_gentreeg(options='', debug=False):
3625
+ r"""
3626
+ Return a generator which creates non-isomorphic trees from nauty's gentreeg
3627
+ program.
3628
+
3629
+ INPUT:
3630
+
3631
+ - ``options`` -- string (default: ``""``); a string passed to ``gentreeg``
3632
+ as if it was run at a system command line. At a minimum, you *must* pass
3633
+ the number of vertices you desire. Sage expects the graphs to be in
3634
+ nauty's "sparse6" format, do not set an option to change this default or
3635
+ results will be unpredictable.
3636
+
3637
+ - ``debug`` -- boolean (default: ``False``); if ``True`` the first line of
3638
+ ``gentreeg``'s output to standard error is captured and the first call to
3639
+ the generator's ``next()`` function will return this line as a string. A
3640
+ line leading with ">A" indicates a successful initiation of the program
3641
+ with some information on the arguments, while a line beginning with ">E"
3642
+ indicates an error with the input.
3643
+
3644
+ The possible options, obtained as output of ``gentreeg -help``::
3645
+
3646
+ n : the number of vertices. Must be in range 1..128
3647
+ res/mod : only generate subset res out of subsets 0..mod-1
3648
+ -D<int> : an upper bound for the maximum degree
3649
+ -Z<int>:<int> : bounds on the diameter
3650
+ -q : suppress auxiliary output
3651
+
3652
+ Options which cause ``gentreeg`` to use an output format different than the
3653
+ sparse6 format are not listed above (-p, -l, -u) as they will confuse the
3654
+ creation of a Sage graph. The res/mod option can be useful when using the
3655
+ output in a routine run several times in parallel.
3656
+
3657
+ OUTPUT:
3658
+
3659
+ A generator which will produce the graphs as Sage graphs. These will be
3660
+ simple graphs: no loops, no multiple edges, no directed edges.
3661
+
3662
+ .. SEEALSO::
3663
+
3664
+ :meth:`trees` -- another generator of trees
3665
+
3666
+ EXAMPLES:
3667
+
3668
+ The generator can be used to construct trees for testing, one at a time
3669
+ (usually inside a loop). Or it can be used to create an entire list all at
3670
+ once if there is sufficient memory to contain it::
3671
+
3672
+ sage: # needs nauty
3673
+ sage: gen = graphs.nauty_gentreeg("4")
3674
+ sage: next(gen)
3675
+ Graph on 4 vertices
3676
+ sage: next(gen)
3677
+ Graph on 4 vertices
3678
+ sage: next(gen)
3679
+ Traceback (most recent call last):
3680
+ ...
3681
+ StopIteration
3682
+
3683
+ The number of trees on the first few vertex counts. This agrees with
3684
+ :oeis:`A000055`::
3685
+
3686
+ sage: [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(1, 15)] # needs nauty
3687
+ [1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
3688
+
3689
+ The ``debug`` switch can be used to examine ``gentreeg``'s reaction to the
3690
+ input in the ``options`` string. We illustrate success. (A failure will be
3691
+ a string beginning with ">E".) Passing the "-q" switch to ``gentreeg`` will
3692
+ suppress the indicator of a successful initiation, and so the first returned
3693
+ value might be an empty string if ``debug`` is ``True``::
3694
+
3695
+ sage: # needs nauty
3696
+ sage: gen = graphs.nauty_gentreeg("4", debug=True)
3697
+ sage: print(next(gen))
3698
+ >A ...gentreeg ...
3699
+ sage: gen = graphs.nauty_gentreeg("4 -q", debug=True)
3700
+ sage: next(gen)
3701
+ ''
3702
+
3703
+ TESTS:
3704
+
3705
+ The number `n` of vertices must be in range 1..128::
3706
+
3707
+ sage: # needs nauty
3708
+ sage: list(graphs.nauty_gentreeg("0", debug=False))
3709
+ Traceback (most recent call last):
3710
+ ...
3711
+ ValueError: wrong format of parameter options
3712
+ sage: list(graphs.nauty_gentreeg("0", debug=True))
3713
+ ['>E gentreeg: n must be in the range 1..128\n']
3714
+ sage: list(graphs.nauty_gentreeg("200", debug=True))
3715
+ ['>E gentreeg: n must be in the range 1..128\n']
3716
+
3717
+ Wrong input::
3718
+
3719
+ sage: # needs nauty
3720
+ sage: list(graphs.nauty_gentreeg("3 -x", debug=False))
3721
+ Traceback (most recent call last):
3722
+ ...
3723
+ ValueError: wrong format of parameter options
3724
+ sage: list(graphs.nauty_gentreeg("3 -x", debug=True))
3725
+ ['>E Usage: ...gentreeg [-D#] [-Z#:#] [-ulps] [-q] n... [res/mod] ...
3726
+ sage: list(graphs.nauty_gentreeg("3", debug=True))
3727
+ ['>A ...gentreeg ...\n', Graph on 3 vertices]
3728
+ """
3729
+ import shlex
3730
+ from sage.features.nauty import NautyExecutable
3731
+ gen_path = NautyExecutable("gentreeg").absolute_filename()
3732
+ sp = subprocess.Popen(shlex.quote(gen_path) + " {0}".format(options), shell=True,
3733
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3734
+ stderr=subprocess.PIPE, close_fds=True,
3735
+ encoding='latin-1')
3736
+ msg = sp.stderr.readline()
3737
+ if debug:
3738
+ yield msg
3739
+ elif msg.startswith('>E'):
3740
+ raise ValueError('wrong format of parameter options')
3741
+ gen = sp.stdout
3742
+ while True:
3743
+ try:
3744
+ s = next(gen)
3745
+ except StopIteration:
3746
+ # Exhausted list of graphs from nauty geng
3747
+ return
3748
+ G = Graph(s[:-1], format='sparse6', loops=False, multiedges=False)
3749
+ yield G
3750
+
3751
+
3752
+ def RingedTree(k, vertex_labels=True):
3753
+ r"""
3754
+ Return the ringed tree on k-levels.
3755
+
3756
+ A ringed tree of level `k` is a binary tree with `k` levels (counting
3757
+ the root as a level), in which all vertices at the same level are connected
3758
+ by a ring.
3759
+
3760
+ More precisely, in each layer of the binary tree (i.e. a layer is the set of
3761
+ vertices `[2^i...2^{i+1}-1]`) two vertices `u,v` are adjacent if `u=v+1` or
3762
+ if `u=2^i` and `v=2^{i+1}-1`.
3763
+
3764
+ Ringed trees are defined in [CFHM2013]_.
3765
+
3766
+ INPUT:
3767
+
3768
+ - ``k`` -- the number of levels of the ringed tree
3769
+
3770
+ - ``vertex_labels`` -- boolean; whether to label vertices as binary words
3771
+ (default) or as integers
3772
+
3773
+ EXAMPLES::
3774
+
3775
+ sage: # needs networkx
3776
+ sage: G = graphs.RingedTree(5)
3777
+ sage: P = G.plot(vertex_labels=False, vertex_size=10) # needs sage.plot
3778
+ sage: P.show() # long time # needs sage.plot
3779
+ sage: G.vertices(sort=True)
3780
+ ['', '0', '00', '000', '0000', '0001', '001', '0010', '0011', '01',
3781
+ '010', '0100', '0101', '011', '0110', '0111', '1', '10', '100',
3782
+ '1000', '1001', '101', '1010', '1011', '11', '110', '1100', '1101',
3783
+ '111', '1110', '1111']
3784
+
3785
+ TESTS::
3786
+
3787
+ sage: G = graphs.RingedTree(-1)
3788
+ Traceback (most recent call last):
3789
+ ...
3790
+ ValueError: The number of levels must be >= 1.
3791
+ sage: G = graphs.RingedTree(5, vertex_labels=False) # needs networkx
3792
+ sage: G.vertices(sort=True) # needs networkx
3793
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
3794
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
3795
+ """
3796
+ if k < 1:
3797
+ raise ValueError('The number of levels must be >= 1.')
3798
+
3799
+ # Creating the Balanced tree, which contains most edges already
3800
+ g = BalancedTree(2, k - 1)
3801
+ g.name('Ringed Tree on ' + str(k) + ' levels')
3802
+
3803
+ # We consider edges layer by layer
3804
+ for i in range(1, k):
3805
+ vertices = list(range(2**(i) - 1, 2**(i + 1) - 1))
3806
+
3807
+ # Add the missing edges
3808
+ g.add_cycle(vertices)
3809
+
3810
+ # And set the vertices' positions
3811
+ radius = i if i <= 1 else 1.5**i
3812
+ shift = -2**(i - 2) + .5 if i > 1 else 0
3813
+ g._circle_embedding(vertices, radius=radius, shift=shift)
3814
+
3815
+ # Specific position for the central vertex
3816
+ g._pos[0] = (0, 0.2)
3817
+
3818
+ # Relabel vertices as binary words
3819
+ if not vertex_labels:
3820
+ return g
3821
+
3822
+ vertices = ['']
3823
+ for i in range(k - 1):
3824
+ for j in range(2**(i) - 1, 2**(i + 1) - 1):
3825
+ v = vertices[j]
3826
+ vertices.append(v + '0')
3827
+ vertices.append(v + '1')
3828
+
3829
+ g.relabel(vertices)
3830
+
3831
+ return g
3832
+
3833
+
3834
+ def MathonPseudocyclicMergingGraph(M, t):
3835
+ r"""
3836
+ Mathon's merging of classes in a pseudo-cyclic 3-class association scheme.
3837
+
3838
+ Construct strongly regular graphs from p.97 of [BL1984]_.
3839
+
3840
+ INPUT:
3841
+
3842
+ - ``M`` -- the list of matrices in a pseudo-cyclic 3-class association scheme;
3843
+ the identity matrix must be the first entry
3844
+
3845
+ - ``t`` -- integer; the number of the graph, from 0 to 2
3846
+
3847
+ .. SEEALSO::
3848
+
3849
+ - :func:`~sage.graphs.strongly_regular_db.is_muzychuk_S6`
3850
+
3851
+ TESTS::
3852
+
3853
+ sage: from sage.graphs.generators.families import MathonPseudocyclicMergingGraph as mer
3854
+ sage: from sage.graphs.generators.smallgraphs import _EllipticLinesProjectivePlaneScheme as ES
3855
+
3856
+ sage: # long time, needs sage.libs.gap
3857
+ sage: G = mer(ES(3), 0)
3858
+ sage: G.is_strongly_regular(parameters=True)
3859
+ (784, 243, 82, 72)
3860
+ sage: G = mer(ES(3), 1)
3861
+ sage: G.is_strongly_regular(parameters=True)
3862
+ (784, 270, 98, 90)
3863
+ sage: G = mer(ES(3), 2)
3864
+ sage: G.is_strongly_regular(parameters=True)
3865
+ (784, 297, 116, 110)
3866
+ sage: G = mer(ES(2), 2)
3867
+ Traceback (most recent call last):
3868
+ ...
3869
+ AssertionError...
3870
+
3871
+ sage: # needs sage.libs.gap
3872
+ sage: M = ES(3)
3873
+ sage: M = [M[1],M[0],M[2],M[3]]
3874
+ sage: G = mer(M, 2)
3875
+ Traceback (most recent call last):
3876
+ ...
3877
+ AssertionError...
3878
+ """
3879
+ from sage.graphs.graph import Graph
3880
+ from sage.matrix.constructor import identity_matrix
3881
+ assert len(M) == 4
3882
+ assert M[0] == identity_matrix(M[0].nrows())
3883
+ A = sum(x.tensor_product(x) for x in M[1:])
3884
+ if t > 0:
3885
+ A += sum(x.tensor_product(M[0]) for x in M[1:])
3886
+ if t > 1:
3887
+ A += sum(M[0].tensor_product(x) for x in M[1:])
3888
+ return Graph(A)
3889
+
3890
+
3891
+ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None):
3892
+ r"""
3893
+ Return a strongly regular graph on `(4t+1)(4t-1)^2` vertices from
3894
+ [Mat1978]_.
3895
+
3896
+ Let `4t-1` be a prime power, and `4t+1` be such that there exists
3897
+ a strongly regular graph `G` with parameters `(4t+1,2t,t-1,t)`. In
3898
+ particular, `4t+1` must be a sum of two squares [Mat1978]_. With
3899
+ this input, Mathon [Mat1978]_ gives a construction of a strongly regular
3900
+ graph with parameters `(4 \mu + 1, 2 \mu, \mu-1, \mu)`, where
3901
+ `\mu = t(4t(4t-1)-1)`. The construction is optionally parametrised by an
3902
+ a skew-symmetric Latin square of order `4t+1`, with entries in
3903
+ `-2t,...,-1,0,1,...,2t`.
3904
+
3905
+ Our implementation follows a description given in [ST1981]_.
3906
+
3907
+ INPUT:
3908
+
3909
+ - ``t`` -- positive integer
3910
+
3911
+ - ``G`` -- if ``None`` (default), try to construct the necessary graph
3912
+ with parameters `(4t+1,2t,t-1,t)`, otherwise use the user-supplied one,
3913
+ with vertices labelled from `0` to `4t`.
3914
+
3915
+ - ``L`` -- if ``None`` (default), construct a necessary skew Latin square,
3916
+ otherwise use the user-supplied one. Here non-isomorphic Latin squares
3917
+ -- one constructed from `Z/9Z`, and the other from `(Z/3Z)^2` --
3918
+ lead to non-isomorphic graphs.
3919
+
3920
+ .. SEEALSO::
3921
+
3922
+ - :func:`~sage.graphs.strongly_regular_db.is_mathon_PC_srg`
3923
+
3924
+ EXAMPLES:
3925
+
3926
+ Using default ``G`` and ``L``. ::
3927
+
3928
+ sage: from sage.graphs.generators.families import MathonPseudocyclicStronglyRegularGraph
3929
+ sage: G = MathonPseudocyclicStronglyRegularGraph(1); G # needs database_graphs sage.modules sage.rings.finite_rings
3930
+ Mathon's PC SRG on 45 vertices: Graph on 45 vertices
3931
+ sage: G.is_strongly_regular(parameters=True) # needs database_graphs sage.modules sage.rings.finite_rings
3932
+ (45, 22, 10, 11)
3933
+
3934
+ Supplying ``G`` and ``L`` (constructed from the automorphism group
3935
+ of ``G``). The entries of L can't be tested directly because
3936
+ there's some unpredictability in the way that GAP chooses a
3937
+ representative in ``NormalSubgroups()``, the function that
3938
+ underlies our own
3939
+ :meth:`~sage.groups.perm_gps.permgroup.PermutationGroup_generic.normal_subgroups`
3940
+ method::
3941
+
3942
+ sage: # needs sage.groups sage.libs.gap sage.rings.finite_rings
3943
+ sage: G = graphs.PaleyGraph(9)
3944
+ sage: a = G.automorphism_group(partition=[sorted(G)])
3945
+ sage: it = (x for x in a.normal_subgroups() if x.order() == 9)
3946
+ sage: subg = next(iter(it))
3947
+ sage: r = [matrix(libgap.PermutationMat(libgap(z), 9).sage())
3948
+ ....: for z in subg]
3949
+ sage: ff = list(map(lambda y: (y[0]-1,y[1]-1),
3950
+ ....: Permutation(map(lambda x: 1+r.index(x^-1), r)).cycle_tuples()[1:]))
3951
+ sage: L = sum(i*(r[a]-r[b]) for i,(a,b) in zip(range(1,len(ff)+1), ff))
3952
+ sage: G.relabel(range(9))
3953
+ sage: G3x3 = graphs.MathonPseudocyclicStronglyRegularGraph(2, G=G, L=L)
3954
+ sage: G3x3.is_strongly_regular(parameters=True)
3955
+ (441, 220, 109, 110)
3956
+ sage: G3x3.automorphism_group(algorithm='bliss').order() # optional - bliss
3957
+ 27
3958
+ sage: G9 = graphs.MathonPseudocyclicStronglyRegularGraph(2)
3959
+ sage: G9.is_strongly_regular(parameters=True)
3960
+ (441, 220, 109, 110)
3961
+ sage: G9.automorphism_group(algorithm='bliss').order() # optional - bliss
3962
+ 9
3963
+
3964
+ TESTS::
3965
+
3966
+ sage: graphs.MathonPseudocyclicStronglyRegularGraph(5) # needs sage.modules
3967
+ Traceback (most recent call last):
3968
+ ...
3969
+ ValueError: 21 must be a sum of two squares!...
3970
+ """
3971
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF
3972
+ from sage.rings.integer_ring import ZZ
3973
+ from sage.matrix.constructor import matrix, block_matrix, \
3974
+ ones_matrix, identity_matrix
3975
+ from sage.arith.misc import two_squares
3976
+ p = 4*t + 1
3977
+ try:
3978
+ x = two_squares(p)
3979
+ except ValueError:
3980
+ raise ValueError(str(p)+" must be a sum of two squares!")
3981
+ if G is None:
3982
+ from sage.graphs.strongly_regular_db import strongly_regular_graph as SRG
3983
+ G = SRG(p, 2*t, t - 1)
3984
+ G.relabel(range(p))
3985
+ if L is None:
3986
+ from sage.matrix.constructor import circulant
3987
+ L = circulant(list(range(2 * t + 1)) + list(range(-2 * t, 0)))
3988
+ q = 4*t - 1
3989
+ K = GF(q, prefix='x')
3990
+ K_pairs = set(frozenset([x, -x]) for x in K)
3991
+ K_pairs.discard(frozenset([0]))
3992
+ a = [None]*(q-1) # order the non-0 elements of K as required
3993
+ for i, (x, y) in enumerate(K_pairs):
3994
+ a[i] = x
3995
+ a[-i-1] = y
3996
+ a.append(K(0)) # and append the 0 of K at the end
3997
+ P = [matrix(ZZ, q, q, lambda i, j: 1 if a[j] == a[i] + b else 0)
3998
+ for b in a]
3999
+ g = K.primitive_element()
4000
+ F = sum(P[a.index(g**(2*i))] for i in range(1, 2*t))
4001
+ E = matrix(ZZ, q, q, lambda i, j: 0 if (a[j] - a[0]).is_square() else 1)
4002
+
4003
+ def B(m):
4004
+ I = identity_matrix(q)
4005
+ J = ones_matrix(q)
4006
+ if m == 0:
4007
+ def f(i, j):
4008
+ if i == j:
4009
+ return 0 * I
4010
+ elif (a[j] - a[i]).is_square():
4011
+ return I + F
4012
+ else:
4013
+ return J - F
4014
+ elif m < 2*t:
4015
+ def f(i, j):
4016
+ return F * P[a.index(g**(2*m) * (a[i] + a[j]))]
4017
+ elif m == 2*t:
4018
+ def f(i, j):
4019
+ return E * P[i]
4020
+ return block_matrix(q, q, [f(i, j) for i in range(q) for j in range(q)])
4021
+
4022
+ def Acon(i, j):
4023
+ J = ones_matrix(q**2)
4024
+ if i == j:
4025
+ return B(0)
4026
+ if L[i, j] > 0:
4027
+ if G.has_edge(i, j):
4028
+ return B(L[i, j])
4029
+ return J - B(L[i, j])
4030
+ if G.has_edge(i, j):
4031
+ return B(-L[i, j]).T
4032
+ return J - B(-L[i, j]).T
4033
+
4034
+ A = Graph(block_matrix(p, p, [Acon(i, j) for i in range(p) for j in range(p)]))
4035
+ A.name("Mathon's PC SRG on " + str(p*q**2) + " vertices")
4036
+ A.relabel()
4037
+ return A
4038
+
4039
+
4040
+ def TuranGraph(n, r):
4041
+ r"""
4042
+ Return the Turan graph with parameters `n, r`.
4043
+
4044
+ Turan graphs are complete multipartite graphs with `n` vertices and `r`
4045
+ subsets, denoted `T(n,r)`, with the property that the sizes of the subsets
4046
+ are as close to equal as possible. The graph `T(n,r)` will have `n \pmod r`
4047
+ subsets of size `\lfloor n/r \rfloor` and `r - (n \pmod r)` subsets of size
4048
+ `\lceil n/r \rceil`. See the :wikipedia:`Turan_graph` for more information.
4049
+
4050
+ INPUT:
4051
+
4052
+ - ``n`` -- integer; the number of vertices in the graph
4053
+
4054
+ - ``r`` -- integer; the number of partitions of the graph
4055
+
4056
+ EXAMPLES:
4057
+
4058
+ The Turan graph is a complete multipartite graph::
4059
+
4060
+ sage: g = graphs.TuranGraph(13, 4)
4061
+ sage: k = graphs.CompleteMultipartiteGraph([3,3,3,4])
4062
+ sage: g.is_isomorphic(k)
4063
+ True
4064
+
4065
+ The Turan graph `T(n,r)` has `\frac{(r-1)(n^2-s^2)}{2r} + \frac{s(s-1)}{2}`
4066
+ edges, where `s = n \mod r` (:issue:`34249`)::
4067
+
4068
+ sage: n = 12
4069
+ sage: r = 8
4070
+ sage: g = graphs.TuranGraph(n, r)
4071
+ sage: def count(n, r):
4072
+ ....: s = n % r
4073
+ ....: return (r - 1) * (n**2 - s**2) / (2*r) + s*(s - 1)/2
4074
+ sage: g.size() == count(n, r)
4075
+ True
4076
+ sage: n = randint(3, 100)
4077
+ sage: r = randint(2, n - 1)
4078
+ sage: g = graphs.TuranGraph(n, r)
4079
+ sage: g.size() == count(n, r)
4080
+ True
4081
+
4082
+ TESTS::
4083
+
4084
+ sage: g = graphs.TuranGraph(3,6)
4085
+ Traceback (most recent call last):
4086
+ ...
4087
+ ValueError: input parameters must satisfy "1 < r < n"
4088
+ """
4089
+ if n < 1 or n < r or r < 1:
4090
+ raise ValueError('input parameters must satisfy "1 < r < n"')
4091
+
4092
+ from sage.graphs.generators.basic import CompleteMultipartiteGraph
4093
+
4094
+ p = n // r
4095
+ s = n % r
4096
+ vertex_sets = [p]*(r - s) + [p + 1]*s
4097
+
4098
+ g = CompleteMultipartiteGraph(vertex_sets)
4099
+ g.name('Turan Graph with n: {}, r: {}'.format(n, r))
4100
+
4101
+ return g
4102
+
4103
+
4104
+ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False):
4105
+ r"""
4106
+ Return a strongly regular graph of S6 type from [Muz2007]_ on
4107
+ `n^d((n^d-1)/(n-1)+1)` vertices.
4108
+
4109
+ The construction depends upon a number of parameters, two of them, `n` and
4110
+ `d`, mandatory, and `\Phi` and `\Sigma` mappings defined in [Muz2007]_.
4111
+ These graphs have parameters `(mn^d, n^{d-1}(m-1) - 1,\mu - 2,\mu)`, where
4112
+ `\mu=\frac{n^{d-1}-1}{n-1}n^{d-1}` and `m:=\frac{n^d-1}{n-1}+1`.
4113
+
4114
+ Some details on `\Phi` and `\Sigma` are as follows. Let `L` be the
4115
+ complete graph on `M:=\{0,..., m-1\}` with the matching
4116
+ `\{(2i,2i+1) | i=0,...,m/2\}` removed.
4117
+ Then one arbitrarily chooses injections `\Phi_i`
4118
+ from the edges of `L` on `i \in M` into sets of parallel classes of affine
4119
+ `d`-dimensional designs; our implementation uses the designs of hyperplanes
4120
+ in `d`-dimensional affine geometries over `GF(n)`. Finally, for each edge
4121
+ `ij` of `L` one arbitrarily chooses bijections `\Sigma_{ij}` between
4122
+ `\Phi_i` and `\Phi_j`. More details, in particular how these choices lead
4123
+ to non-isomorphic graphs, are in [Muz2007]_.
4124
+
4125
+ INPUT:
4126
+
4127
+ - ``n`` -- integer; a prime power
4128
+
4129
+ - ``d`` -- integer; must be odd if `n` is odd
4130
+
4131
+ - ``Phi`` is an optional parameter of the construction; it must be either
4132
+
4133
+ - ``'fixed'`` -- this will generate fixed default `\Phi_i`, for `i \in M`, or
4134
+
4135
+ - ``'random'`` -- `\Phi_i` are generated at random, or
4136
+
4137
+ - A dictionary describing the functions `\Phi_i`; for `i \in M`,
4138
+ Phi[(i, T)] in `M`, for each edge T of `L` on `i`.
4139
+ Also, each `\Phi_i` must be injective.
4140
+
4141
+ - ``Sigma`` is an optional parameter of the construction; it must be either
4142
+
4143
+ - ``'fixed'`` -- this will generate a fixed default `\Sigma`, or
4144
+
4145
+ - ``'random'`` -- `\Sigma` is generated at random
4146
+
4147
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, print progress
4148
+ information
4149
+
4150
+ .. SEEALSO::
4151
+
4152
+ - :func:`~sage.graphs.strongly_regular_db.is_muzychuk_S6`
4153
+
4154
+ .. TODO::
4155
+
4156
+ Implement the possibility to explicitly supply the parameter `\Sigma`
4157
+ of the construction.
4158
+
4159
+ EXAMPLES::
4160
+
4161
+ sage: # needs sage.combinat sage.modules sage.rings.finite_rings
4162
+ sage: graphs.MuzychukS6Graph(3, 3).is_strongly_regular(parameters=True)
4163
+ (378, 116, 34, 36)
4164
+ sage: phi = {(2,(0,2)):0, (1,(1,3)):1, (0,(0,3)):1, (2,(1,2)):1,
4165
+ ....: (1,(1,2)):0, (0,(0,2)):0, (3,(0,3)):0, (3,(1,3)):1}
4166
+ sage: graphs.MuzychukS6Graph(2, 2, Phi=phi).is_strongly_regular(parameters=True)
4167
+ (16, 5, 0, 2)
4168
+
4169
+ TESTS::
4170
+
4171
+ sage: # needs sage.modules
4172
+ sage: graphs.MuzychukS6Graph(2,2,Phi='random',Sigma='random').is_strongly_regular(parameters=True) # needs sage.rings.finite_rings
4173
+ (16, 5, 0, 2)
4174
+ sage: graphs.MuzychukS6Graph(3,3,Phi='random',Sigma='random').is_strongly_regular(parameters=True) # needs sage.rings.finite_rings
4175
+ (378, 116, 34, 36)
4176
+ sage: graphs.MuzychukS6Graph(3,2)
4177
+ Traceback (most recent call last):
4178
+ ...
4179
+ AssertionError: n must be even or d must be odd
4180
+ sage: graphs.MuzychukS6Graph(6,2)
4181
+ Traceback (most recent call last):
4182
+ ...
4183
+ AssertionError: n must be a prime power
4184
+ sage: graphs.MuzychukS6Graph(3,1)
4185
+ Traceback (most recent call last):
4186
+ ...
4187
+ AssertionError: d must be at least 2
4188
+ sage: graphs.MuzychukS6Graph(3,3,Phi=42) # needs sage.rings.finite_rings
4189
+ Traceback (most recent call last):
4190
+ ...
4191
+ AssertionError: Phi must be a dictionary or 'random' or 'fixed'
4192
+ sage: graphs.MuzychukS6Graph(3,3,Sigma=42) # needs sage.rings.finite_rings
4193
+ Traceback (most recent call last):
4194
+ ...
4195
+ ValueError: Sigma must be 'random' or 'fixed'
4196
+ """
4197
+ # TO DO: optimise
4198
+ # add option to return phi, sigma? generate phi, sigma from seed? (int say?)
4199
+
4200
+ from sage.combinat.designs.block_design import ProjectiveGeometryDesign
4201
+ from sage.misc.prandom import randrange
4202
+ from sage.misc.functional import is_even
4203
+ from sage.arith.misc import is_prime_power
4204
+ from sage.graphs.generators.basic import CompleteGraph
4205
+ from sage.rings.finite_rings.finite_field_constructor import GF
4206
+ from sage.matrix.special import ones_matrix
4207
+ from sage.matrix.constructor import matrix
4208
+ from sage.rings.rational_field import QQ
4209
+ from sage.rings.integer_ring import ZZ
4210
+ from time import time
4211
+
4212
+ assert d > 1, 'd must be at least 2'
4213
+ assert is_even(n * (d-1)), 'n must be even or d must be odd'
4214
+ assert is_prime_power(n), 'n must be a prime power'
4215
+ t = time()
4216
+
4217
+ # build L, L_i and the design
4218
+ m = int((n**d - 1)/(n - 1) + 1) # from m = p + 1, p = (n^d-1) / (n-1)
4219
+ L = CompleteGraph(m)
4220
+ L.delete_edges([(2 * x, 2 * x + 1) for x in range(m // 2)])
4221
+ L_i = [L.edges_incident(x, labels=False) for x in range(m)]
4222
+ Design = ProjectiveGeometryDesign(d, d-1, GF(n, 'a'), point_coordinates=False)
4223
+ projBlocks = Design.blocks()
4224
+ atInf = projBlocks[-1]
4225
+ Blocks = [[x for x in block if x not in atInf] for block in projBlocks[:-1]]
4226
+ if verbose:
4227
+ print('finished preamble at %f (+%f)' % (time() - t, time() - t))
4228
+ t1 = time()
4229
+
4230
+ # sort the hyperplanes into parallel classes
4231
+ ParClasses = [Blocks]
4232
+ while ParClasses[0]:
4233
+ nextHyp = ParClasses[0].pop()
4234
+ for C in ParClasses[1:]:
4235
+ listC = sum(C, [])
4236
+ for x in nextHyp:
4237
+ if x in listC:
4238
+ break
4239
+ else:
4240
+ C.append(nextHyp)
4241
+ break
4242
+ else:
4243
+ ParClasses.append([nextHyp])
4244
+ del ParClasses[0]
4245
+ if verbose:
4246
+ print('finished ParClasses at %f (+%f)' % (time() - t, time() - t1))
4247
+ t1 = time()
4248
+
4249
+ # build E^C_j
4250
+ E = {}
4251
+ v = ZZ(n**d)
4252
+ k = ZZ(n**(d-1))
4253
+ ones = ones_matrix(v)
4254
+ ones_v = ones/v
4255
+ for C in ParClasses:
4256
+ EC = matrix(QQ, v)
4257
+ for line in C:
4258
+ for i, j in combinations(line, 2):
4259
+ EC[i, j] = EC[j, i] = 1/k
4260
+ EC -= ones_v
4261
+ E[tuple(C[0])] = EC
4262
+ if verbose:
4263
+ print('finished E at %f (+%f)' % (time() - t, time() - t1))
4264
+ t1 = time()
4265
+
4266
+ # handle Phi
4267
+ if Phi == 'random':
4268
+ Phi = {}
4269
+ for x in range(m):
4270
+ temp = list(range(len(ParClasses)))
4271
+ for line in L_i[x]:
4272
+ rand = randrange(0, len(temp))
4273
+ Phi[(x, line)] = temp.pop(rand)
4274
+ elif Phi == 'fixed':
4275
+ Phi = {(x, line): val for x in range(m)
4276
+ for val, line in enumerate(L_i[x])}
4277
+ else:
4278
+ assert isinstance(Phi, dict), \
4279
+ "Phi must be a dictionary or 'random' or 'fixed'"
4280
+ assert set(Phi.keys()) == {(x, line) for x in range(m)
4281
+ for line in L_i[x]}, \
4282
+ 'each Phi_i must have domain L_i'
4283
+ for x in range(m):
4284
+ assert m - 2 == len({val for key, val in Phi.items()
4285
+ if key[0] == x}), \
4286
+ 'each phi_i must be injective'
4287
+ for val in Phi.values():
4288
+ assert val in range(m - 1), \
4289
+ 'codomain should be {0,..., (n^d - 1)/(n - 1) - 1}'
4290
+ phi = {(x, line): ParClasses[Phi[(x, line)]] for x in range(m) for line in L_i[x]}
4291
+ if verbose:
4292
+ print('finished phi at %f (+%f)' % (time() - t, time() - t1))
4293
+ t1 = time()
4294
+
4295
+ # handle sigma
4296
+ sigma = {}
4297
+ if Sigma == 'random':
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
+ rand = randrange(0, len(temp))
4304
+ sigma[(i, j, tuple(hyp))] = temp[rand]
4305
+ sigma[(j, i, tuple(temp[rand]))] = hyp
4306
+ del temp[rand]
4307
+ elif Sigma == 'fixed':
4308
+ for x in range(m):
4309
+ for line in L_i[x]:
4310
+ [i, j] = line
4311
+ temp = phi[(j, line)][:]
4312
+ for hyp in phi[(i, line)]:
4313
+ val = temp.pop()
4314
+ sigma[(i, j, tuple(hyp))] = val
4315
+ sigma[(j, i, tuple(val))] = hyp
4316
+ else:
4317
+ raise ValueError("Sigma must be 'random' or 'fixed'")
4318
+ if verbose:
4319
+ print('finished sigma at %f (+%f)' % (time() - t, time() - t1))
4320
+ t1 = time()
4321
+
4322
+ # build V
4323
+ edges = [] # how many? *m^2*n^2
4324
+ for i, j in L.edges(sort=True, labels=False):
4325
+ for hyp in phi[(i, (i, j))]:
4326
+ for x in hyp:
4327
+ newEdges = [((i, x), (j, y))
4328
+ for y in sigma[(i, j, tuple(hyp))]]
4329
+ edges.extend(newEdges)
4330
+ if verbose:
4331
+ print('finished edges at %f (+%f)' % (time() - t, time() - t1))
4332
+ t1 = time()
4333
+ V = Graph(edges)
4334
+ if verbose:
4335
+ print('finished V at %f (+%f)' % (time() - t, time() - t1))
4336
+ t1 = time()
4337
+
4338
+ # build D_i, F_i and A_i
4339
+ D_i = [0]*m
4340
+ for x in range(m):
4341
+ D_i[x] = sum([E[tuple(phi[x, line][0])] for line in L_i[x]])
4342
+ F_i = [1 - D_i[x] - ones_v for x in range(m)]
4343
+ # as the sum of (1/v)*J_\Omega_i, D_i, F_i is identity
4344
+ A_i = [(v-k)*ones_v - k*F_i[x] for x in range(m)]
4345
+ # we know A_i = k''*(1/v)*J_\Omega_i + r''*D_i + s''*F_i,
4346
+ # and (k'', s'', r'') = (v - k, 0, -k)
4347
+ if verbose:
4348
+ print('finished D, F and A at %f (+%f)' % (time() - t, time() - t1))
4349
+ t1 = time()
4350
+
4351
+ # add the edges of the graph of B to V
4352
+ for i in range(m):
4353
+ V.add_edges([((i, x), (i, y)) for x in range(v)
4354
+ for y in range(v) if not A_i[i][(x, y)]])
4355
+
4356
+ V.name('Muzychuk S6 graph with parameters ('+str(n)+','+str(d)+')')
4357
+ if verbose:
4358
+ print('finished at %f (+%f)' % ((time() - t), time() - t1))
4359
+ return V
4360
+
4361
+
4362
+ def CubeConnectedCycle(d):
4363
+ r"""
4364
+ Return the cube-connected cycle of dimension `d`.
4365
+
4366
+ The cube-connected cycle of order `d` is the `d`-dimensional hypercube
4367
+ with each of its vertices replaced by a cycle of length `d`. This graph has
4368
+ order `d \times 2^d`.
4369
+ The construction is as follows:
4370
+ Construct vertex `(x,y)` for `0 \leq x < 2^d`, `0 \leq y < d`.
4371
+ For each vertex, `(x,y)`, add an edge between it and `(x, (y-1) \mod d))`,
4372
+ `(x,(y+1) \mod d)`, and `(x \oplus 2^y, y)`, where `\oplus` is the bitwise
4373
+ xor operator.
4374
+
4375
+ For `d=1` and `2`, the cube-connected cycle graph contains self-loops or
4376
+ multiple edges between a pair of vertices, but for all other `d`, it is
4377
+ simple.
4378
+
4379
+ INPUT:
4380
+
4381
+ - ``d`` -- positive integer; the dimension of the desired hypercube as well
4382
+ as the length of the cycle to be placed at each vertex of the
4383
+ `d`-dimensional hypercube
4384
+
4385
+ EXAMPLES:
4386
+
4387
+ The order of the graph is `d \times 2^d` ::
4388
+
4389
+ sage: d = 3
4390
+ sage: g = graphs.CubeConnectedCycle(d)
4391
+ sage: len(g) == d*2**d
4392
+ True
4393
+
4394
+ The diameter of cube-connected cycles for `d > 3` is
4395
+ `2d + \lfloor \frac{d}{2} \rfloor - 2` ::
4396
+
4397
+ sage: d = 4
4398
+ sage: g = graphs.CubeConnectedCycle(d)
4399
+ sage: g.diameter() == 2*d+d//2-2
4400
+ True
4401
+
4402
+ All vertices have degree `3` when `d > 1` ::
4403
+
4404
+ sage: g = graphs.CubeConnectedCycle(5)
4405
+ sage: all(g.degree(v) == 3 for v in g)
4406
+ True
4407
+
4408
+ TESTS::
4409
+
4410
+ sage: g = graphs.CubeConnectedCycle(0)
4411
+ Traceback (most recent call last):
4412
+ ...
4413
+ ValueError: the dimension d must be greater than 0
4414
+ """
4415
+ if d < 1:
4416
+ raise ValueError('the dimension d must be greater than 0')
4417
+
4418
+ G = Graph(name=f"Cube-Connected Cycle of dimension {d}")
4419
+
4420
+ if d == 1:
4421
+ G.allow_loops(True)
4422
+ # only d = 1 requires loops
4423
+ G.add_edges([((0, 0), (0, 1)), ((0, 0), (0, 0)), ((0, 1), (0, 1))])
4424
+ return G
4425
+
4426
+ if d == 2:
4427
+ # only d = 2 require multiple edges
4428
+ G.allow_multiple_edges(True)
4429
+ G.add_edges([((0, 0), (0, 1)), ((0, 0), (0, 1)), ((0, 0), (1, 0)),
4430
+ ((0, 1), (2, 1)), ((1, 0), (1, 1)), ((1, 0), (1, 1)),
4431
+ ((1, 1), (3, 1)), ((2, 0), (2, 1)), ((2, 0), (2, 1)),
4432
+ ((2, 0), (3, 0)), ((3, 0), (3, 1)), ((3, 0), (3, 1))])
4433
+ return G
4434
+
4435
+ for x in range(1 << d):
4436
+ G.add_cycle([(x, y) for y in range(d)])
4437
+
4438
+ for x, y in G:
4439
+ G.add_edge((x, y), (x ^ (1 << y), y))
4440
+
4441
+ return G
4442
+
4443
+
4444
+ def StaircaseGraph(n):
4445
+ r"""
4446
+ Return a staircase graph with `2n` nodes
4447
+
4448
+ For `n \geq 3`, the staircase graph of order `2n` is the graph obtained
4449
+ from the ladder graph of order `2n - 2`, i.e., ``graphs.LadderGraph(n - 1)``
4450
+ by introducing two new nodes `2n - 2` and `2n - 1`, and then joining the
4451
+ node `2n - 2` with `0` and `n - 1`, the node `2n - 1` with `n - 2` and
4452
+ `2n - 3`, and the nodes `2n - 2` and `2n - 1` with each other.
4453
+
4454
+ Note that ``graphs.StaircaseGraph(4)`` is also known as the ``Bicorn
4455
+ graph``. It is the only brick that has a unique `b`-invariant edge.
4456
+
4457
+ PLOTTING:
4458
+
4459
+ Upon construction, the position dictionary is filled to override
4460
+ the spring-layout algorithm. By convention, each staircase graph will be
4461
+ displayed horizontally, with the first `n - 1` nodes displayed from left to
4462
+ right on the top horizontal line, the second `n - 1` nodes displayed from
4463
+ left to right on the middle horizontal line, and the last two nodes
4464
+ displayed at the bottom two corners.
4465
+
4466
+ INPUT:
4467
+
4468
+ - ``n`` -- an integer at least 3; number of nodes is `2n`
4469
+
4470
+ OUTPUT:
4471
+
4472
+ - ``G`` -- a staircase graph of order `2n`; note that a
4473
+ :class:`ValueError` is returned if `n < 3`
4474
+
4475
+ EXAMPLES:
4476
+
4477
+ Construct and show a staircase graph with 10 nodes::
4478
+
4479
+ sage: g = graphs.StaircaseGraph(5)
4480
+ sage: g.show() # long time # needs sage.plot
4481
+
4482
+ Construct and show the Bicorn graph. Note that the edge `(1, 4)` is the
4483
+ unique `b`-invariant edge::
4484
+
4485
+ sage: bicornGraph = graphs.StaircaseGraph(4)
4486
+ sage: bicornGraph.show() # long time # needs sage.plot
4487
+
4488
+ Create several staircase graphs in a Sage graphics array::
4489
+
4490
+ sage: # needs sage.plots
4491
+ sage: g = []
4492
+ sage: j = []
4493
+ sage: for i in range(9):
4494
+ ....: k = graphs.StaircaseGraph(i+3)
4495
+ ....: g.append(k)
4496
+ sage: for i in range(3):
4497
+ ....: n = []
4498
+ ....: for m in range(3):
4499
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4500
+ ....: j.append(n)
4501
+ sage: G = graphics_array(j)
4502
+ sage: G.show() # long time
4503
+
4504
+ TESTS:
4505
+
4506
+ The input parameter must be an integer that is at least 3::
4507
+
4508
+ sage: G = graphs.StaircaseGraph(2)
4509
+ Traceback (most recent call last):
4510
+ ...
4511
+ ValueError: parameter n must be at least 3
4512
+
4513
+ REFERENCES:
4514
+
4515
+ - [LM2024]_
4516
+
4517
+ .. SEEALSO::
4518
+
4519
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.LadderGraph`
4520
+
4521
+ AUTHORS:
4522
+
4523
+ - Janmenjaya Panda (2024-06-09)
4524
+ """
4525
+ if n < 3:
4526
+ raise ValueError("parameter n must be at least 3")
4527
+
4528
+ pos_dict = {
4529
+ 0: (0, 1),
4530
+ n - 2: (n, 1),
4531
+ 2*n - 2: (0, -1),
4532
+ 2*n - 1: (n, -1)
4533
+ }
4534
+
4535
+ edges = [
4536
+ (0, n - 1),
4537
+ (0, 2*n - 2),
4538
+ (n - 2, 2*n - 3),
4539
+ (n - 2, 2*n - 1),
4540
+ (n - 1, 2*n - 2),
4541
+ (2*n - 3, 2*n - 1),
4542
+ (2*n - 2, 2*n - 1)
4543
+ ]
4544
+
4545
+ for v in range(1, n - 2):
4546
+ pos_dict[v] = (v + 1, 1)
4547
+ edges.append((v, v + n - 1))
4548
+
4549
+ for v in range(n - 1, 2*n - 2):
4550
+ pos_dict[v] = (v - n + 2, 0)
4551
+
4552
+ G = Graph(2 * n, pos=pos_dict, name="Staircase graph")
4553
+ G.add_edges(edges)
4554
+ G.add_path(list(range(n - 1)))
4555
+ G.add_path(list(range(n - 1, 2*n - 2)))
4556
+ return G
4557
+
4558
+
4559
+ def BiwheelGraph(n):
4560
+ r"""
4561
+ Return a biwheel graph with `2n` nodes
4562
+
4563
+ For `n \geq 4`, the biwheel graph of order `2n` is the planar
4564
+ bipartite graph obtained from the cycle graph of order `2n - 2`, i.e.,
4565
+ ``graphs.CycleGraph(2*n - 2)`` (called the `rim` of the biwheel graph) by
4566
+ introducing two new nodes `2n - 2` and `2n - 1` (called the *hubs* of the
4567
+ biwheel graph), and then joining the node `2n - 2` with the odd indexed
4568
+ nodes up to `2n - 3` and joining the node `2n - 1` with the even indexed
4569
+ nodes up to `2n - 4`.
4570
+
4571
+ PLOTTING:
4572
+
4573
+ Upon construction, the position dictionary is filled to override
4574
+ the spring-layout algorithm. By convention, each biwheel graph will be
4575
+ displayed with the first (0) node at the right if `n` is even or
4576
+ otherwise at an angle `\pi / (2n - 2)` with respect to the origin, with the
4577
+ rest of the nodes up to `2n - 3` following in a counterclockwise manner.
4578
+ Note that the last two nodes, i.e., the hubs `2n - 2` and `2n - 1` will
4579
+ be displayed at the coordinates `(-1/3, 0)` and `(1/3, 0)` respectively.
4580
+
4581
+ INPUT:
4582
+
4583
+ - ``n`` -- an integer at least 4; number of nodes is `2n`
4584
+
4585
+ OUTPUT:
4586
+
4587
+ - ``G`` -- a biwheel graph of order `2n`; note that a
4588
+ :class:`ValueError` is returned if `n < 4`
4589
+
4590
+ EXAMPLES:
4591
+
4592
+ Construct and show a biwheel graph with 10 nodes::
4593
+
4594
+ sage: g = graphs.BiwheelGraph(5)
4595
+ sage: g.show() # long time # needs sage.plot
4596
+ sage: g.is_planar() # needs planarity
4597
+ True
4598
+ sage: g.is_bipartite()
4599
+ True
4600
+
4601
+ Create several biwheel graphs in a Sage graphics array::
4602
+
4603
+ sage: # needs sage.plots
4604
+ sage: g = []
4605
+ sage: j = []
4606
+ sage: for i in range(9):
4607
+ ....: k = graphs.BiwheelGraph(i+4)
4608
+ ....: g.append(k)
4609
+ sage: for i in range(3):
4610
+ ....: n = []
4611
+ ....: for m in range(3):
4612
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4613
+ ....: j.append(n)
4614
+ sage: G = graphics_array(j)
4615
+ sage: G.show() # long time
4616
+
4617
+ TESTS:
4618
+
4619
+ The input parameter must be an integer that is at least 4::
4620
+
4621
+ sage: G = graphs.BiwheelGraph(3)
4622
+ Traceback (most recent call last):
4623
+ ...
4624
+ ValueError: parameter n must be at least 4
4625
+
4626
+ REFERENCES:
4627
+
4628
+ - [LM2024]_
4629
+
4630
+ .. SEEALSO::
4631
+
4632
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.WheelGraph`,
4633
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.TruncatedBiwheelGraph`
4634
+
4635
+ AUTHORS:
4636
+
4637
+ - Janmenjaya Panda (2024-06-09)
4638
+ """
4639
+ if n < 4:
4640
+ raise ValueError("parameter n must be at least 4")
4641
+
4642
+ angle_param = 0
4643
+
4644
+ if n % 2:
4645
+ from math import pi
4646
+ angle_param = pi / (2*n - 2)
4647
+
4648
+ G = Graph(2 * n, name="Biwheel graph")
4649
+ pos_dict = G._circle_embedding(list(range(2*n - 2)), angle=angle_param, return_dict=True)
4650
+ edges = []
4651
+
4652
+ from sage.rings.rational_field import QQ
4653
+ pos_dict[2*n - 2] = (-QQ((1, 3)), 0)
4654
+ pos_dict[2*n - 1] = (QQ((1, 3)), 0)
4655
+
4656
+ for i in range(2*n - 2):
4657
+ if i % 2 == 0:
4658
+ edges += [(i, 2*n - 1)]
4659
+ else:
4660
+ edges += [(i, 2*n - 2)]
4661
+
4662
+ G.set_pos(pos_dict)
4663
+ G.add_cycle(list(range(2*n - 2)))
4664
+ G.add_edges(edges)
4665
+ return G
4666
+
4667
+
4668
+ def TruncatedBiwheelGraph(n):
4669
+ r"""
4670
+ Return a truncated biwheel graph with `2n` nodes
4671
+
4672
+ For `n \geq 3`, the truncated biwheel graph of order `2n` is the graph
4673
+ obtained from the path graph of order `2n - 2`, i.e.,
4674
+ ``graphs.PathGraph(2*n - 2)`` by introducing two new nodes `2n - 2` and
4675
+ `2n - 1`, and then joining the node `2n - 2` with the odd indexed nodes
4676
+ up to `2n - 3`, joining the node `2n - 1` with the even indexed nodes up to
4677
+ `2n - 4` and adding the edges `(0, 2n - 2)` and `(2n - 3, 2n - 1)`.
4678
+
4679
+ PLOTTING:
4680
+
4681
+ Upon construction, the position dictionary is filled to override the
4682
+ spring-layout algorithm. By convention, each truncated biwheel graph will
4683
+ be displayed horizontally, with the first `2n - 2` nodes displayed from
4684
+ left to right on the middle horizontal line and the nodes `2n - 2` and
4685
+ `2n - 1` displayed at the top and the bottom central positions
4686
+ respectively.
4687
+
4688
+ INPUT:
4689
+
4690
+ - ``n`` -- an integer at least 3; number of nodes is `2n`
4691
+
4692
+ OUTPUT:
4693
+
4694
+ - ``G`` -- a truncated biwheel graph of order `2n`; note that a
4695
+ :class:`ValueError` is returned if `n < 3`
4696
+
4697
+ EXAMPLES:
4698
+
4699
+ Construct and show a truncated biwheel graph with 10 nodes::
4700
+
4701
+ sage: g = graphs.TruncatedBiwheelGraph(5)
4702
+ sage: g.show() # long time # needs sage.plot
4703
+
4704
+ Create several truncated biwheel graphs in a Sage graphics array::
4705
+
4706
+ sage: # needs sage.plots
4707
+ sage: g = []
4708
+ sage: j = []
4709
+ sage: for i in range(9):
4710
+ ....: k = graphs.TruncatedBiwheelGraph(i+3)
4711
+ ....: g.append(k)
4712
+ sage: for i in range(3):
4713
+ ....: n = []
4714
+ ....: for m in range(3):
4715
+ ....: n.append(g[3*i + m].plot(vertex_size=50 - 4*(3*i+m), vertex_labels=False))
4716
+ ....: j.append(n)
4717
+ sage: G = graphics_array(j)
4718
+ sage: G.show() # long time
4719
+
4720
+ TESTS:
4721
+
4722
+ The input parameter must be an integer that is at least 3::
4723
+
4724
+ sage: G = graphs.TruncatedBiwheelGraph(2)
4725
+ Traceback (most recent call last):
4726
+ ...
4727
+ ValueError: parameter n must be at least 3
4728
+
4729
+ REFERENCES:
4730
+
4731
+ - [LM2024]_
4732
+
4733
+ .. SEEALSO::
4734
+
4735
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.WheelGraph`,
4736
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.BiwheelGraph`
4737
+
4738
+ AUTHORS:
4739
+
4740
+ - Janmenjaya Panda (2024-06-09)
4741
+ """
4742
+ if n < 3:
4743
+ raise ValueError("parameter n must be at least 3")
4744
+
4745
+ pos_dict = {2*n - 2: (0, n), 2*n - 1: (0, -n)}
4746
+ edges = [(0, 2*n - 2), (2*n - 3, 2*n - 1)]
4747
+
4748
+ for v in range(2*n - 2):
4749
+ pos_dict[v] = (2*(v-n) + 3, 0)
4750
+ if v % 2 == 0:
4751
+ edges += [(v, 2*n - 1)]
4752
+ else:
4753
+ edges += [(v, 2*n - 2)]
4754
+
4755
+ G = Graph(2 * n, pos=pos_dict, name="Truncated biwheel graph")
4756
+ G.add_path(list(range(2*n - 2)))
4757
+ G.add_edges(edges)
4758
+
4759
+ return G