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.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- 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")
|