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,2623 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Random 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
+ # and Emily A. Kirkman
11
+ # Copyright (C) 2009 Michael C. Yurko <myurko@gmail.com>
12
+ #
13
+ # Distributed under the terms of the GNU General Public License (GPL)
14
+ # http://www.gnu.org/licenses/
15
+ ###########################################################################
16
+
17
+ import sys
18
+ # import from Sage library
19
+ from sage.graphs.graph import Graph
20
+ from sage.misc.randstate import current_randstate
21
+ from sage.misc.randstate import set_random_seed
22
+ from sage.misc.prandom import random
23
+ from sage.misc.prandom import randint
24
+
25
+
26
+ def RandomGNP(n, p, seed=None, fast=True, algorithm='Sage'):
27
+ r"""
28
+ Return a random graph on `n` nodes. Each edge is inserted independently
29
+ with probability `p`.
30
+
31
+ INPUT:
32
+
33
+ - ``n`` -- number of nodes of the graph
34
+
35
+ - ``p`` -- probability of an edge
36
+
37
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
38
+ number generator (default: ``None``)
39
+
40
+ - ``fast`` -- boolean (default: ``True``) to use the algorithm with
41
+ time complexity in `O(n+m)` proposed in [BB2005a]_. It is designed
42
+ for generating large sparse graphs. It is faster than other algorithms for
43
+ *LARGE* instances (try it to know whether it is useful for you).
44
+
45
+ - ``algorithm`` -- (default: ``'Sage'``) this function uses the
46
+ algorithm implemented in ``sage.graphs.graph_generators_pyx.pyx``. When
47
+ ``algorithm='networkx'``, this function calls the NetworkX function
48
+ ``fast_gnp_random_graph``, unless ``fast=False``, then
49
+ ``gnp_random_graph``. Try them to know which algorithm is the best for
50
+ you. The ``fast`` parameter is not taken into account by the 'Sage'
51
+ algorithm so far.
52
+
53
+ REFERENCES:
54
+
55
+ - [ER1959]_
56
+
57
+ - [Gil1959]_
58
+
59
+ PLOTTING: When plotting, this graph will use the default spring-layout
60
+ algorithm, unless a position dictionary is specified.
61
+
62
+ EXAMPLES: We show the edge list of a random graph on 6 nodes with
63
+ probability `p = .4`::
64
+
65
+ sage: set_random_seed(0)
66
+ sage: graphs.RandomGNP(6, .4).edges(sort=true, labels=False)
67
+ [(0, 3), (1, 2), (2, 3), (2, 4)]
68
+
69
+ We plot a random graph on 12 nodes with probability `p = .71`::
70
+
71
+ sage: gnp = graphs.RandomGNP(12,.71)
72
+ sage: gnp.show() # long time # needs sage.plot
73
+
74
+ We view many random graphs using a graphics array::
75
+
76
+ sage: g = []
77
+ sage: j = []
78
+ sage: for i in range(9):
79
+ ....: k = graphs.RandomGNP(i+3,.43)
80
+ ....: g.append(k)
81
+ sage: for i in range(3): # needs sage.plot
82
+ ....: n = []
83
+ ....: for m in range(3):
84
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
85
+ ....: j.append(n)
86
+ sage: G = graphics_array(j) # needs sage.plot
87
+ sage: G.show() # long time # needs sage.plot
88
+ sage: graphs.RandomGNP(4,1)
89
+ Complete graph: Graph on 4 vertices
90
+
91
+ TESTS::
92
+
93
+ sage: graphs.RandomGNP(50,.2,algorithm=50)
94
+ Traceback (most recent call last):
95
+ ...
96
+ ValueError: 'algorithm' must be equal to 'networkx' or to 'Sage'.
97
+ sage: set_random_seed(0)
98
+ sage: graphs.RandomGNP(50,.2, algorithm='Sage').size()
99
+ 243
100
+ sage: graphs.RandomGNP(50,.2, algorithm='networkx').size() # needs networkx
101
+ 279 # 32-bit
102
+ 209 # 64-bit
103
+ """
104
+ if n < 0:
105
+ raise ValueError("The number of nodes must be positive or null.")
106
+ if 0.0 > p or 1.0 < p:
107
+ raise ValueError("The probability p must be in [0..1].")
108
+
109
+ if p == 1:
110
+ from sage.graphs.generators.basic import CompleteGraph
111
+ return CompleteGraph(n)
112
+
113
+ if algorithm == 'networkx':
114
+ if seed is None:
115
+ seed = int(current_randstate().long_seed() % sys.maxsize)
116
+ import networkx
117
+ if fast:
118
+ G = networkx.fast_gnp_random_graph(n, p, seed=seed)
119
+ else:
120
+ G = networkx.gnp_random_graph(n, p, seed=seed)
121
+ return Graph(G)
122
+ elif algorithm in ['Sage', 'sage']:
123
+ # We use the Sage generator
124
+ from sage.graphs.graph_generators_pyx import RandomGNP as sageGNP
125
+ return sageGNP(n, p, seed=seed)
126
+ else:
127
+ raise ValueError("'algorithm' must be equal to 'networkx' or to 'Sage'.")
128
+
129
+
130
+ def RandomBarabasiAlbert(n, m, seed=None):
131
+ r"""
132
+ Return a random graph created using the Barabasi-Albert preferential
133
+ attachment model.
134
+
135
+ A graph with `m` vertices and no edges is initialized, and a graph of `n`
136
+ vertices is grown by attaching new vertices each with `m` edges that are
137
+ attached to existing vertices, preferentially with high degree.
138
+
139
+ INPUT:
140
+
141
+ - ``n`` -- number of vertices in the graph
142
+
143
+ - ``m`` -- number of edges to attach from each new node
144
+
145
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
146
+ number generator (default: ``None``)
147
+
148
+ EXAMPLES:
149
+
150
+ We show the edge list of a random graph on 6 nodes with `m = 2`::
151
+
152
+ sage: G = graphs.RandomBarabasiAlbert(6,2) # needs networkx
153
+ sage: G.order(), G.size() # needs networkx
154
+ (6, 8)
155
+ sage: G.degree_sequence() # random # needs networkx
156
+ [4, 3, 3, 2, 2, 2]
157
+
158
+ We plot a random graph on 12 nodes with `m = 3`::
159
+
160
+ sage: ba = graphs.RandomBarabasiAlbert(12,3) # needs networkx
161
+ sage: ba.show() # long time # needs networkx sage.plot
162
+
163
+ We view many random graphs using a graphics array::
164
+
165
+ sage: # needs networkx sage.plot
166
+ sage: g = []
167
+ sage: j = []
168
+ sage: for i in range(1,10):
169
+ ....: k = graphs.RandomBarabasiAlbert(i+3, 3)
170
+ ....: g.append(k)
171
+ sage: for i in range(3):
172
+ ....: n = []
173
+ ....: for m in range(3):
174
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
175
+ ....: j.append(n)
176
+ sage: G = graphics_array(j)
177
+ sage: G.show() # long time
178
+
179
+ When `m = 1`, the generated graph is a tree::
180
+
181
+ sage: graphs.RandomBarabasiAlbert(6, 1).is_tree() # needs networkx
182
+ True
183
+ """
184
+ if seed is None:
185
+ seed = int(current_randstate().long_seed() % sys.maxsize)
186
+ import networkx
187
+ return Graph(networkx.barabasi_albert_graph(int(n), int(m), seed=seed))
188
+
189
+
190
+ def RandomBipartite(n1, n2, p, set_position=False, seed=None):
191
+ r"""
192
+ Return a bipartite graph with `n1+n2` vertices such that any edge
193
+ from `[n1]` to `[n2]` exists with probability `p`.
194
+
195
+ INPUT:
196
+
197
+ - ``n1``, ``n2`` -- cardinalities of the two sets
198
+
199
+ - ``p`` -- probability for an edge to exist
200
+
201
+ - ``set_position`` -- boolean (default: ``False``); if set to ``True``, we
202
+ assign positions to the vertices so that the set of cardinality `n1` is
203
+ on the line `y=1` and the set of cardinality `n2` is on the line `y=0`
204
+
205
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
206
+ number generator (default: ``None``)
207
+
208
+ EXAMPLES::
209
+
210
+ sage: g = graphs.RandomBipartite(5, 2, 0.5) # needs numpy
211
+ sage: g.vertices(sort=True) # needs numpy
212
+ [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1)]
213
+
214
+ TESTS::
215
+
216
+ sage: g = graphs.RandomBipartite(5, -3, 0.5) # needs numpy
217
+ Traceback (most recent call last):
218
+ ...
219
+ ValueError: n1 and n2 should be integers strictly greater than 0
220
+ sage: g = graphs.RandomBipartite(5, 3, 1.5) # needs numpy
221
+ Traceback (most recent call last):
222
+ ...
223
+ ValueError: parameter p is a probability, and so should be a real value between 0 and 1
224
+
225
+ :issue:`12155`::
226
+
227
+ sage: graphs.RandomBipartite(5, 6, .2).complement() # needs numpy
228
+ complement(Random bipartite graph of order 5+6 with edge probability 0.200000000000000): Graph on 11 vertices
229
+
230
+ Test assigned positions::
231
+
232
+ sage: # needs numpy
233
+ sage: graphs.RandomBipartite(1, 2, .1, set_position=True).get_pos()
234
+ {(0, 0): (1, 1.0), (1, 0): (0, 0), (1, 1): (2.0, 0.0)}
235
+ sage: graphs.RandomBipartite(2, 1, .1, set_position=True).get_pos()
236
+ {(0, 0): (0, 1), (0, 1): (2.0, 1.0), (1, 0): (1, 0.0)}
237
+ sage: graphs.RandomBipartite(2, 2, .1, set_position=True).get_pos()
238
+ {(0, 0): (0, 1), (0, 1): (2.0, 1.0), (1, 0): (0, 0), (1, 1): (2.0, 0.0)}
239
+ sage: graphs.RandomBipartite(2, 2, .1, set_position=False).get_pos()
240
+ """
241
+ if not (p >= 0 and p <= 1):
242
+ raise ValueError("parameter p is a probability, and so should be a real value between 0 and 1")
243
+ if not (n1 > 0 and n2 > 0):
244
+ raise ValueError("n1 and n2 should be integers strictly greater than 0")
245
+ if seed is not None:
246
+ set_random_seed(seed)
247
+
248
+ from numpy.random import uniform
249
+
250
+ g = Graph(name=f"Random bipartite graph of order {n1}+{n2} with edge probability {p}")
251
+
252
+ S1 = [(0, i) for i in range(n1)]
253
+ S2 = [(1, i) for i in range(n2)]
254
+ g.add_vertices(S1)
255
+ g.add_vertices(S2)
256
+
257
+ for w in range(n2):
258
+ for v in range(n1):
259
+ if uniform() <= p:
260
+ g.add_edge((0, v), (1, w))
261
+
262
+ # We now assign positions to vertices:
263
+ # - vertices in S1 are placed on the line from (0, 1) to (max(n1, n2), 1)
264
+ # - vertices in S2 are placed on the line from (0, 0) to (max(n1, n2), 0)
265
+ # If S1 or S2 has a single vertex, it is centered in the line.
266
+ if set_position:
267
+ nmax = max(n1, n2)
268
+ g._line_embedding(S1, first=(0, 1), last=(nmax, 1))
269
+ g._line_embedding(S2, first=(0, 0), last=(nmax, 0))
270
+
271
+ return g
272
+
273
+
274
+ def RandomRegularBipartite(n1, n2, d1, set_position=False, seed=None):
275
+ r"""
276
+ Return a random regular bipartite graph on `n1 + n2` vertices.
277
+
278
+ The bipartite graph has `n1 * d1` edges. Hence, `n2` must divide `n1 * d1`.
279
+ Each vertex of the set of cardinality `n1` has degree `d1` (which can be at
280
+ most `n2`) and each vertex in the set of cardinality `n2` has degree
281
+ `(n1 * d1) / n2`. The bipartite graph has no multiple edges.
282
+
283
+ This generator implements an algorithm inspired by that of [MW1990]_ for
284
+ the uniform generation of random regular bipartite graphs. It performs well
285
+ when `d1 = o(n2^{1/3})` or (`n2 - d1 = o(n2^{1/3})`). In other cases, the
286
+ running time can be huge. Note that the currently implemented algorithm
287
+ does not generate uniformly random graphs.
288
+
289
+ INPUT:
290
+
291
+ - ``n1``, ``n2`` -- number of vertices in each side
292
+
293
+ - ``d1`` -- degree of the vertices in the set of cardinality `n1`
294
+
295
+ - ``set_position`` -- boolean (default: ``False``); if set to ``True``, we
296
+ assign positions to the vertices so that the set of cardinality `n1` is
297
+ on the line `y=1` and the set of cardinality `n2` is on the line `y=0`.
298
+
299
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
300
+ number generator (default: ``None``)
301
+
302
+ EXAMPLES::
303
+
304
+ sage: g = graphs.RandomRegularBipartite(4, 6, 3)
305
+ sage: g.order(), g.size()
306
+ (10, 12)
307
+ sage: set(g.degree())
308
+ {2, 3}
309
+
310
+ sage: graphs.RandomRegularBipartite(1, 2, 2, set_position=True).get_pos()
311
+ {0: (1, 1.0), 1: (0, 0), 2: (2.0, 0.0)}
312
+ sage: graphs.RandomRegularBipartite(2, 1, 1, set_position=True).get_pos()
313
+ {0: (0, 1), 1: (2.0, 1.0), 2: (1, 0.0)}
314
+ sage: graphs.RandomRegularBipartite(2, 3, 3, set_position=True).get_pos()
315
+ {0: (0, 1), 1: (3.0, 1.0), 2: (0, 0), 3: (1.5, 0.0), 4: (3.0, 0.0)}
316
+ sage: graphs.RandomRegularBipartite(2, 3, 3, set_position=False).get_pos()
317
+
318
+ TESTS:
319
+
320
+ Giving invalid parameters::
321
+
322
+ sage: graphs.RandomRegularBipartite(0, 2, 1)
323
+ Traceback (most recent call last):
324
+ ...
325
+ ValueError: n1 and n2 must be integers greater than 0
326
+ sage: graphs.RandomRegularBipartite(2, 3, 2)
327
+ Traceback (most recent call last):
328
+ ...
329
+ ValueError: the product n1 * d1 must be a multiple of n2
330
+ sage: graphs.RandomRegularBipartite(1, 1, 2)
331
+ Traceback (most recent call last):
332
+ ...
333
+ ValueError: d1 must be less than or equal to n2
334
+ """
335
+ if n1 < 1 or n2 < 1:
336
+ raise ValueError("n1 and n2 must be integers greater than 0")
337
+ if d1 > n2:
338
+ raise ValueError("d1 must be less than or equal to n2")
339
+ d2 = (n1 * d1) // n2
340
+ if n1 * d1 != n2 * d2:
341
+ raise ValueError("the product n1 * d1 must be a multiple of n2")
342
+ if seed is not None:
343
+ set_random_seed(seed)
344
+
345
+ complement = False
346
+ if d1 > n2/2 or d2 > n1/2:
347
+ # We build the complement graph instead
348
+ complement = True
349
+ d1 = n2 - d1
350
+ d2 = n1 - d2
351
+
352
+ E = set()
353
+ F = set()
354
+
355
+ if d1:
356
+ from sage.misc.prandom import shuffle, choice
357
+
358
+ M1 = n1 * d1 * (d1 - 1)
359
+ M2 = n2 * d2 * (d2 - 1)
360
+ M = n1 * d1 + n2 * d2
361
+ UB_parallel = (M1 * M2) / M**2
362
+
363
+ # We create a set of n1 * d1 random edges with possible repetitions. We
364
+ # require that the number of repeated edges is bounded and that an edge
365
+ # can be repeated only once.
366
+ L = [u for u in range(n1) for i in range(d1)]
367
+ R = [u for u in range(n1, n1 + n2) for i in range(d2)]
368
+ restart = True
369
+ while restart:
370
+ restart = False
371
+ shuffle(R)
372
+ E = set()
373
+ F = set()
374
+ for e in zip(L, R):
375
+ if e in E:
376
+ if e in F:
377
+ # We have more than 2 times e => restart
378
+ restart = True
379
+ break
380
+ else:
381
+ F.add(e)
382
+ if len(F) >= UB_parallel:
383
+ # We have too many parallel edges
384
+ restart = True
385
+ break
386
+ else:
387
+ E.add(e)
388
+
389
+ # We remove multiple edges by applying random forward d-switching. That is,
390
+ # given edge e that is repeated twice, we select single edges f and g with
391
+ # no common end points, and then create 4 new edges. We forbid creating new
392
+ # multiple edges.
393
+ while F:
394
+ # random forward d-switching
395
+ e = F.pop()
396
+ E.discard(e)
397
+ TE = tuple(E.difference(F))
398
+ # We select 2 vertex disjoint edges
399
+ while True:
400
+ f = choice(TE)
401
+ if e[0] == f[0] or e[1] == f[1]:
402
+ continue
403
+ g = choice(TE)
404
+ if e[0] != g[0] and e[1] != g[1] and f[0] != g[0] and f[1] != g[1]:
405
+ new_edges = [(f[0], e[1]), (e[0], f[1]), (e[0], g[1]), (g[0], e[1])]
406
+ if not E.intersection(new_edges):
407
+ # We are not creating new parallel edges.
408
+ # To generate uniformly random graphs we would have to
409
+ # implement a probabilistic restart of the whole algorithm
410
+ # here, see [MW1990].
411
+ break
412
+ E.discard(f)
413
+ E.discard(g)
414
+ E.update(new_edges)
415
+
416
+ if complement:
417
+ from sage.graphs.generators.basic import CompleteBipartiteGraph
418
+ E = E.symmetric_difference(CompleteBipartiteGraph(n1, n2).edges(sort=False, labels=False))
419
+ d1, d2 = n2 - d1, n1 - d2
420
+
421
+ name = "Random regular bipartite graph of order {}+{} and degrees {} and {}".format(n1, n2, d1, d2)
422
+ G = Graph(list(E), name=name)
423
+
424
+ # We now assign positions to vertices:
425
+ # - vertices 0,..,n1-1 are placed on the line (0, 1) to (max(n1, n2), 1)
426
+ # - vertices n1,..,n1+n2-1 are placed on the line (0, 0) to (max(n1, n2), 0)
427
+ # If n1 (or n2) is 1, the vertex is centered in the line.
428
+ if set_position:
429
+ nmax = max(n1, n2)
430
+ G._line_embedding(list(range(n1)), first=(0, 1), last=(nmax, 1))
431
+ G._line_embedding(list(range(n1, n1 + n2)), first=(0, 0), last=(nmax, 0))
432
+
433
+ return G
434
+
435
+
436
+ def RandomBlockGraph(m, k, kmax=None, incidence_structure=False, seed=None):
437
+ r"""
438
+ Return a Random Block Graph.
439
+
440
+ A block graph is a connected graph in which every biconnected component
441
+ (block) is a clique.
442
+
443
+ .. SEEALSO::
444
+
445
+ - :wikipedia:`Block_graph` for more details on these graphs
446
+ - :meth:`~sage.graphs.graph.Graph.is_block_graph` -- test if a graph is a block graph
447
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
448
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cuts_tree`
449
+ - :meth:`~sage.combinat.designs.incidence_structures.IncidenceStructure`
450
+
451
+ INPUT:
452
+
453
+ - ``m`` -- integer; number of blocks (at least one)
454
+
455
+ - ``k`` -- integer; minimum number of vertices of a block (at least two)
456
+
457
+ - ``kmax`` -- integer (default: ``None``); by default, each block has `k`
458
+ vertices. When the parameter `kmax` is specified (with `kmax \geq k`), the
459
+ number of vertices of each block is randomly chosen between `k` and
460
+ `kmax`.
461
+
462
+ - ``incidence_structure`` -- boolean (default: ``False``); when set to
463
+ ``True``, the incidence structure of the graphs is returned instead of the
464
+ graph itself, that is the list of the lists of vertices in each
465
+ block. This is useful for the creation of some hypergraphs.
466
+
467
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
468
+ number generator (default: ``None``)
469
+
470
+ OUTPUT:
471
+
472
+ A Graph when ``incidence_structure==False`` (default), and otherwise an
473
+ incidence structure.
474
+
475
+ EXAMPLES:
476
+
477
+ A block graph with a single block is a clique::
478
+
479
+ sage: B = graphs.RandomBlockGraph(1, 4)
480
+ sage: B.is_clique()
481
+ True
482
+
483
+ A block graph with blocks of order 2 is a tree::
484
+
485
+ sage: B = graphs.RandomBlockGraph(10, 2)
486
+ sage: B.is_tree()
487
+ True
488
+
489
+ Every biconnected component of a block graph is a clique::
490
+
491
+ sage: B = graphs.RandomBlockGraph(5, 3, kmax=6)
492
+ sage: blocks,cuts = B.blocks_and_cut_vertices()
493
+ sage: all(B.is_clique(block) for block in blocks)
494
+ True
495
+
496
+ A block graph with blocks of order `k` has `m*(k-1)+1` vertices::
497
+
498
+ sage: m, k = 6, 4
499
+ sage: B = graphs.RandomBlockGraph(m, k)
500
+ sage: B.order() == m*(k-1)+1
501
+ True
502
+
503
+ Test recognition methods::
504
+
505
+ sage: B = graphs.RandomBlockGraph(6, 2, kmax=6)
506
+ sage: B.is_block_graph()
507
+ True
508
+ sage: B in graph_classes.Block
509
+ True
510
+
511
+ Asking for the incidence structure::
512
+
513
+ sage: m, k = 6, 4
514
+ sage: IS = graphs.RandomBlockGraph(m, k, incidence_structure=True)
515
+ sage: from sage.combinat.designs.incidence_structures import IncidenceStructure
516
+ sage: IncidenceStructure(IS) # needs sage.modules
517
+ Incidence structure with 19 points and 6 blocks
518
+ sage: m*(k-1)+1
519
+ 19
520
+
521
+ TESTS:
522
+
523
+ A block graph has at least one block, so `m\geq 1`::
524
+
525
+ sage: B = graphs.RandomBlockGraph(0, 1)
526
+ Traceback (most recent call last):
527
+ ...
528
+ ValueError: the number `m` of blocks must be >= 1
529
+
530
+ A block has at least 2 vertices, so `k\geq 2`::
531
+
532
+ sage: B = graphs.RandomBlockGraph(1, 1)
533
+ Traceback (most recent call last):
534
+ ...
535
+ ValueError: the minimum number `k` of vertices in a block must be >= 2
536
+
537
+ The maximum size of a block is at least its minimum size, so `k\leq kmax`::
538
+
539
+ sage: B = graphs.RandomBlockGraph(1, 3, kmax=2)
540
+ Traceback (most recent call last):
541
+ ...
542
+ ValueError: the maximum number `kmax` of vertices in a block must be >= `k`
543
+ """
544
+ from sage.misc.prandom import choice
545
+ from sage.sets.disjoint_set import DisjointSet
546
+
547
+ if m < 1:
548
+ raise ValueError("the number `m` of blocks must be >= 1")
549
+ if k < 2:
550
+ raise ValueError("the minimum number `k` of vertices in a block must be >= 2")
551
+ if kmax is None:
552
+ kmax = k
553
+ elif kmax < k:
554
+ raise ValueError("the maximum number `kmax` of vertices in a block must be >= `k`")
555
+ if seed is not None:
556
+ set_random_seed(seed)
557
+
558
+ if m == 1:
559
+ # A block graph with a single block is a clique
560
+ IS = [list(range(randint(k, kmax)))]
561
+
562
+ elif kmax == 2:
563
+ # A block graph with blocks of order 2 is a tree
564
+ IS = [list(e) for e in RandomTree(m + 1).edges(sort=True, labels=False)]
565
+
566
+ else:
567
+ # We start with a random tree of order m
568
+ T = RandomTree(m)
569
+
570
+ # We create a block of order in range [k,kmax] per vertex of the tree
571
+ B = {u: [(u, i) for i in range(randint(k, kmax))] for u in T}
572
+
573
+ # For each edge of the tree, we choose 1 vertex in each of the
574
+ # corresponding blocks and we merge them. We use a disjoint set data
575
+ # structure to keep a unique identifier per merged vertices
576
+ DS = DisjointSet([i for u in B for i in B[u]])
577
+ for u, v in T.edges(sort=True, labels=0):
578
+ DS.union(choice(B[u]), choice(B[v]))
579
+
580
+ # We relabel vertices in the range [0, m*(k-1)] and build the incidence
581
+ # structure
582
+ new_label = {root: i for i, root in enumerate(DS.root_to_elements_dict())}
583
+ IS = [[new_label[DS.find(v)] for v in B[u]] for u in B]
584
+
585
+ if incidence_structure:
586
+ return IS
587
+
588
+ # We finally build the block graph
589
+ if k == kmax:
590
+ BG = Graph(name="Random Block Graph with {} blocks of order {}".format(m, k))
591
+ else:
592
+ BG = Graph(name="Random Block Graph with {} blocks of order {} to {}".format(m, k, kmax))
593
+ for block in IS:
594
+ BG.add_clique(block)
595
+ return BG
596
+
597
+
598
+ def RandomBoundedToleranceGraph(n, seed=None):
599
+ r"""
600
+ Return a random bounded tolerance graph.
601
+
602
+ The random tolerance graph is built from a random bounded tolerance
603
+ representation by using the function `ToleranceGraph`. This representation
604
+ is a list `((l_0,r_0,t_0), (l_1,r_1,t_1), ..., (l_k,r_k,t_k))` where `k =
605
+ n-1` and `I_i = (l_i,r_i)` denotes a random interval and `t_i` a random
606
+ positive value less than or equal to the length of the interval `I_i`. The
607
+ width of the representation is limited to `n^2 * 2^n`.
608
+
609
+ .. NOTE::
610
+
611
+ The tolerance representation used to create the graph can
612
+ be recovered using ``get_vertex()`` or ``get_vertices()``.
613
+
614
+ INPUT:
615
+
616
+ - ``n`` -- number of vertices of the random graph
617
+
618
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
619
+ number generator (default: ``None``)
620
+
621
+ EXAMPLES:
622
+
623
+ Every (bounded) tolerance graph is perfect. Hence, the
624
+ chromatic number is equal to the clique number ::
625
+
626
+ sage: g = graphs.RandomBoundedToleranceGraph(8)
627
+ sage: g.clique_number() == g.chromatic_number() # needs cliquer
628
+ True
629
+
630
+ TESTS:
631
+
632
+ Check that :issue:`32186` is fixed::
633
+
634
+ sage: for _ in range(100): _ = graphs.RandomBoundedToleranceGraph(1)
635
+
636
+ Check input parameter::
637
+
638
+ sage: g = graphs.RandomToleranceGraph(-2)
639
+ Traceback (most recent call last):
640
+ ...
641
+ ValueError: the number `n` of vertices must be >= 0
642
+ """
643
+ if n < 0:
644
+ raise ValueError('the number `n` of vertices must be >= 0')
645
+ if seed is not None:
646
+ set_random_seed(seed)
647
+
648
+ from sage.graphs.generators.intersection import ToleranceGraph
649
+
650
+ W = n ** 2 * 2 ** n
651
+ tolrep = []
652
+ for _ in range(n):
653
+ left = randint(0, W - 1)
654
+ right = randint(0, W)
655
+ if left >= right:
656
+ left, right = right, left + 1
657
+ tolrep.append((left, right, randint(1, right - left)))
658
+
659
+ return ToleranceGraph(tolrep)
660
+
661
+
662
+ def RandomGNM(n, m, dense=False, seed=None):
663
+ r"""
664
+ Return a graph randomly picked out of all graphs on `n` vertices with `m`
665
+ edges.
666
+
667
+ INPUT:
668
+
669
+ - ``n`` -- number of vertices
670
+
671
+ - ``m`` -- number of edges
672
+
673
+ - ``dense`` -- whether to use NetworkX's
674
+ :func:`dense_gnm_random_graph` or :func:`gnm_random_graph`
675
+
676
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
677
+ number generator (default: ``None``)
678
+
679
+ EXAMPLES:
680
+
681
+ We show the edge list of a random graph on 5 nodes with 10 edges::
682
+
683
+ sage: graphs.RandomGNM(5, 10).edges(sort=True, labels=False) # needs networkx
684
+ [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
685
+
686
+ We plot a random graph on 12 nodes and 12 edges::
687
+
688
+ sage: gnm = graphs.RandomGNM(12, 12) # needs networkx
689
+ sage: gnm.show() # long time # needs networkx sage.plot
690
+
691
+ We view many random graphs using a graphics array::
692
+
693
+ sage: # needs networkx sage.plot
694
+ sage: g = []
695
+ sage: j = []
696
+ sage: for i in range(9):
697
+ ....: k = graphs.RandomGNM(i+3, i^2-i)
698
+ ....: g.append(k)
699
+ sage: for i in range(3):
700
+ ....: n = []
701
+ ....: for m in range(3):
702
+ ....: n.append(g[3*i + m].plot(vertex_size=50, vertex_labels=False))
703
+ ....: j.append(n)
704
+ sage: G = graphics_array(j)
705
+ sage: G.show() # long time
706
+ """
707
+ if seed is None:
708
+ seed = int(current_randstate().long_seed() % sys.maxsize)
709
+ import networkx
710
+ if dense:
711
+ return Graph(networkx.dense_gnm_random_graph(n, m, seed=seed))
712
+ else:
713
+ return Graph(networkx.gnm_random_graph(n, m, seed=seed))
714
+
715
+
716
+ def RandomNewmanWattsStrogatz(n, k, p, seed=None):
717
+ r"""
718
+ Return a Newman-Watts-Strogatz small world random graph on `n` vertices.
719
+
720
+ From the NetworkX documentation: first create a ring over `n` nodes. Then
721
+ each node in the ring is connected with its `k` nearest neighbors. Then
722
+ shortcuts are created by adding new edges as follows: for each edge `u-v` in
723
+ the underlying "`n`-ring with `k` nearest neighbors"; with probability `p`
724
+ add a new edge `u-w` with randomly-chosen existing node `w`. In contrast
725
+ with ``networkx.watts_strogatz_graph()``, no edges are removed.
726
+
727
+ INPUT:
728
+
729
+ - ``n`` -- number of vertices
730
+
731
+ - ``k`` -- each vertex is connected to its `k` nearest neighbors
732
+
733
+ - ``p`` -- the probability of adding a new edge for each edge
734
+
735
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
736
+ number generator (default: ``None``)
737
+
738
+ EXAMPLES:
739
+
740
+ We check that the generated graph contains a cycle of order `n`::
741
+
742
+ sage: # needs networkx
743
+ sage: G = graphs.RandomNewmanWattsStrogatz(7, 2, 0.2)
744
+ sage: G.order()
745
+ 7
746
+ sage: C7 = graphs.CycleGraph(7)
747
+ sage: G.subgraph_search(C7)
748
+ Subgraph of (): Graph on 7 vertices
749
+ sage: G.diameter() <= C7.diameter()
750
+ True
751
+
752
+ ::
753
+
754
+ sage: G = graphs.RandomNewmanWattsStrogatz(12, 2, .3) # needs networkx
755
+ sage: G.show() # long time # needs networkx sage.plot
756
+
757
+ TESTS:
758
+
759
+ We check that when `k = 2` and `p = 0`, the generated graph is a cycle::
760
+
761
+ sage: G = graphs.RandomNewmanWattsStrogatz(7, 2, 0) # needs networkx
762
+ sage: G.is_cycle() # needs networkx
763
+ True
764
+
765
+ We check that when `k = 4` and `p = 0`, the generated graph is a circulant
766
+ graph of parameters ``[1, 2]``::
767
+
768
+ sage: G = graphs.RandomNewmanWattsStrogatz(7, 4, 0) # needs networkx
769
+ sage: G.is_isomorphic(graphs.CirculantGraph(7, [1, 2])) # needs networkx
770
+ True
771
+
772
+ REFERENCE:
773
+
774
+ [NWS2002]_
775
+ """
776
+ if seed is None:
777
+ seed = int(current_randstate().long_seed() % sys.maxsize)
778
+ import networkx
779
+ return Graph(networkx.newman_watts_strogatz_graph(n, k, p, seed=seed))
780
+
781
+
782
+ def RandomHolmeKim(n, m, p, seed=None):
783
+ r"""
784
+ Return a random graph generated by the Holme and Kim algorithm for
785
+ graphs with power law degree distribution and approximate average
786
+ clustering.
787
+
788
+ INPUT:
789
+
790
+ - ``n`` -- number of vertices
791
+
792
+ - ``m`` -- number of random edges to add for each new node
793
+
794
+ - ``p`` -- probability of adding a triangle after adding a random edge
795
+
796
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
797
+ number generator (default: ``None``)
798
+
799
+ From the NetworkX documentation: the average clustering has a hard time
800
+ getting above a certain cutoff that depends on `m`. This cutoff is often
801
+ quite low. Note that the transitivity (fraction of triangles to possible
802
+ triangles) seems to go down with network size. It is essentially the
803
+ Barabasi-Albert growth model with an extra step that each random edge is
804
+ followed by a chance of making an edge to one of its neighbors too (and thus
805
+ a triangle). This algorithm improves on B-A in the sense that it enables a
806
+ higher average clustering to be attained if desired. It seems possible to
807
+ have a disconnected graph with this algorithm since the initial `m` nodes
808
+ may not be all linked to a new node on the first iteration like the BA
809
+ model.
810
+
811
+ EXAMPLES::
812
+
813
+ sage: G = graphs.RandomHolmeKim(12, 3, .3) # needs networkx
814
+ sage: G.show() # long time # needs networkx sage.plot
815
+
816
+ REFERENCE:
817
+
818
+ [HK2002a]_
819
+ """
820
+ if seed is None:
821
+ seed = int(current_randstate().long_seed() % sys.maxsize)
822
+ import networkx
823
+ return Graph(networkx.powerlaw_cluster_graph(n, m, p, seed=seed))
824
+
825
+
826
+ def RandomIntervalGraph(n, seed=None):
827
+ r"""
828
+ Return a random interval graph.
829
+
830
+ An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}`
831
+ of intervals : to each interval of the list is associated one
832
+ vertex, two vertices being adjacent if the two corresponding
833
+ intervals intersect.
834
+
835
+ A random interval graph of order `n` is generated by picking
836
+ random values for the `(a_i,b_j)`, each of the two coordinates
837
+ being generated from the uniform distribution on the interval
838
+ `[0,1]`.
839
+
840
+ This definitions follows [BF2001]_.
841
+
842
+ .. NOTE::
843
+
844
+ The vertices are named 0, 1, 2, and so on. The intervals
845
+ used to create the graph are saved with the graph and can
846
+ be recovered using ``get_vertex()`` or ``get_vertices()``.
847
+
848
+ .. SEEALSO::
849
+
850
+ - :meth:`sage.graphs.generators.intersection.IntervalGraph`
851
+ - :meth:`sage.graphs.generators.random.RandomProperIntervalGraph`
852
+
853
+ INPUT:
854
+
855
+ - ``n`` -- integer; the number of vertices in the random graph
856
+
857
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
858
+ number generator (default: ``None``)
859
+
860
+ EXAMPLES:
861
+
862
+ As for any interval graph, the chromatic number is equal to
863
+ the clique number ::
864
+
865
+ sage: g = graphs.RandomIntervalGraph(8)
866
+ sage: g.clique_number() == g.chromatic_number() # needs cliquer
867
+ True
868
+ """
869
+ if seed is not None:
870
+ set_random_seed(seed)
871
+ from sage.graphs.generators.intersection import IntervalGraph
872
+
873
+ intervals = [tuple(sorted((random(), random()))) for i in range(n)]
874
+ return IntervalGraph(intervals, True)
875
+
876
+
877
+ def RandomProperIntervalGraph(n, seed=None):
878
+ r"""
879
+ Return a random proper interval graph.
880
+
881
+ An interval graph is built from a list `(a_i,b_i)_{1\leq i \leq n}` of
882
+ intervals : to each interval of the list is associated one vertex, two
883
+ vertices being adjacent if the two corresponding (closed) intervals
884
+ intersect. An interval graph is proper if no interval of the list properly
885
+ contains another interval.
886
+ Observe that proper interval graphs coincide with unit interval graphs.
887
+ See the :wikipedia:`Interval_graph` for more details.
888
+
889
+ This method implements the random proper interval graph generator proposed
890
+ in [SYKU2010]_ which outputs graphs with uniform probability. The time
891
+ complexity of this generator is in `O(n^3)`.
892
+
893
+ .. NOTE::
894
+
895
+ The vertices are named 0, 1, 2, and so on. The intervals
896
+ used to create the graph are saved with the graph and can
897
+ be recovered using ``get_vertex()`` or ``get_vertices()``.
898
+
899
+ .. SEEALSO::
900
+
901
+ - :meth:`sage.graphs.generators.intersection.IntervalGraph`
902
+ - :meth:`sage.graphs.generators.random.RandomIntervalGraph`
903
+
904
+ INPUT:
905
+
906
+ - ``n`` -- positive integer; the number of vertices of the graph
907
+
908
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
909
+ number generator (default: ``None``)
910
+
911
+ EXAMPLES::
912
+
913
+ sage: from sage.graphs.generators.random import RandomProperIntervalGraph
914
+ sage: G = RandomProperIntervalGraph(10)
915
+ sage: G.is_interval()
916
+ True
917
+
918
+ TESTS::
919
+
920
+ sage: from sage.graphs.generators.random import RandomProperIntervalGraph
921
+ sage: RandomProperIntervalGraph(0)
922
+ Graph on 0 vertices
923
+ sage: RandomProperIntervalGraph(1)
924
+ Graph on 1 vertex
925
+ sage: RandomProperIntervalGraph(-1)
926
+ Traceback (most recent call last):
927
+ ...
928
+ ValueError: parameter n must be >= 0
929
+ """
930
+ if seed is not None:
931
+ set_random_seed(seed)
932
+ if n < 0:
933
+ raise ValueError('parameter n must be >= 0')
934
+ if not n:
935
+ return Graph()
936
+
937
+ from sage.graphs.generators.intersection import IntervalGraph
938
+
939
+ if n == 1:
940
+ return IntervalGraph([[0, 1]])
941
+
942
+ from sage.combinat.combinat import catalan_number
943
+ from sage.functions.other import binomial
944
+
945
+ # let np = n' = n - 1
946
+ np = n - 1
947
+
948
+ # Choose case 1 with probability C(n') / (C(n') + binomial(n', n' // 2))
949
+ cnp = catalan_number(np)
950
+ if random() < cnp / (cnp + binomial(np, np // 2)):
951
+ # Case 1: Generate a balanced nonnegative string (that can be
952
+ # reversible) of length 2n' as follows. We generate the sequence of '['
953
+ # and ']' from left to right. Assume we have already chosen k symbols
954
+ # x_1x_2...x_k, with k < 2n'. The next symbol x_{k+1} is '[' with
955
+ # probability (h_x(k) + 2) (r - h_x(k) + 1) / (2 (r + 1) (h_x(k) + 1))
956
+ # where r = 2n' - k - 1 and
957
+ # h_x(k) = 0 if k == 0, h_x(k - 1) + 1 if x_i == 0 else h_x(k - 1) - 1.
958
+ #
959
+ # Since the i-th interval starts at the i-th symbol [ and ends at the
960
+ # i-th symbol ], we directly build the intervals
961
+ intervals = [[0, 2*n] for _ in range(n)]
962
+ L = 1 # next starting interval
963
+ R = 0 # next ending interval
964
+ hx = [0]
965
+ r = 2 * np - 1
966
+ for k in range(2 * np):
967
+ # Choose symbol x_{k+1}
968
+ if random() < ((hx[k] + 2) * (r - hx[k] + 1)) / (2 * (r + 1) * (hx[k] + 1)):
969
+ # We have chosen symbol [, so we start an interval
970
+ hx.append(hx[k] + 1)
971
+ intervals[L][0] = k + 1
972
+ L += 1
973
+ else:
974
+ # We have chosen symbol ], so we end an interval
975
+ hx.append(hx[k] - 1)
976
+ intervals[R][1] = k + 1
977
+ R += 1
978
+ r -= 1
979
+ # Add the last symbol, ], to get a sequence of length 2*n
980
+ intervals[R][1] = k + 2
981
+
982
+ # Finally return the interval graph
983
+ return IntervalGraph(intervals)
984
+
985
+ # Otherwise, generate a balanced nonnegative reversible string of length
986
+ # 2n'. This case happens with small probability and is way more complex.
987
+ # The string is of the form x_1x_2...x_ny_n..y_2y_1, where y_i is ] if x_i
988
+ # is [, and [ otherwise.
989
+
990
+ from sage.misc.cachefunc import cached_function
991
+
992
+ @cached_function
993
+ def compute_C(n, h):
994
+ """
995
+ Return C(n, h) as defined below.
996
+
997
+ Recall that the Catalan number is C(n) = binomial(2n, n) / (n + 1)
998
+ and let C(n, h) = 0 if h > n. The following equations hold for each
999
+ integers i and k with 0 <= i <= k.
1000
+
1001
+ 1. C(2k, 2i + 1) = 0, C(2k + 1, 2i) = 0,
1002
+ 2. C(2k, 0) = C(k), C(k, k) = 1, and
1003
+ 3. C(k, i) = C(k - 1, i - 1) + C(k - 1, i + 1).
1004
+ """
1005
+ if h > n:
1006
+ return 0
1007
+ if n % 2 != h % 2:
1008
+ # C(2k, 2i + 1) = 0 and C(2k + 1, 2i) = 0
1009
+ # i.e., if n and h have different parity
1010
+ return 0
1011
+ if n == h:
1012
+ return 1
1013
+ if not h and not n % 2:
1014
+ # C(2k, 0) = C(k)
1015
+ return catalan_number(n // 2)
1016
+ # Otherwise, C(k, i) = C(k - 1, i - 1) + C(k - 1, i + 1)
1017
+ return compute_C(n - 1, h - 1) + compute_C(n - 1, h + 1)
1018
+
1019
+ # We first fill an array hx of length n, backward, and then use it to choose
1020
+ # the symbols x_1x_2...x_n (and so symbols y_n...y_2y_1).
1021
+ hx = [0] * n
1022
+ hx[1] = 1
1023
+ # Set hx[np] = h with probability C(np, h) / binomial(np, np // 2)
1024
+ number = randint(0, binomial(np, np // 2))
1025
+ total = 0
1026
+ for h in range(np + 1):
1027
+ total += compute_C(np, h)
1028
+ if number < total:
1029
+ break
1030
+ hx[np] = h
1031
+
1032
+ x = [']']
1033
+ y = ['[']
1034
+ for i in range(np - 1, 0, -1):
1035
+ # Choose symbol x_i
1036
+ if random() < (hx[i + 1] + 2) * (i - hx[i + 1] + 1) / (2 * (i + 1) * (hx[i + 1] + 1)):
1037
+ hx[i] = hx[i + 1] + 1
1038
+ x.append(']')
1039
+ y.append('[')
1040
+ else:
1041
+ hx[i] = hx[i + 1] - 1
1042
+ x.append('[')
1043
+ y.append(']')
1044
+ x.append('[')
1045
+ x.reverse()
1046
+ y.append(']')
1047
+ x.extend(y)
1048
+
1049
+ # We now turn the sequence of symbols to proper intervals.
1050
+ # The i-th intervals starts from the index of the i-th symbol [ in
1051
+ # symbols and ends at the position of the i-th symbol ].
1052
+ intervals = [[0, 2 * n] for _ in range(n)]
1053
+ L = 0 # next starting interval
1054
+ R = 0 # next ending interval
1055
+ for pos, symbol in enumerate(x):
1056
+ if symbol == '[':
1057
+ intervals[L][0] = pos
1058
+ L += 1
1059
+ else:
1060
+ intervals[R][1] = pos
1061
+ R += 1
1062
+
1063
+ # We finally return the resulting interval graph
1064
+ return IntervalGraph(intervals)
1065
+
1066
+
1067
+ # Random Chordal Graphs
1068
+
1069
+ def growing_subtrees(T, k):
1070
+ r"""
1071
+ Return a list of the vertex sets of `n` randomly chosen subtrees of `T`.
1072
+
1073
+ For a tree of order `n`, the collection contains `n` subtrees with maximum
1074
+ order `k` and average order `\frac{k + 1}{2}`.
1075
+
1076
+ This method is part of
1077
+ :meth:`~sage.graphs.generators.random.RandomChordalGraph`.
1078
+
1079
+ ALGORITHM:
1080
+
1081
+ For each subtree `T_i`, the algorithm picks a size `k_i` randomly from
1082
+ `[1,k]`. Then a random node of `T` is chosen as the first node of `T_i`. In
1083
+ each of the subsequent `k_i - 1` iterations, it picks a random node in the
1084
+ neighborhood of `T_i` and adds it to `T_i`.
1085
+
1086
+ See [SHET2018]_ for more details.
1087
+
1088
+ INPUT:
1089
+
1090
+ - ``T`` -- a tree
1091
+
1092
+ - ``k`` -- a strictly positive integer; maximum size of a subtree
1093
+
1094
+ EXAMPLES::
1095
+
1096
+ sage: from sage.graphs.generators.random import growing_subtrees
1097
+ sage: T = graphs.RandomTree(10)
1098
+ sage: S = growing_subtrees(T, 5)
1099
+ sage: len(S)
1100
+ 10
1101
+ """
1102
+ from sage.misc.prandom import choice
1103
+ n = T.order()
1104
+ S = []
1105
+ for _ in range(n):
1106
+ ki = randint(1, k)
1107
+ if ki == n:
1108
+ Vi = frozenset(T)
1109
+ else:
1110
+ x = T.random_vertex()
1111
+ Ti = set([x])
1112
+ neighbors = set(T.neighbor_iterator(x))
1113
+ for j in range(ki - 1):
1114
+ # Select a random neighbor z outside of Ti and add it to Ti
1115
+ z = choice(tuple(neighbors))
1116
+ Ti.add(z)
1117
+ neighbors.update(y for y in T.neighbor_iterator(z) if y not in Ti)
1118
+ Vi = frozenset(Ti)
1119
+ S.append(Vi)
1120
+
1121
+ return S
1122
+
1123
+
1124
+ def connecting_nodes(T, l):
1125
+ r"""
1126
+ Return a list of the vertex sets of `n` randomly chosen subtrees of `T`.
1127
+
1128
+ This method is part of
1129
+ :meth:`~sage.graphs.generators.random.RandomChordalGraph`.
1130
+
1131
+ ALGORITHM:
1132
+
1133
+ For each subtree `T_i`, we first select `k_i` nodes of `T`, where `k_i` is a
1134
+ random integer from a Poisson distribution with mean `l`. `T_i` is then
1135
+ generated to be the minimal subtree that contains the selected `k_i`
1136
+ nodes. This implies that a subtree will most likely have many more nodes
1137
+ than those selected initially, and this must be taken into consideration
1138
+ when choosing `l`.
1139
+
1140
+ See [SHET2018]_ for more details.
1141
+
1142
+ INPUT:
1143
+
1144
+ - ``T`` -- a tree
1145
+
1146
+ - ``l`` -- a strictly positive real number; mean of a Poisson distribution
1147
+
1148
+ EXAMPLES::
1149
+
1150
+ sage: from sage.graphs.generators.random import connecting_nodes
1151
+ sage: T = graphs.RandomTree(10)
1152
+ sage: S = connecting_nodes(T, 5) # needs numpy
1153
+ sage: len(S) # needs numpy
1154
+ 10
1155
+ """
1156
+ from sage.combinat.permutation import Permutations
1157
+ from sage.data_structures.bitset import Bitset
1158
+ from numpy.random import poisson
1159
+
1160
+ n = T.order()
1161
+ V = list(T)
1162
+ P = Permutations(V)
1163
+ active = Bitset(capacity=n)
1164
+
1165
+ # Choose a root
1166
+ root = T.random_vertex()
1167
+
1168
+ # Perform BFS from root and identify parent in root to leaf orientation
1169
+ parent = {root: root}
1170
+ dist = {root: 0}
1171
+ bfs = [root]
1172
+ i = 0
1173
+ while i < n:
1174
+ u = bfs[i]
1175
+ d = dist[u]
1176
+ for v in T.neighbor_iterator(u):
1177
+ if v not in parent:
1178
+ parent[v] = u
1179
+ dist[v] = d + 1
1180
+ bfs.append(v)
1181
+ i += 1
1182
+
1183
+ S = []
1184
+ for _ in range(n):
1185
+ ki = poisson(l)
1186
+ if not ki:
1187
+ ki = 1
1188
+ elif ki >= n:
1189
+ Ti = frozenset(V)
1190
+
1191
+ if ki < n:
1192
+ # Select ki vertices at random
1193
+ Vi = set(P.random_element()[:ki])
1194
+ # Arrange them by distance to root and mark them as active
1195
+ d = max(dist[u] for u in Vi)
1196
+ Li = [set() for _ in range(d + 1)]
1197
+ active.clear()
1198
+ for u in Vi:
1199
+ Li[dist[u]].add(u)
1200
+ active.add(u)
1201
+ # Add to Vi the vertices of a minimal subtree containing Vi.
1202
+ # To do so, add the parents of the vertices at distance d to Vi,
1203
+ # mark them as active and add them to the set of vertices at
1204
+ # distance d - 1. Then mark the vertices at distance d as
1205
+ # inactive. Repeat the same procedure for the vertices at distance
1206
+ # d - 1, d - 2, etc. This procedure ends when at most one active
1207
+ # vertex remains.
1208
+ while len(active) > 1:
1209
+ for u in Li[d]:
1210
+ p = parent[u]
1211
+ Vi.add(p)
1212
+ Li[d - 1].add(p)
1213
+ active.add(p)
1214
+ active.discard(u)
1215
+ d -= 1
1216
+ Ti = frozenset(Vi)
1217
+
1218
+ S.append(Ti)
1219
+
1220
+ return S
1221
+
1222
+
1223
+ def pruned_tree(T, f, s):
1224
+ r"""
1225
+ Return a list of the vertex sets of `n` randomly chosen subtrees of `T`.
1226
+
1227
+ This method is part of
1228
+ :meth:`~sage.graphs.generators.random.RandomChordalGraph`.
1229
+
1230
+ ALGORITHM:
1231
+
1232
+ For each subtree `T_i`, it randomly selects a fraction `f` of the edges on
1233
+ the tree and removes them. The number of edges to delete, say `l`, is
1234
+ calculated as `\lfloor((n - 1)f\rfloor`, which will leave `l + 1` subtrees
1235
+ in total. Then, it determines the sizes of the `l + 1` subtrees and stores
1236
+ the distinct values. Finally, it picks a random size `k_i` from the set of
1237
+ largest `100(1-s)\%` of distinct values, and randomly chooses a subtree with
1238
+ size `k_i`.
1239
+
1240
+ See [SHET2018]_ for more details.
1241
+
1242
+ INPUT:
1243
+
1244
+ - ``T`` -- a tree
1245
+
1246
+ - ``f`` -- a rational number; the edge deletion fraction. This value must be
1247
+ chosen in `[0..1]`
1248
+
1249
+ - ``s`` -- a real number between 0 and 1; selection barrier for the size of
1250
+ trees
1251
+
1252
+ EXAMPLES::
1253
+
1254
+ sage: from sage.graphs.generators.random import pruned_tree
1255
+ sage: T = graphs.RandomTree(11)
1256
+ sage: S = pruned_tree(T, 1/10, 0.5)
1257
+ sage: len(S)
1258
+ 11
1259
+ """
1260
+ n = T.order()
1261
+ ke = int((n - 1) * f)
1262
+ if not ke:
1263
+ # No removed edge. Only one possible subtree
1264
+ return [tuple(T)] * n
1265
+ elif ke == n - 1:
1266
+ # All edges are removed. Only n possible subtrees
1267
+ return [(u,) for u in T]
1268
+
1269
+ random_edge_iterator = T.random_edge_iterator(labels=False)
1270
+ TT = T.copy()
1271
+ S = []
1272
+
1273
+ for _ in range(n):
1274
+ # Choose ke = (n - 1) * f edges and remove them from TT
1275
+ E = set()
1276
+ while len(E) < ke:
1277
+ E.add(next(random_edge_iterator))
1278
+ TT.delete_edges(E)
1279
+
1280
+ # Compute the connected components of TT and arrange them by sizes
1281
+ CC = {}
1282
+ for c in TT.connected_components(sort=False):
1283
+ l = len(c)
1284
+ if l in CC:
1285
+ CC[l].append(c)
1286
+ else:
1287
+ CC[l] = [c]
1288
+
1289
+ # Randomly select a subtree size ki from the highest 100(1 - s) %
1290
+ # subtree sizes
1291
+ sizes = sorted(set(CC.keys()), reverse=True)
1292
+ ki = sizes[randint(0, int(len(sizes) * (1 - s)))]
1293
+
1294
+ # Randomly select a subtree of size ki
1295
+ Ti = frozenset(CC[ki][randint(0, len(CC[ki]) - 1)])
1296
+
1297
+ S.append(Ti)
1298
+
1299
+ TT.add_edges(E)
1300
+
1301
+ return S
1302
+
1303
+
1304
+ def RandomChordalGraph(n, algorithm='growing', k=None, l=None, f=None, s=None, seed=None):
1305
+ r"""
1306
+ Return a random chordal graph of order ``n``.
1307
+
1308
+ A Graph `G` is said to be chordal if it contains no induced hole (a cycle of
1309
+ length at least 4). Equivalently, `G` is chordal if it has a perfect
1310
+ elimination orderings, if each minimal separator is a clique, or if it is
1311
+ the intersection graphs of subtrees of a tree. See the
1312
+ :wikipedia:`Chordal_graph`.
1313
+
1314
+ This generator implements the algorithms proposed in [SHET2018]_ for
1315
+ generating random chordal graphs as the intersection graph of `n` subtrees
1316
+ of a tree of order `n`.
1317
+
1318
+ The returned graph is not necessarily connected.
1319
+
1320
+ INPUT:
1321
+
1322
+ - ``n`` -- integer; the number of nodes of the graph
1323
+
1324
+ - ``algorithm`` -- string (default: ``'growing'``); the choice of the
1325
+ algorithm for randomly selecting `n` subtrees of a random tree of order
1326
+ `n`. Possible choices are:
1327
+
1328
+ - ``'growing'`` -- for each subtree `T_i`, the algorithm picks a size
1329
+ `k_i` randomly from `[1,k]`. Then a random node of `T` is chosen as the
1330
+ first node of `T_i`. In each of the subsequent `k_i - 1` iterations, it
1331
+ picks a random node in the neighborhood of `T_i` and adds it to `T_i`.
1332
+
1333
+ - ``'connecting'`` -- for each subtree `T_i`, it first selects `k_i` nodes
1334
+ of `T`, where `k_i` is a random integer from a Poisson distribution with
1335
+ mean `l`. `T_i` is then generated to be the minimal subtree containing
1336
+ the selected `k_i` nodes. This implies that a subtree will most likely
1337
+ have many more nodes than those selected initially, and this must be
1338
+ taken into consideration when choosing `l`.
1339
+
1340
+ - ``'pruned'`` -- for each subtree `T_i`, it randomly selects a fraction
1341
+ `f` of the edges on the tree and removes them. The number of edges to
1342
+ delete, say `l`, is calculated as `\lfloor (n - 1) f \rfloor`, which will
1343
+ leave `l + 1` subtrees in total. Then, it determines the sizes of the `l
1344
+ + 1` subtrees and stores the distinct values. Finally, it picks a random
1345
+ size `k_i` from the set of largest `100(1-s)\%` of distinct values, and
1346
+ randomly chooses a subtree with size `k_i`.
1347
+
1348
+ - ``k`` -- integer (default: ``None``); maximum size of a subtree. If not
1349
+ specified (``None``), the maximum size is set to `\sqrt{n}`.
1350
+ This parameter is used only when ``algorithm="growing"``. See
1351
+ :meth:`~sage.graphs.generators.random.growing_subtrees` for more details.
1352
+
1353
+ - ``l`` -- a strictly positive real number (default: ``None``); mean of a
1354
+ Poisson distribution. If not specified, the mean in set to `\log_2{n}`.
1355
+ This parameter is used only when ``algorithm="connecting"``. See
1356
+ :meth:`~sage.graphs.generators.random.connecting_nodes` for more details.
1357
+
1358
+ - ``f`` -- a rational number (default: ``None``); the edge deletion
1359
+ fraction. This value must be chosen in `[0..1]`. If not specified, this
1360
+ parameter is set to `\frac{1}{n-1}`.
1361
+ This parameter is used only when ``algorithm="pruned"``.
1362
+ See :meth:`~sage.graphs.generators.random.pruned_tree` for more details.
1363
+
1364
+ - ``s`` -- a real number between 0 and 1 (default: ``None``); selection
1365
+ barrier for the size of trees. If not specified, this parameter is set to
1366
+ `0.5`. This parameter is used only when ``algorithm="pruned"``.
1367
+ See :meth:`~sage.graphs.generators.random.pruned_tree` for more details.
1368
+
1369
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1370
+ number generator (default: ``None``)
1371
+
1372
+ EXAMPLES::
1373
+
1374
+ sage: from sage.graphs.generators.random import RandomChordalGraph
1375
+ sage: T = RandomChordalGraph(20, algorithm='growing', k=5)
1376
+ sage: T.is_chordal()
1377
+ True
1378
+ sage: T = RandomChordalGraph(20, algorithm='connecting', l=3) # needs numpy
1379
+ sage: T.is_chordal() # needs numpy
1380
+ True
1381
+ sage: T = RandomChordalGraph(20, algorithm='pruned', f=1/3, s=.5)
1382
+ sage: T.is_chordal()
1383
+ True
1384
+
1385
+ TESTS::
1386
+
1387
+ sage: from sage.graphs.generators.random import RandomChordalGraph
1388
+ sage: all(RandomChordalGraph(i).is_chordal() for i in range(4))
1389
+ True
1390
+ sage: RandomChordalGraph(3, algorithm="Carmen Cru")
1391
+ Traceback (most recent call last):
1392
+ ...
1393
+ NotImplementedError: unknown algorithm 'Carmen Cru'
1394
+ sage: RandomChordalGraph(3, algorithm='growing', k=0)
1395
+ Traceback (most recent call last):
1396
+ ...
1397
+ ValueError: parameter k must be >= 1
1398
+ sage: RandomChordalGraph(3, algorithm='connecting', l=0)
1399
+ Traceback (most recent call last):
1400
+ ...
1401
+ ValueError: parameter l must be > 0
1402
+ sage: RandomChordalGraph(3, algorithm='pruned', f=2)
1403
+ Traceback (most recent call last):
1404
+ ...
1405
+ ValueError: parameter f must be 0 <= f <= 1
1406
+ sage: RandomChordalGraph(3, algorithm='pruned', s=1)
1407
+ Traceback (most recent call last):
1408
+ ...
1409
+ ValueError: parameter s must be 0 < s < 1
1410
+
1411
+ .. SEEALSO::
1412
+
1413
+ - :meth:`~sage.graphs.generators.random.growing_subtrees`
1414
+ - :meth:`~sage.graphs.generators.random.connecting_nodes`
1415
+ - :meth:`~sage.graphs.generators.random.pruned_tree`
1416
+ - :wikipedia:`Chordal_graph`
1417
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.is_chordal`
1418
+ - :meth:`~sage.graphs.graph_generators.GraphGenerators.IntersectionGraph`
1419
+ """
1420
+ if n < 2:
1421
+ return Graph(n, name="Random Chordal Graph")
1422
+
1423
+ if seed is not None:
1424
+ set_random_seed(seed)
1425
+
1426
+ # 1. Generate a random tree of order n
1427
+ T = RandomTree(n)
1428
+
1429
+ # 2. Generate n non-empty subtrees of T: {T1,...,Tn}
1430
+ if algorithm == "growing":
1431
+ if k is None:
1432
+ from sage.misc.functional import isqrt
1433
+ k = isqrt(n)
1434
+ elif k < 1:
1435
+ raise ValueError("parameter k must be >= 1")
1436
+
1437
+ S = growing_subtrees(T, k)
1438
+
1439
+ elif algorithm == "connecting":
1440
+ if l is None:
1441
+ from sage.rings.integer import Integer
1442
+ l = Integer(n).log(2)
1443
+ elif l <= 0:
1444
+ raise ValueError("parameter l must be > 0")
1445
+
1446
+ S = connecting_nodes(T, l)
1447
+
1448
+ elif algorithm == "pruned":
1449
+ if f is None:
1450
+ from sage.rings.rational import Rational
1451
+ f = 1 / Rational(n - 1)
1452
+ elif f < 0 or f > 1:
1453
+ raise ValueError("parameter f must be 0 <= f <= 1")
1454
+ if s is None:
1455
+ s = .5
1456
+ elif s <= 0 or s >= 1:
1457
+ raise ValueError("parameter s must be 0 < s < 1")
1458
+
1459
+ S = pruned_tree(T, f, s)
1460
+
1461
+ else:
1462
+ raise NotImplementedError("unknown algorithm '{}'".format(algorithm))
1463
+
1464
+ # 3. Build the intersection graph of {V(T1),...,V(Tn)}
1465
+ vertex_to_subtrees = [[] for _ in range(n)]
1466
+ for i, s in enumerate(S):
1467
+ for x in s:
1468
+ vertex_to_subtrees[x].append(i)
1469
+ G = Graph(n, name="Random Chordal Graph")
1470
+ for X in vertex_to_subtrees:
1471
+ G.add_clique(X)
1472
+
1473
+ return G
1474
+
1475
+
1476
+ def RandomLobster(n, p, q, seed=None):
1477
+ r"""
1478
+ Return a random lobster.
1479
+
1480
+ A lobster is a tree that reduces to a caterpillar when pruning all
1481
+ leaf vertices. A caterpillar is a tree that reduces to a path when
1482
+ pruning all leaf vertices (`q=0`).
1483
+
1484
+ INPUT:
1485
+
1486
+ - ``n`` -- expected number of vertices in the backbone
1487
+
1488
+ - ``p`` -- probability of adding an edge to the backbone
1489
+
1490
+ - ``q`` -- probability of adding an edge (claw) to the arms
1491
+
1492
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1493
+ number generator (default: ``None``)
1494
+
1495
+ EXAMPLES:
1496
+
1497
+ We check a random graph with 12 backbone
1498
+ nodes and probabilities `p = 0.7` and `q = 0.3`::
1499
+
1500
+ sage: # needs networkx
1501
+ sage: G = graphs.RandomLobster(12, 0.7, 0.3)
1502
+ sage: leaves = [v for v in G.vertices(sort=False) if G.degree(v) == 1]
1503
+ sage: G.delete_vertices(leaves) # caterpillar
1504
+ sage: leaves = [v for v in G.vertices(sort=False) if G.degree(v) == 1]
1505
+ sage: G.delete_vertices(leaves) # path
1506
+ sage: s = G.degree_sequence()
1507
+ sage: if G:
1508
+ ....: if G.num_verts() == 1:
1509
+ ....: assert s == [0]
1510
+ ....: else:
1511
+ ....: assert s[-2:] == [1, 1]
1512
+ ....: assert all(d == 2 for d in s[:-2])
1513
+
1514
+ ::
1515
+
1516
+ sage: G = graphs.RandomLobster(9, .6, .3) # needs networkx
1517
+ sage: G.show() # long time # needs networkx sage.plot
1518
+ """
1519
+ if seed is None:
1520
+ seed = int(current_randstate().long_seed() % sys.maxsize)
1521
+ import networkx
1522
+ return Graph(networkx.random_lobster(n, p, q, seed=seed))
1523
+
1524
+
1525
+ def RandomTree(n, seed=None):
1526
+ r"""
1527
+ Return a random tree on `n` nodes numbered `0` through `n-1`.
1528
+
1529
+ By Cayley's theorem, there are `n^{n-2}` trees with vertex
1530
+ set `\{0,1,\dots,n-1\}`. This constructor chooses one of these uniformly
1531
+ at random.
1532
+
1533
+ ALGORITHM:
1534
+
1535
+ The algorithm works by generating an `(n-2)`-long
1536
+ random sequence of numbers chosen independently and uniformly
1537
+ from `\{0,1,\dots,n-1\}` and then applies an inverse
1538
+ Prufer transformation.
1539
+
1540
+ INPUT:
1541
+
1542
+ - ``n`` -- number of vertices in the tree
1543
+
1544
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1545
+ number generator (default: ``None``)
1546
+
1547
+ EXAMPLES::
1548
+
1549
+ sage: G = graphs.RandomTree(10)
1550
+ sage: G.is_tree()
1551
+ True
1552
+ sage: G.show() # long time # needs sage.plot
1553
+
1554
+ TESTS:
1555
+
1556
+ Ensuring that we encounter no unexpected surprise ::
1557
+
1558
+ sage: all( graphs.RandomTree(10).is_tree()
1559
+ ....: for i in range(100) )
1560
+ True
1561
+
1562
+ Random tree with one and zero vertices::
1563
+
1564
+ sage: graphs.RandomTree(0)
1565
+ Graph on 0 vertices
1566
+ sage: graphs.RandomTree(1)
1567
+ Graph on 1 vertex
1568
+ """
1569
+ g = Graph(n)
1570
+ if n <= 1:
1571
+ return g
1572
+
1573
+ if seed is not None:
1574
+ set_random_seed(seed)
1575
+
1576
+ # create random Prufer code
1577
+ code = [randint(0, n - 1) for i in range(n - 2)]
1578
+
1579
+ # We count the number of symbols of each type.
1580
+ # count[k] is the number of times k appears in code
1581
+ #
1582
+ # (count[k] is set to -1 when the corresponding vertex is not
1583
+ # available anymore)
1584
+ count = [0] * n
1585
+ for k in code:
1586
+ count[k] += 1
1587
+
1588
+ # We use a heap to store vertices for which count[k] == 0 and get the vertex
1589
+ # with smallest index
1590
+ from heapq import heapify, heappop, heappush
1591
+ zeros = [x for x in range(n) if not count[x]]
1592
+ heapify(zeros)
1593
+
1594
+ for s in code:
1595
+ x = heappop(zeros)
1596
+ g.add_edge(x, s)
1597
+ count[x] = -1
1598
+ count[s] -= 1
1599
+ if not count[s]:
1600
+ heappush(zeros, s)
1601
+
1602
+ # Adding as an edge the last two available vertices
1603
+ g.add_edge(zeros)
1604
+
1605
+ return g
1606
+
1607
+
1608
+ def RandomTreePowerlaw(n, gamma=3, tries=1000, seed=None):
1609
+ """
1610
+ Return a tree with a power law degree distribution, or ``False`` on failure.
1611
+
1612
+ From the NetworkX documentation: a trial power law degree sequence is chosen
1613
+ and then elements are swapped with new elements from a power law
1614
+ distribution until the sequence makes a tree (size = order - 1).
1615
+
1616
+ INPUT:
1617
+
1618
+ - ``n`` -- number of vertices
1619
+
1620
+ - ``gamma`` -- exponent of power law distribution
1621
+
1622
+ - ``tries`` -- number of attempts to adjust sequence to make a tree
1623
+
1624
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1625
+ number generator (default: ``None``)
1626
+
1627
+ EXAMPLES:
1628
+
1629
+ We check that the generated graph is a tree::
1630
+
1631
+ sage: G = graphs.RandomTreePowerlaw(10, 3) # needs networkx
1632
+ sage: G.is_tree() # needs networkx
1633
+ True
1634
+ sage: G.order(), G.size() # needs networkx
1635
+ (10, 9)
1636
+
1637
+ ::
1638
+
1639
+ sage: G = graphs.RandomTreePowerlaw(15, 2) # needs networkx
1640
+ sage: if G: # random output # long time, needs networkx sage.plot
1641
+ ....: G.show()
1642
+ """
1643
+ if seed is None:
1644
+ seed = int(current_randstate().long_seed() % sys.maxsize)
1645
+ import networkx
1646
+ try:
1647
+ return Graph(networkx.random_powerlaw_tree(n, gamma, seed=seed, tries=tries))
1648
+ except networkx.NetworkXError:
1649
+ return False
1650
+
1651
+
1652
+ def RandomKTree(n, k, seed=None):
1653
+ r"""
1654
+ Return a random `k`-tree on `n` nodes numbered `0` through `n-1`.
1655
+
1656
+ ALGORITHM:
1657
+
1658
+ The algorithm first generates a complete graph on `k + 1` vertices.
1659
+ Vertices are subsequently generated by randomly choosing one of the
1660
+ existing cliques in the graph, and creating a new clique by replacing
1661
+ one of the vertices in the selected clique with a newly created one.
1662
+
1663
+ INPUT:
1664
+
1665
+ - ``n`` -- number of vertices in the `k`-tree
1666
+
1667
+ - ``k`` -- within a clique each vertex is connected to `k` vertices. `k`
1668
+ also corresponds to the treewidth of the `k`-tree
1669
+
1670
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1671
+ number generator (default: ``None``)
1672
+
1673
+ TESTS::
1674
+
1675
+ sage: g=graphs.RandomKTree(50,5)
1676
+ sage: g.size()
1677
+ 235
1678
+ sage: g.order()
1679
+ 50
1680
+ sage: g.treewidth() # needs cliquer
1681
+ 5
1682
+ sage: graphs.RandomKTree(-5, 5)
1683
+ Traceback (most recent call last):
1684
+ ...
1685
+ ValueError: n must not be negative
1686
+ sage: graphs.RandomKTree(5, -5)
1687
+ Traceback (most recent call last):
1688
+ ...
1689
+ ValueError: k must not be negative
1690
+ sage: graphs.RandomKTree(2, 5)
1691
+ Traceback (most recent call last):
1692
+ ...
1693
+ ValueError: n must be greater than k
1694
+ sage: G = graphs.RandomKTree(50, 0)
1695
+ sage: G.treewidth()
1696
+ 0
1697
+
1698
+ EXAMPLES::
1699
+
1700
+ sage: G = graphs.RandomKTree(50, 5)
1701
+ sage: G.treewidth() # needs cliquer
1702
+ 5
1703
+ sage: G.show() # not tested
1704
+ """
1705
+ if n < 0:
1706
+ raise ValueError("n must not be negative")
1707
+
1708
+ if k < 0:
1709
+ raise ValueError("k must not be negative")
1710
+
1711
+ # A graph with treewidth 0 has no edges
1712
+ if k == 0:
1713
+ g = Graph(n, name="Random 0-tree")
1714
+ return g
1715
+
1716
+ if n < k + 1:
1717
+ raise ValueError("n must be greater than k")
1718
+
1719
+ if seed is not None:
1720
+ set_random_seed(seed)
1721
+
1722
+ g = Graph(name=f"Random {k}-tree")
1723
+ g.add_clique(list(range(k + 1)))
1724
+
1725
+ cliques = [list(range(k+1))]
1726
+
1727
+ # Randomly choose a row, and copy 1 of the cliques
1728
+ # One of those vertices is then replaced with a new vertex
1729
+ for newVertex in range(k + 1, n):
1730
+ copiedClique = cliques[randint(0, len(cliques)-1)].copy()
1731
+ copiedClique[randint(0, k)] = newVertex
1732
+ cliques.append(copiedClique)
1733
+ for u in copiedClique:
1734
+ if u != newVertex:
1735
+ g.add_edge(u, newVertex)
1736
+ return g
1737
+
1738
+
1739
+ def RandomPartialKTree(n, k, x, seed=None):
1740
+ r"""
1741
+ Return a random partial `k`-tree on `n` nodes.
1742
+
1743
+ A partial `k`-tree is defined as a subgraph of a `k`-tree. This can also be
1744
+ described as a graph with treewidth at most `k`.
1745
+
1746
+ INPUT:
1747
+
1748
+ - ``n`` -- number of vertices in the `k`-tree
1749
+
1750
+ - ``k`` -- within a clique each vertex is connected to `k` vertices. `k`
1751
+ also corresponds to the treewidth of the `k`-tree
1752
+
1753
+ - ``x`` -- how many edges are deleted from the `k`-tree
1754
+
1755
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1756
+ number generator (default: ``None``)
1757
+
1758
+ TESTS::
1759
+
1760
+ sage: g=graphs.RandomPartialKTree(50,5,2)
1761
+ sage: g.order()
1762
+ 50
1763
+ sage: g.size()
1764
+ 233
1765
+ sage: g.treewidth() # needs cliquer
1766
+ 5
1767
+ sage: graphs.RandomPartialKTree(-5, 5, 2)
1768
+ Traceback (most recent call last):
1769
+ ...
1770
+ ValueError: n must not be negative
1771
+ sage: graphs.RandomPartialKTree(5, -5, 2)
1772
+ Traceback (most recent call last):
1773
+ ...
1774
+ ValueError: k must not be negative
1775
+ sage: G = graphs.RandomPartialKTree(2, 5, 2)
1776
+ Traceback (most recent call last):
1777
+ ...
1778
+ ValueError: n must be greater than k
1779
+ sage: G = graphs.RandomPartialKTree(5, 2, 100)
1780
+ Traceback (most recent call last):
1781
+ ...
1782
+ ValueError: x must be less than the number of edges in the `k`-tree with `n` nodes
1783
+ sage: G = graphs.RandomPartialKTree(50, 0, 0)
1784
+ sage: G.treewidth() # needs cliquer
1785
+ 0
1786
+ sage: G = graphs.RandomPartialKTree(5, 2, 7)
1787
+ sage: G.treewidth() # needs cliquer
1788
+ 0
1789
+ sage: G.size()
1790
+ 0
1791
+
1792
+ EXAMPLES::
1793
+
1794
+ sage: G = graphs.RandomPartialKTree(50,5,2)
1795
+ sage: G.treewidth() # needs cliquer
1796
+ 5
1797
+ sage: G.show() # not tested
1798
+ """
1799
+ if n < 0:
1800
+ raise ValueError("n must not be negative")
1801
+
1802
+ if k < 0:
1803
+ raise ValueError("k must not be negative")
1804
+
1805
+ # A graph with treewidth 0 has no edges
1806
+ if k == 0:
1807
+ g = Graph(n, name="Random partial 0-tree")
1808
+ return g
1809
+
1810
+ if n < k + 1:
1811
+ raise ValueError("n must be greater than k")
1812
+
1813
+ if seed is not None:
1814
+ set_random_seed(seed)
1815
+
1816
+ # This formula calculates how many edges are in a `k`-tree with `n` nodes
1817
+ edgesInKTree = (k ^ 2 + k) / 2 + (n - k - 1) * k
1818
+
1819
+ # Check that x doesn't delete too many edges
1820
+ if x > edgesInKTree:
1821
+ raise ValueError("x must be less than the number of edges in the `k`-tree with `n` nodes")
1822
+
1823
+ # The graph will have no edges
1824
+ if x == edgesInKTree:
1825
+ g = Graph(n, name=f"Random partial {k}-tree")
1826
+ return g
1827
+
1828
+ g = RandomKTree(n, k, seed)
1829
+
1830
+ from sage.misc.prandom import shuffle
1831
+
1832
+ edges = list(g.edges())
1833
+ # Deletes x random edges from the graph
1834
+ shuffle(edges)
1835
+ g.delete_edges(edges[:x])
1836
+
1837
+ g.name(f"Random partial {k}-tree")
1838
+ return g
1839
+
1840
+
1841
+ def RandomRegular(d, n, seed=None):
1842
+ r"""
1843
+ Return a random `d`-regular graph on `n` vertices, or ``False`` on failure.
1844
+
1845
+ Since every edge is incident to two vertices, `n\times d` must be even.
1846
+
1847
+ INPUT:
1848
+
1849
+ - ``d`` -- degree
1850
+
1851
+ - ``n`` -- number of vertices
1852
+
1853
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1854
+ number generator (default: ``None``)
1855
+
1856
+ EXAMPLES:
1857
+
1858
+ We check that a random graph with 8 nodes each of degree 3 is 3-regular::
1859
+
1860
+ sage: G = graphs.RandomRegular(3, 8) # needs networkx
1861
+ sage: G.is_regular(k=3) # needs networkx
1862
+ True
1863
+ sage: G.degree_histogram() # needs networkx
1864
+ [0, 0, 0, 8]
1865
+
1866
+ ::
1867
+
1868
+ sage: G = graphs.RandomRegular(3, 20) # needs networkx
1869
+ sage: if G: # random output # long time, needs networkx sage.plot
1870
+ ....: G.show()
1871
+
1872
+ REFERENCES:
1873
+
1874
+ - [KV2003]_
1875
+
1876
+ - [SW1999]_
1877
+ """
1878
+ if seed is None:
1879
+ seed = int(current_randstate().long_seed() % sys.maxsize)
1880
+ import networkx
1881
+ try:
1882
+ N = networkx.random_regular_graph(d, n, seed=seed)
1883
+ if N is False:
1884
+ return False
1885
+ return Graph(N, sparse=True)
1886
+ except Exception:
1887
+ return False
1888
+
1889
+
1890
+ def RandomShell(constructor, seed=None):
1891
+ """
1892
+ Return a random shell graph for the constructor given.
1893
+
1894
+ INPUT:
1895
+
1896
+ - ``constructor`` -- list of 3-tuples `(n, m, d)`, each representing a
1897
+ shell, where:
1898
+
1899
+ - ``n`` -- the number of vertices in the shell
1900
+
1901
+ - ``m`` -- the number of edges in the shell
1902
+
1903
+ - ``d`` -- the ratio of inter (next) shell edges to intra shell edges
1904
+
1905
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1906
+ number generator (default: ``None``)
1907
+
1908
+ EXAMPLES::
1909
+
1910
+ sage: G = graphs.RandomShell([(10,20,0.8),(20,40,0.8)]) # needs networkx
1911
+ sage: G.order(), G.size() # needs networkx
1912
+ (30, 52)
1913
+ sage: G.show() # long time # needs networkx sage.plot
1914
+ """
1915
+ if seed is None:
1916
+ seed = int(current_randstate().long_seed() % sys.maxsize)
1917
+ import networkx
1918
+ return Graph(networkx.random_shell_graph(constructor, seed=seed))
1919
+
1920
+
1921
+ def RandomToleranceGraph(n, seed=None):
1922
+ r"""
1923
+ Return a random tolerance graph.
1924
+
1925
+ The random tolerance graph is built from a random tolerance representation
1926
+ by using the function
1927
+ :meth:`~sage.graphs.generators.intersection.ToleranceGraph`. This
1928
+ representation is a list `((l_0,r_0,t_0), (l_1,r_1,t_1), ...,
1929
+ (l_k,r_k,t_k))` where `k = n-1` and `I_i = (l_i,r_i)` denotes a random
1930
+ interval and `t_i` a random positive value. The width of the representation
1931
+ is limited to `n^2 * 2^n`.
1932
+
1933
+ .. NOTE::
1934
+
1935
+ The vertices are named `0, 1, \cdots, n-1`. The tolerance representation
1936
+ used to create the graph is saved with the graph and can be recovered
1937
+ using :meth:`~sage.graphs.generic_graph.GenericGraph.get_vertex` or
1938
+ :meth:`~sage.graphs.generic_graph.GenericGraph.get_vertices`.
1939
+
1940
+ INPUT:
1941
+
1942
+ - ``n`` -- number of vertices of the random graph
1943
+
1944
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
1945
+ number generator (default: ``None``)
1946
+
1947
+ EXAMPLES:
1948
+
1949
+ Every tolerance graph is perfect. Hence, the chromatic number is equal to
1950
+ the clique number ::
1951
+
1952
+ sage: g = graphs.RandomToleranceGraph(8)
1953
+ sage: g.clique_number() == g.chromatic_number() # needs cliquer
1954
+ True
1955
+
1956
+ TESTS::
1957
+
1958
+ sage: g = graphs.RandomToleranceGraph(-2)
1959
+ Traceback (most recent call last):
1960
+ ...
1961
+ ValueError: the number `n` of vertices must be >= 0
1962
+ """
1963
+ from sage.graphs.generators.intersection import ToleranceGraph
1964
+
1965
+ if n < 0:
1966
+ raise ValueError('the number `n` of vertices must be >= 0')
1967
+ if seed is not None:
1968
+ set_random_seed(seed)
1969
+
1970
+ W = n**2 * 2**n
1971
+
1972
+ tolrep = []
1973
+ for _ in range(n):
1974
+ left = randint(0, W)
1975
+ right = randint(0, W)
1976
+ if left > right:
1977
+ left, right = right, left
1978
+ # The tolerance value must be > 0
1979
+ tolrep.append((left, right, randint(1, W)))
1980
+
1981
+ g = ToleranceGraph(tolrep)
1982
+ g.name("Random tolerance graph")
1983
+ return g
1984
+
1985
+
1986
+ # uniform random triangulation using Schaeffer-Poulalhon algorithm
1987
+
1988
+ def _auxiliary_random_forest_word(n, k):
1989
+ r"""
1990
+ Return a random word used to generate random triangulations.
1991
+
1992
+ INPUT:
1993
+
1994
+ - ``n`` -- integer
1995
+
1996
+ - ``k`` -- integer
1997
+
1998
+ OUTPUT:
1999
+
2000
+ A binary sequence `w` of length `4n+2k-4` with `n` ones, such that any
2001
+ proper prefix `u` of `w` satisfies `3|u|_1 - |u|_0 \geq -2k+4` (where
2002
+ `|u|_1` and `|u|_0` are respectively the number of 1s and 0s in `u`). Those
2003
+ words are the expected input of :func:`_contour_and_graph_from_words`.
2004
+
2005
+ ALGORITHM:
2006
+
2007
+ A random word with these numbers of `0` and `1` plus one additional `0` is
2008
+ chosen. This word is then rotated such the prefix property is fulfilled for
2009
+ each proper prefix and only violated by the final `0` (which is deleted
2010
+ afterwards). There is exactly one such rotation (compare Section 4.3 in
2011
+ [PS2006]_).
2012
+
2013
+ Let us consider a word `w` satisfying the expected conditions. By
2014
+ drawing a step `(1,3)` for each `1` and a step `(1,-1)` for each `0` in
2015
+ `w`, one gets a path starting at height `0`, ending at height `-2k+3`
2016
+ (before removing the final `0`) and staying above (or on) the horizontal
2017
+ line of height `-2k+4` except at the end point.
2018
+
2019
+ Now consider an arbitrary word `w` with `n` ones and `3n+2k-3` zeros. By
2020
+ cutting the word at the first position of minimum height, let us write
2021
+ `w=uv`. One can then see that the word `vu` touches the line of height
2022
+ `-2k+3` only after the last step. Further one can see that this is the only
2023
+ rotation of the word `w` with this property.
2024
+
2025
+ EXAMPLES::
2026
+
2027
+ sage: from sage.graphs.generators.random import _auxiliary_random_forest_word
2028
+ sage: with(seed(94364165)):
2029
+ ....: _auxiliary_random_forest_word(4, 3)
2030
+ ....: _auxiliary_random_forest_word(3, 5)
2031
+ [1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
2032
+ [1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2033
+
2034
+ TESTS::
2035
+
2036
+ sage: def partial_sums(w):
2037
+ ....: steps = {1: 3, 0: -1}
2038
+ ....: curr_sum = 0
2039
+ ....: for x in w:
2040
+ ....: curr_sum += steps[x]
2041
+ ....: yield curr_sum
2042
+
2043
+ sage: for k in range(3,6):
2044
+ ....: for n in range(k, 10):
2045
+ ....: w = _auxiliary_random_forest_word(n, k)
2046
+ ....: assert len(w) == 4*n + 2*k - 4
2047
+ ....: assert w.count(1) == n
2048
+ ....: for partial_sum in partial_sums(w):
2049
+ ....: assert partial_sum >= -2*k + 4
2050
+ """
2051
+ from sage.misc.prandom import shuffle
2052
+ w = [0] * (3*n + 2*k - 3) + [1] * n
2053
+ shuffle(w)
2054
+
2055
+ # Finding the admissible shift
2056
+ partial_sum = 0
2057
+ min_value = 0
2058
+ min_pos = 0
2059
+ for i, x in enumerate(w):
2060
+ if x:
2061
+ partial_sum += 3
2062
+ else:
2063
+ partial_sum -= 1
2064
+ if partial_sum < min_value:
2065
+ min_value = partial_sum
2066
+ min_pos = i
2067
+ return w[min_pos+1:] + w[:min_pos]
2068
+
2069
+
2070
+ def _contour_and_graph_from_words(pendant_word, forest_word):
2071
+ r"""
2072
+ Return the contour word and the graph of inner vertices of the `k`-gonal
2073
+ forest associated with the words ``pendant_word`` and ``forest_word``.
2074
+
2075
+ INPUT:
2076
+
2077
+ - ``pendant_word`` -- a word with `k-1` zeros and `k-3` ones
2078
+
2079
+ - ``forest_word`` -- a word in `0` and `1` as given by
2080
+ :func:`_auxiliary_random_word` with the parameter ``k`` set to the number
2081
+ of zeros in ``pendant_word`` plus `1`
2082
+
2083
+ ``forest_word`` must satisfy the conditions hinted in Proposition 5.4 of
2084
+ [PS2006]_ (see :func:`_auxiliary_random_forest_word`).
2085
+
2086
+ OUTPUT:
2087
+
2088
+ a pair ``(seq, G)`` where:
2089
+
2090
+ - ``seq`` is a sequence of pairs (label, integer) representing the
2091
+ contour walk along the `k`-gonal forest associated with the words
2092
+ ``pendant_word`` and ``forest_word``
2093
+
2094
+ - ``G`` -- the `k`-gonal forest associated with the words ``pendant_word``
2095
+ and ``forest_word``
2096
+
2097
+ The underlying bijection from words to `k`-gonal forests is described in
2098
+ Section 5.1 of [PS2006]_. The ``pendant_word`` corresponds to the factor
2099
+ `\binom{2k-4}{k-3}` in the counting formula of Proposition 5.4 and the
2100
+ ``forest_word`` corresponds to the factor `\frac{2k-3}{3m+2k-3}
2101
+ \binom{4m+2k-4}{m}`.
2102
+
2103
+ In the ``forest_word``, the letter `1` means going away from the root ("up")
2104
+ from an inner vertex to another inner vertex. The letter `0` denotes all
2105
+ other steps of the discovery, i.e. either discovering a leaf vertex or going
2106
+ toward the root ("down").
2107
+
2108
+ Inner vertices are tagged with 'in' and leaves are tagged with
2109
+ 'lf'. Inner vertices are moreover labelled by integers, and leaves
2110
+ by the label of the neighbor inner vertex.
2111
+
2112
+ EXAMPLES::
2113
+
2114
+ sage: from sage.graphs.generators.random import _contour_and_graph_from_words
2115
+ sage: seq, G = _contour_and_graph_from_words([0, 0], [1, 0, 0, 0, 0, 0])
2116
+ sage: seq
2117
+ [('in', 0),
2118
+ ('in', 3),
2119
+ ('lf', 3),
2120
+ ('in', 3),
2121
+ ('lf', 3),
2122
+ ('in', 3),
2123
+ ('in', 0),
2124
+ ('in', 1),
2125
+ ('in', 2)]
2126
+ sage: G
2127
+ Graph on 4 vertices
2128
+
2129
+ sage: from sage.graphs.generators.random import _auxiliary_random_forest_word
2130
+ sage: _, G = _contour_and_graph_from_words([0, 1, 0, 0, 1, 0], _auxiliary_random_forest_word(20, 5)) # random
2131
+ sage: len(G.faces())
2132
+ 2
2133
+
2134
+ sage: longw = [1,1,0,1,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0]
2135
+ sage: _, G = _contour_and_graph_from_words([0, 0], longw)
2136
+ sage: G.get_embedding()
2137
+ {0: [1, 2, 3],
2138
+ 1: [2, 0],
2139
+ 2: [0, 1],
2140
+ 3: [0, 4],
2141
+ 4: [3, 5, 6],
2142
+ 5: [4],
2143
+ 6: [4, 7, 8],
2144
+ 7: [6],
2145
+ 8: [6]}
2146
+ """
2147
+ k = (len(pendant_word) + 4) // 2
2148
+
2149
+ index = 0 # numbering of inner vertices
2150
+ word = [('in', 0)] # the word representing the contour walk
2151
+
2152
+ # start with the outer face, a cycle of length k
2153
+ edges = [[i, (i + 1) % k] for i in range(k)]
2154
+ embedding = {i: [(i + 1) % k, (i - 1 + k) % k] for i in range(k)}
2155
+
2156
+ # add the pendant edges
2157
+ for x in pendant_word:
2158
+ if x:
2159
+ word.extend([('lf', index), ('in', index)])
2160
+ else:
2161
+ index += 1
2162
+ word.append(('in', index))
2163
+
2164
+ # add trees
2165
+ curr_word_pos = 0
2166
+ curr_forest_word_pos = 0
2167
+ while curr_forest_word_pos < len(forest_word):
2168
+ x = forest_word[curr_forest_word_pos]
2169
+ # insert a tree at current position
2170
+ if x:
2171
+ index += 1
2172
+ embedding[index] = [word[curr_word_pos][1]]
2173
+ embedding[word[curr_word_pos][1]].append(index)
2174
+ edges.append([word[curr_word_pos][1], index])
2175
+ # stack of leaves still to be created
2176
+ leaf_stack = [index, index]
2177
+ # stack of active inner nodes
2178
+ inner_stack = [word[curr_word_pos][1], index]
2179
+ word.insert(curr_word_pos+1, ('in', index))
2180
+ curr_word_pos += 1
2181
+ while len(inner_stack) > 1:
2182
+ curr_forest_word_pos += 1
2183
+ x = forest_word[curr_forest_word_pos]
2184
+ if x:
2185
+ index += 1
2186
+ embedding[index] = inner_stack[-1:]
2187
+ embedding[inner_stack[-1]].append(index)
2188
+ leaf_stack.extend([index, index])
2189
+ inner_stack.append(index)
2190
+ edges.append(inner_stack[-2:])
2191
+ word.insert(curr_word_pos+1, ('in', index))
2192
+ curr_word_pos += 1
2193
+ else:
2194
+ # up and down to a new leaf
2195
+ if leaf_stack and inner_stack[-1] == leaf_stack[-1]:
2196
+ leaf_stack.pop()
2197
+ word.insert(curr_word_pos+1, ('lf', inner_stack[-1]))
2198
+ word.insert(curr_word_pos+2, ('in', inner_stack[-1]))
2199
+ curr_word_pos += 2
2200
+ # going down to a known inner vertex
2201
+ else:
2202
+ inner_stack.pop()
2203
+ word.insert(curr_word_pos+1, ('in', inner_stack[-1]))
2204
+ curr_word_pos += 1
2205
+ # go to next insertion position
2206
+ else:
2207
+ curr_word_pos += 1
2208
+ if word[curr_word_pos][0] == 'lf':
2209
+ curr_word_pos += 1
2210
+ curr_forest_word_pos += 1
2211
+
2212
+ G = Graph(edges, format='list_of_edges')
2213
+ G.set_embedding(embedding)
2214
+ return word, G
2215
+
2216
+
2217
+ def RandomTriangulation(n, set_position=False, k=3, seed=None):
2218
+ r"""
2219
+ Return a random inner triangulation of an outer face of degree ``k`` with
2220
+ ``n`` vertices in total.
2221
+
2222
+ An inner triangulation is a plane graph all of whose faces (except the
2223
+ outer/unbounded face) are triangles (3-cycles).
2224
+
2225
+ INPUT:
2226
+
2227
+ - ``n`` -- the number of vertices of the graph
2228
+
2229
+ - ``k`` -- the size of the outer face
2230
+
2231
+ - ``set_position`` -- boolean (default: ``False``); if set to ``True``, this
2232
+ will compute coordinates for a planar drawing of the graph
2233
+
2234
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
2235
+ number generator (default: ``None``)
2236
+
2237
+ OUTPUT:
2238
+
2239
+ A random graph chosen uniformly among the inner triangulations of a *rooted*
2240
+ `k`-gon with `n` vertices (including the `k` vertices from the outer face).
2241
+ This is a planar graph and comes with a combinatorial embedding. The
2242
+ vertices of the root edge are labelled ``-1`` and ``-2`` and the outer face
2243
+ is the face returned by :meth:`Graph.faces` in which ``-1`` and ``-2`` are
2244
+ consecutive vertices in this order.
2245
+
2246
+ Because some triangulations have nontrivial automorphism
2247
+ groups, this may not be equal to the uniform distribution among inner
2248
+ triangulations of unrooted `k`-gons.
2249
+
2250
+ ALGORITHM:
2251
+
2252
+ The algorithm is taken from [PS2006]_, Section 5.
2253
+
2254
+ Starting from a planar `k`-gonal forest (represented by its contour as a
2255
+ sequence of vertices), one performs local closures, until no
2256
+ one is possible. A local closure amounts to replace in the cyclic
2257
+ contour word a sequence ``in1, in2, in3, lf, in3`` by
2258
+ ``in1, in3``.
2259
+
2260
+ At every step of the algorithm, newly created edges are recorded
2261
+ in a graph, which will be returned at the end.
2262
+ The combinatorial embedding is also computed and recorded in the
2263
+ output graph.
2264
+
2265
+ .. SEEALSO::
2266
+
2267
+ :meth:`~sage.graphs.graph_generators.GraphGenerators.triangulations`,
2268
+ :func:`~sage.topology.simplicial_complex_examples.RandomTwoSphere`.
2269
+
2270
+ EXAMPLES::
2271
+
2272
+ sage: G = graphs.RandomTriangulation(6, True); G
2273
+ Graph on 6 vertices
2274
+ sage: G.is_planar() # needs planarity
2275
+ True
2276
+ sage: G.girth()
2277
+ 3
2278
+ sage: G.plot(vertex_size=0, vertex_labels=False) # needs sage.plot
2279
+ Graphics object consisting of 13 graphics primitives
2280
+
2281
+ sage: H = graphs.RandomTriangulation(7, k=5)
2282
+ sage: sorted(len(f) for f in H.faces())
2283
+ [3, 3, 3, 3, 3, 3, 3, 5]
2284
+
2285
+ TESTS::
2286
+
2287
+ sage: G.get_embedding() is not None
2288
+ True
2289
+
2290
+ sage: graphs.RandomTriangulation(3, k=4)
2291
+ Traceback (most recent call last):
2292
+ ...
2293
+ ValueError: The number 'n' of vertices must be at least the size 'k' of the outer face.
2294
+ sage: graphs.RandomTriangulation(3, k=2)
2295
+ Traceback (most recent call last):
2296
+ ...
2297
+ ValueError: The size 'k' of the outer face must be at least 3.
2298
+
2299
+ sage: # needs planarity
2300
+ sage: for i in range(10):
2301
+ ....: g = graphs.RandomTriangulation(30) # random
2302
+ ....: assert g.is_planar()
2303
+ sage: for k in range(3, 10):
2304
+ ....: g = graphs.RandomTriangulation(10, k=k) # random
2305
+ ....: assert g.is_planar(on_embedding=g.get_embedding())
2306
+ """
2307
+ if k < 3:
2308
+ raise ValueError("The size 'k' of the outer face must be at least 3.")
2309
+ if n < k:
2310
+ raise ValueError("The number 'n' of vertices must be at least the size "
2311
+ "'k' of the outer face.")
2312
+ if seed is not None:
2313
+ set_random_seed(seed)
2314
+
2315
+ from sage.misc.prandom import shuffle
2316
+ pendant_word = [0] * (k-1) + [1] * (k-3)
2317
+ shuffle(pendant_word)
2318
+ forest_word = _auxiliary_random_forest_word(n-k, k)
2319
+ word, graph = _contour_and_graph_from_words(pendant_word, forest_word)
2320
+ edges = []
2321
+ embedding = graph.get_embedding()
2322
+
2323
+ pattern = ['in', 'in', 'in', 'lf', 'in'] # 'partial closures'
2324
+
2325
+ # We greedily perform the replacements 'in1,in2,in3,lf,in3'->'in1,in3'.
2326
+ while True:
2327
+ # first we rotate the word to it starts with pattern
2328
+ word2 = []
2329
+ N = len(word)
2330
+ for i in range(N):
2331
+ if all(word[(i + j) % N][0] == pattern[j] for j in range(5)):
2332
+ word2 = word[i:] + word[:i]
2333
+ break
2334
+
2335
+ if len(word2) >= 5:
2336
+ word = [word2[0]] + word2[4:]
2337
+ in1, in2, in3 = (u[1] for u in word2[:3])
2338
+ edges.append([in1, in3]) # edge 'in1,in3'
2339
+ idx = embedding[in1].index(in2)
2340
+ embedding[in1].insert(idx, in3)
2341
+ idx = embedding[in3].index(in2)
2342
+ embedding[in3].insert(idx + 1, in1)
2343
+ else:
2344
+ break
2345
+
2346
+ graph.add_edges(edges)
2347
+ graph.set_embedding(embedding)
2348
+ graph.relabel({0: -2, 1: -1})
2349
+ assert graph.num_edges() == 3*n - 3 - k
2350
+ assert graph.num_verts() == n
2351
+ if set_position:
2352
+ graph.layout(layout='planar', save_pos=True)
2353
+ return graph
2354
+
2355
+
2356
+ def blossoming_contour(t, shift=0, seed=None):
2357
+ """
2358
+ Return a random blossoming of a binary tree `t`, as a contour word.
2359
+
2360
+ This is doing several things simultaneously:
2361
+
2362
+ - complete the binary tree, by adding leaves labelled ``xb``,
2363
+ - add a vertex labelled ``n`` at the middle of every inner
2364
+ edge, with a leaf labelled ``x`` either on the left or on the
2365
+ right (at random),
2366
+ - number all vertices (but not leaves) by integers starting from `shift`,
2367
+ - compute the counter-clockwise contour word of the result.
2368
+
2369
+ Initial vertices receive the label ``i``.
2370
+
2371
+ This is an auxiliary function, used for the generation of random
2372
+ planar bicubic maps.
2373
+
2374
+ INPUT:
2375
+
2376
+ - ``t`` -- a binary tree (non-empty)
2377
+
2378
+ - ``shift`` -- integer (default: `0`); used as a starting index
2379
+
2380
+ OUTPUT: contour word of a random blossoming of `t`
2381
+
2382
+ EXAMPLES::
2383
+
2384
+ sage: from sage.graphs.generators.random import blossoming_contour
2385
+ sage: print(blossoming_contour(BinaryTrees(1).an_element()))
2386
+ [('i', 0), ('xb',), ('i', 0), ('xb',), ('i', 0)]
2387
+
2388
+ sage: t = BinaryTrees(2).random_element() # needs sage.combinat
2389
+ sage: print(blossoming_contour(t)) # random # needs sage.combinat
2390
+ [('i', 0), ('xb',), ('i', 0), ('n', 2), ('i', 1), ('xb',), ('i', 1),
2391
+ ('xb',), ('i', 1), ('n', 2), ('x',), ('n', 2), ('i', 0)]
2392
+
2393
+ sage: w = blossoming_contour(BinaryTrees(3).random_element()); len(w) # needs sage.combinat
2394
+ 21
2395
+ sage: w.count(('xb',)) # needs sage.combinat
2396
+ 4
2397
+ sage: w.count(('x',)) # needs sage.combinat
2398
+ 2
2399
+
2400
+ TESTS::
2401
+
2402
+ sage: from sage.graphs.generators.random import blossoming_contour
2403
+ sage: blossoming_contour(BinaryTrees(0).an_element())
2404
+ Traceback (most recent call last):
2405
+ ...
2406
+ ValueError: tree must be non-empty
2407
+ """
2408
+ if not t:
2409
+ raise ValueError('tree must be non-empty')
2410
+ if seed is not None:
2411
+ set_random_seed(seed)
2412
+
2413
+ t1, t2 = t
2414
+ leaf_xb = ('xb',)
2415
+ leaf_x = ('x',)
2416
+ n1 = t1.node_number()
2417
+ n = t.node_number()
2418
+
2419
+ # adding buds on edges in t1
2420
+ if not t1:
2421
+ tt1 = [leaf_xb]
2422
+ elif randint(0, 1):
2423
+ label1 = ('n', shift)
2424
+ tt1 = [label1, leaf_x, label1] + blossoming_contour(t1, shift + 1)
2425
+ tt1 += [label1]
2426
+ else:
2427
+ label1 = ('n', shift + 2 * n1 - 1)
2428
+ tt1 = [label1] + blossoming_contour(t1, shift)
2429
+ tt1 += [label1, leaf_x, label1]
2430
+
2431
+ # adding buds on edges in t2
2432
+ if not t2:
2433
+ tt2 = [leaf_xb]
2434
+ elif randint(0, 1):
2435
+ label2 = ('n', shift + 2 * n1 + 1)
2436
+ tt2 = [label2, leaf_x, label2]
2437
+ tt2 += blossoming_contour(t2, shift + 2 * n1 + 2) + [label2]
2438
+ else:
2439
+ label2 = ('n', shift + 2 * n - 2)
2440
+ tt2 = [label2] + blossoming_contour(t2, shift + 2 * n1 + 1)
2441
+ tt2 += [label2, leaf_x, label2]
2442
+
2443
+ label = [('i', shift + 2 * n1)]
2444
+ return label + tt1 + label + tt2 + label
2445
+
2446
+
2447
+ def RandomBicubicPlanar(n, seed=None):
2448
+ """
2449
+ Return the graph of a random bipartite cubic map with `3 n` edges.
2450
+
2451
+ INPUT:
2452
+
2453
+ - ``n`` -- integer (at least `1`)
2454
+
2455
+ - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
2456
+ number generator (default: ``None``)
2457
+
2458
+ OUTPUT:
2459
+
2460
+ a graph with multiple edges (no embedding is provided)
2461
+
2462
+ The algorithm used is described in [Sch1999]_. This samples
2463
+ a random rooted bipartite cubic map, chosen uniformly at random.
2464
+
2465
+ First one creates a random binary tree with `n` vertices. Next one
2466
+ turns this into a blossoming tree (at random) and reads the
2467
+ contour word of this blossoming tree.
2468
+
2469
+ Then one performs a rotation on this word so that this becomes a
2470
+ balanced word. There are three ways to do that, one is picked at
2471
+ random. Then a graph is build from the balanced word by iterated
2472
+ closure (adding edges).
2473
+
2474
+ In the returned graph, the three edges incident to any given
2475
+ vertex are colored by the integers 0, 1 and 2.
2476
+
2477
+ .. SEEALSO:: the auxiliary method :func:`blossoming_contour`
2478
+
2479
+ EXAMPLES::
2480
+
2481
+ sage: # needs sage.combinat
2482
+ sage: n = randint(200, 300)
2483
+ sage: G = graphs.RandomBicubicPlanar(n)
2484
+ sage: G.order() == 2*n
2485
+ True
2486
+ sage: G.size() == 3*n
2487
+ True
2488
+ sage: G.is_bipartite() and G.is_planar() and G.is_regular(3) # needs planarity
2489
+ True
2490
+ sage: dic = {'red': [v for v in G.vertices(sort=False) if v[0] == 'n'],
2491
+ ....: 'blue': [v for v in G.vertices(sort=False) if v[0] != 'n']}
2492
+ sage: G.plot(vertex_labels=False, vertex_size=20, vertex_colors=dic) # needs sage.plot
2493
+ Graphics object consisting of ... graphics primitives
2494
+
2495
+ .. PLOT::
2496
+ :width: 300 px
2497
+
2498
+ G = graphs.RandomBicubicPlanar(200)
2499
+ V0 = [v for v in G.vertices(sort=False) if v[0] == 'n']
2500
+ V1 = [v for v in G.vertices(sort=False) if v[0] != 'n']
2501
+ dic = {'red': V0, 'blue': V1}
2502
+ sphinx_plot(G.plot(vertex_labels=False,vertex_colors=dic))
2503
+ """
2504
+ from sage.combinat.binary_tree import BinaryTrees
2505
+ from sage.rings.finite_rings.integer_mod_ring import Zmod
2506
+ if not n:
2507
+ raise ValueError("n must be at least 1")
2508
+ if seed is not None:
2509
+ set_random_seed(seed)
2510
+
2511
+ # first pick a random binary tree
2512
+ t = BinaryTrees(n).random_element()
2513
+
2514
+ # next pick a random blossoming of this tree, compute its contour
2515
+ contour = blossoming_contour(t) + [('xb',)] # adding the final xb
2516
+
2517
+ # first step : rotate the contour word to one of 3 balanced
2518
+ N = len(contour)
2519
+ double_contour = contour + contour
2520
+ pile = []
2521
+ not_touched = [i for i in range(N) if contour[i][0] in ['x', 'xb']]
2522
+ for i, w in enumerate(double_contour):
2523
+ if w[0] == 'x' and i < N:
2524
+ pile.append(i)
2525
+ elif w[0] == 'xb' and (i % N) in not_touched:
2526
+ if pile:
2527
+ j = pile.pop()
2528
+ not_touched.remove(i % N)
2529
+ not_touched.remove(j)
2530
+
2531
+ # random choice among 3 possibilities for a balanced word
2532
+ idx = not_touched[randint(0, 2)]
2533
+ w = contour[idx + 1:] + contour[:idx + 1]
2534
+
2535
+ # second step : create the graph by closure from the balanced word
2536
+ G = Graph(multiedges=True)
2537
+
2538
+ pile = []
2539
+ Z3 = Zmod(3)
2540
+ colour = Z3.zero()
2541
+ not_touched = [i for i, v in enumerate(w) if v[0] in ['x', 'xb']]
2542
+ for i, wi in enumerate(w):
2543
+ # internal edges
2544
+ if wi[0] == 'i':
2545
+ colour += 1
2546
+ if w[i + 1][0] == 'n':
2547
+ G.add_edge((wi, w[i + 1], colour))
2548
+ elif wi[0] == 'n':
2549
+ colour += 2
2550
+ elif wi[0] == 'x':
2551
+ pile.append(i)
2552
+ elif wi[0] == 'xb' and i in not_touched:
2553
+ if pile:
2554
+ j = pile.pop()
2555
+ G.add_edge((w[i + 1], w[j - 1], colour))
2556
+ not_touched.remove(i)
2557
+ not_touched.remove(j)
2558
+
2559
+ # there remains to add three edges to elements of "not_touched"
2560
+ # from a new vertex labelled "n"
2561
+ for i in not_touched:
2562
+ taken_colours = [edge[2] for edge in G.edges_incident(w[i - 1])]
2563
+ colour = [u for u in Z3 if u not in taken_colours][0]
2564
+ G.add_edge((('n', -1), w[i - 1], colour))
2565
+
2566
+ return G
2567
+
2568
+
2569
+ def RandomUnitDiskGraph(n, radius=.1, side=1, seed=None):
2570
+ r"""
2571
+ Return a random unit disk graph of order `n`.
2572
+
2573
+ A unit disk graph is the intersection graph of a family of unit disks in the
2574
+ Euclidean plane. That is a graph with one vertex per disk of the family and
2575
+ an edge between two vertices whenever they lie within a unit distance of
2576
+ each other. See the :wikipedia:`Unit_disk_graph` for more details.
2577
+
2578
+ INPUT:
2579
+
2580
+ - ``n`` -- number of nodes
2581
+
2582
+ - ``radius`` -- float (default: `0.1`); two vertices at distance less than
2583
+ ``radius`` are connected by an edge
2584
+
2585
+ - ``side`` -- float (default: ``1``); indicate the side of the area in which
2586
+ the points are drawn
2587
+
2588
+ - ``seed`` -- seed of the random number generator
2589
+
2590
+ EXAMPLES:
2591
+
2592
+ When using twice the same seed, the vertices get the same positions::
2593
+
2594
+ sage: # needs scipy
2595
+ sage: from sage.misc.randstate import current_randstate
2596
+ sage: seed = current_randstate().seed()
2597
+ sage: G = graphs.RandomUnitDiskGraph(20, radius=.5, side=1, seed=seed)
2598
+ sage: H = graphs.RandomUnitDiskGraph(20, radius=.2, side=1, seed=seed)
2599
+ sage: H.is_subgraph(G, induced=False)
2600
+ True
2601
+ sage: H.size() <= G.size()
2602
+ True
2603
+ sage: Gpos = G.get_pos()
2604
+ sage: Hpos = H.get_pos()
2605
+ sage: all(Gpos[u] == Hpos[u] for u in G)
2606
+ True
2607
+
2608
+ When the radius is more than `\sqrt{2 \text{side}}`, the graph is a clique::
2609
+
2610
+ sage: G = graphs.RandomUnitDiskGraph(10, radius=2, side=1) # needs scipy
2611
+ sage: G.is_clique() # needs scipy
2612
+ True
2613
+ """
2614
+ if seed is not None:
2615
+ set_random_seed(seed)
2616
+ from scipy.spatial import KDTree
2617
+ points = [(side*random(), side*random()) for i in range(n)]
2618
+ T = KDTree(points)
2619
+ adj = {i: [u for u in T.query_ball_point([points[i]], radius).item() if u != i]
2620
+ for i in range(n)}
2621
+ return Graph(adj, format='dict_of_lists',
2622
+ pos={i: points[i] for i in range(n)},
2623
+ name="Random unit disk graph")