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