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
sage/graphs/schnyder.py
ADDED
@@ -0,0 +1,865 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# sage.doctest: needs planarity
|
3
|
+
"""
|
4
|
+
Schnyder's algorithm for straight-line planar embeddings
|
5
|
+
|
6
|
+
A module for computing the (x,y) coordinates for a straight-line planar
|
7
|
+
embedding of any connected planar graph with at least three vertices. Uses
|
8
|
+
Walter Schnyder's Algorithm from [Sch1990]_.
|
9
|
+
|
10
|
+
AUTHORS:
|
11
|
+
|
12
|
+
- Jonathan Bober, Emily Kirkman (2008-02-09) -- initial version
|
13
|
+
"""
|
14
|
+
# ****************************************************************************
|
15
|
+
# Copyright (C) 2008 Jonathan Bober and Emily Kirkman
|
16
|
+
#
|
17
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
18
|
+
# https://www.gnu.org/licenses/
|
19
|
+
# ****************************************************************************
|
20
|
+
|
21
|
+
from sage.sets.set import Set
|
22
|
+
from .all import DiGraph
|
23
|
+
|
24
|
+
|
25
|
+
def _triangulate(g, comb_emb):
|
26
|
+
"""
|
27
|
+
Helper function to schnyder method for computing coordinates in the plane to
|
28
|
+
plot a planar graph with no edge crossings.
|
29
|
+
|
30
|
+
Given a connected graph g with at least 3 vertices and a planar combinatorial
|
31
|
+
embedding comb_emb of g, modify g in place to form a graph whose faces are
|
32
|
+
all triangles, and return the set of newly created edges. Also ``comb_emb``
|
33
|
+
is updated in place.
|
34
|
+
|
35
|
+
The simple way to triangulate a face is to just pick a vertex and draw
|
36
|
+
an edge from that vertex to every other vertex in the face. Think that this
|
37
|
+
might ultimately result in graphs that don't look very nice when we draw them
|
38
|
+
so we have decided on a different strategy. After handling special cases,
|
39
|
+
we add an edge to connect every third vertex on each face. If this edge is
|
40
|
+
a repeat, we keep the first edge of the face and retry the process at the next
|
41
|
+
edge in the face list. (By removing the special cases we guarantee that this
|
42
|
+
method will work on one of these attempts.)
|
43
|
+
|
44
|
+
INPUT:
|
45
|
+
|
46
|
+
- ``g`` -- the graph to triangulate
|
47
|
+
- ``comb_emb`` -- a planar combinatorial embedding of g
|
48
|
+
|
49
|
+
OUTPUT: a list of edges that are added to the graph (in place)
|
50
|
+
|
51
|
+
EXAMPLES::
|
52
|
+
|
53
|
+
sage: from sage.graphs.schnyder import _triangulate
|
54
|
+
sage: g = Graph(graphs.CycleGraph(4))
|
55
|
+
sage: g.is_planar(set_embedding=True)
|
56
|
+
True
|
57
|
+
sage: _triangulate(g, g._embedding)
|
58
|
+
[(2, 0), (1, 3)]
|
59
|
+
|
60
|
+
sage: g = graphs.PathGraph(3)
|
61
|
+
sage: g.is_planar(set_embedding=True)
|
62
|
+
True
|
63
|
+
sage: new_edges = _triangulate(g, g._embedding)
|
64
|
+
sage: [sorted(e) for e in new_edges]
|
65
|
+
[[0, 2]]
|
66
|
+
|
67
|
+
TESTS:
|
68
|
+
|
69
|
+
:issue:`29522` is fixed::
|
70
|
+
|
71
|
+
sage: g = Graph(2)
|
72
|
+
sage: _triangulate(g, {})
|
73
|
+
Traceback (most recent call last):
|
74
|
+
...
|
75
|
+
NotImplementedError: _triangulate() only knows how to handle connected graphs
|
76
|
+
sage: g = Graph([(0, 1)])
|
77
|
+
sage: _triangulate(g, {})
|
78
|
+
Traceback (most recent call last):
|
79
|
+
...
|
80
|
+
ValueError: a Graph with less than 3 vertices doesn't have any triangulation
|
81
|
+
sage: g = Graph(3)
|
82
|
+
sage: _triangulate(g, {})
|
83
|
+
Traceback (most recent call last):
|
84
|
+
...
|
85
|
+
NotImplementedError: _triangulate() only knows how to handle connected graphs
|
86
|
+
"""
|
87
|
+
# first make sure that the graph has at least 3 vertices, and that it is connected
|
88
|
+
if not g.is_connected():
|
89
|
+
raise NotImplementedError("_triangulate() only knows how to handle connected graphs")
|
90
|
+
if g.order() < 3:
|
91
|
+
raise ValueError("a Graph with less than 3 vertices doesn't have any triangulation")
|
92
|
+
|
93
|
+
# At this point we know that the graph is connected, has at least 3
|
94
|
+
# vertices. This is where the real work starts.
|
95
|
+
|
96
|
+
faces = g.faces(comb_emb)
|
97
|
+
# We start by finding all of the faces of this embedding.
|
98
|
+
|
99
|
+
edges_added = [] # The list of edges that we add to the graph.
|
100
|
+
# This will be returned at the end.
|
101
|
+
|
102
|
+
for face in faces:
|
103
|
+
new_face = []
|
104
|
+
if len(face) < 3:
|
105
|
+
raise RuntimeError('Triangulate method created face %s with < 3 edges.' % face)
|
106
|
+
if len(face) == 3:
|
107
|
+
continue # This face is already triangulated
|
108
|
+
elif len(face) == 4: # In this special case just add diagonal edge to square
|
109
|
+
u, v, w, x = (e[0] for e in face)
|
110
|
+
if w == u or g.has_edge(w, u):
|
111
|
+
u, v, w, x = v, w, x, u
|
112
|
+
new_face = (w, u)
|
113
|
+
comb_emb[w].insert(comb_emb[w].index(x), u)
|
114
|
+
comb_emb[u].insert(comb_emb[u].index(v), w)
|
115
|
+
g.add_edge(new_face)
|
116
|
+
edges_added.append(new_face)
|
117
|
+
else:
|
118
|
+
N = len(face)
|
119
|
+
i = 0
|
120
|
+
while i < N - 1:
|
121
|
+
new_edge = (face[i + 1][1], face[i][0]) # new_edge is from third vertex in face to first
|
122
|
+
if g.has_edge(new_edge) or new_edge[0] == new_edge[1]: # check for repeats
|
123
|
+
new_face.append(face[i]) # if repeated, keep first edge in face instead
|
124
|
+
if i == N - 2: # if we are two from the end, found a triangle already
|
125
|
+
break
|
126
|
+
i += 1
|
127
|
+
continue
|
128
|
+
|
129
|
+
g.add_edge(new_edge)
|
130
|
+
edges_added.append(new_edge)
|
131
|
+
comb_emb[new_edge[0]].insert(comb_emb[new_edge[0]].index((face + new_face)[i + 2][1]), new_edge[1])
|
132
|
+
comb_emb[new_edge[1]].insert(comb_emb[new_edge[1]].index(face[i][1]), new_edge[0])
|
133
|
+
new_face.append((new_edge[1], new_edge[0]))
|
134
|
+
i += 2
|
135
|
+
if i != N:
|
136
|
+
new_face.append(face[-1])
|
137
|
+
faces.append(new_face)
|
138
|
+
|
139
|
+
return edges_added
|
140
|
+
|
141
|
+
|
142
|
+
def _normal_label(g, comb_emb, external_face):
|
143
|
+
r"""
|
144
|
+
Helper function to schnyder method for computing coordinates in
|
145
|
+
the plane to plot a planar graph with no edge crossings.
|
146
|
+
|
147
|
+
Constructs a normal labelling of a triangular graph g, given the
|
148
|
+
planar combinatorial embedding of g and a designated external
|
149
|
+
face. Returns labels dictionary. The normal label is constructed
|
150
|
+
by first contracting the graph down to its external face, then
|
151
|
+
expanding the graph back to the original while simultaneously
|
152
|
+
adding angle labels.
|
153
|
+
|
154
|
+
INPUT:
|
155
|
+
|
156
|
+
- ``g`` -- the graph to find the normal labeling of (g must be triangulated)
|
157
|
+
- ``comb_emb`` -- a planar combinatorial embedding of g
|
158
|
+
- ``external_face`` -- the list of three edges in the external face of g
|
159
|
+
|
160
|
+
OUTPUT: x; tuple with entries
|
161
|
+
|
162
|
+
x[0] = dict of dicts of normal labeling for each vertex of g and each
|
163
|
+
adjacent neighbors u,v (u < v) of vertex:
|
164
|
+
|
165
|
+
{ vertex : { (u,v): angel_label } }
|
166
|
+
|
167
|
+
x[1] = (v1,v2,v3) tuple of the three vertices of the external face.
|
168
|
+
|
169
|
+
EXAMPLES::
|
170
|
+
|
171
|
+
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer
|
172
|
+
sage: g = Graph(graphs.CycleGraph(7))
|
173
|
+
sage: g.is_planar(set_embedding=True)
|
174
|
+
True
|
175
|
+
sage: faces = g.faces(g._embedding)
|
176
|
+
sage: _triangulate(g, g._embedding)
|
177
|
+
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
|
178
|
+
sage: tn = _normal_label(g, g._embedding, faces[0])
|
179
|
+
sage: _realizer(g, tn)
|
180
|
+
({0: [<sage.graphs.schnyder.TreeNode object at ...>]},
|
181
|
+
(1, 0, 2))
|
182
|
+
"""
|
183
|
+
contracted = []
|
184
|
+
contractible = []
|
185
|
+
|
186
|
+
labels = {}
|
187
|
+
|
188
|
+
# For now we will not take the order of the outer face into account.
|
189
|
+
# We will correct this in the end of this function.
|
190
|
+
external_vertices = sorted([external_face[0][0],
|
191
|
+
external_face[1][0],
|
192
|
+
external_face[2][0]])
|
193
|
+
v1, v2, v3 = external_vertices
|
194
|
+
v1_neighbors = Set(g.neighbors(v1))
|
195
|
+
|
196
|
+
neighbor_count = {v: 0 for v in g}
|
197
|
+
for v in g.neighbors(v1):
|
198
|
+
neighbor_count[v] = len(v1_neighbors.intersection(Set(g.neighbors(v))))
|
199
|
+
|
200
|
+
for v in v1_neighbors:
|
201
|
+
if v in [v1, v2, v3]:
|
202
|
+
continue
|
203
|
+
if neighbor_count[v] == 2:
|
204
|
+
contractible.append(v)
|
205
|
+
|
206
|
+
# contraction phase:
|
207
|
+
|
208
|
+
while g.order() > 3:
|
209
|
+
try:
|
210
|
+
v = contractible.pop()
|
211
|
+
except Exception:
|
212
|
+
raise RuntimeError('Contractible list is empty but graph still has %d vertices. (Expected 3.)' % g.order())
|
213
|
+
|
214
|
+
# going to contract v
|
215
|
+
v_neighbors = Set(g.neighbors(v))
|
216
|
+
contracted.append((v, v_neighbors,
|
217
|
+
v_neighbors - v1_neighbors - Set([v1])))
|
218
|
+
g.delete_vertex(v)
|
219
|
+
v1_neighbors -= Set([v])
|
220
|
+
for w in v_neighbors - v1_neighbors - Set([v1]):
|
221
|
+
# adding edge (v1, w)
|
222
|
+
g.add_edge((v1, w))
|
223
|
+
if g.order() == 3:
|
224
|
+
break
|
225
|
+
v1_neighbors += v_neighbors - Set([v1])
|
226
|
+
contractible = []
|
227
|
+
for w in g.neighbors(v1):
|
228
|
+
if (len(v1_neighbors.intersection(Set(g.neighbors(w)))) == 2
|
229
|
+
and w not in [v1, v2, v3]):
|
230
|
+
contractible.append(w)
|
231
|
+
|
232
|
+
# expansion phase:
|
233
|
+
|
234
|
+
v1, v2, v3 = g.vertices(sort=True) # always in sorted order
|
235
|
+
|
236
|
+
labels[v1] = {(v2, v3): 1}
|
237
|
+
labels[v2] = {(v1, v3): 2}
|
238
|
+
labels[v3] = {(v1, v2): 3}
|
239
|
+
|
240
|
+
while contracted:
|
241
|
+
v, new_neighbors, neighbors_to_delete = contracted.pop()
|
242
|
+
# going to add back vertex v
|
243
|
+
labels[v] = {}
|
244
|
+
|
245
|
+
for w in neighbors_to_delete:
|
246
|
+
g.delete_edge((v1, w))
|
247
|
+
|
248
|
+
if len(neighbors_to_delete) == 0:
|
249
|
+
# we are adding v into the face new_neighbors
|
250
|
+
w1, w2, w3 = sorted(new_neighbors)
|
251
|
+
|
252
|
+
labels[v] = {(w1, w2): labels[w3].pop((w1, w2)),
|
253
|
+
(w2, w3): labels[w1].pop((w2, w3)),
|
254
|
+
(w1, w3): labels[w2].pop((w1, w3))}
|
255
|
+
labels[w1][tuple(sorted((w2, v)))] = labels[v][(w2, w3)]
|
256
|
+
labels[w1][tuple(sorted((w3, v)))] = labels[v][(w2, w3)]
|
257
|
+
|
258
|
+
labels[w2][tuple(sorted((w1, v)))] = labels[v][(w1, w3)]
|
259
|
+
labels[w2][tuple(sorted((w3, v)))] = labels[v][(w1, w3)]
|
260
|
+
|
261
|
+
labels[w3][tuple(sorted((w1, v)))] = labels[v][(w1, w2)]
|
262
|
+
labels[w3][tuple(sorted((w2, v)))] = labels[v][(w1, w2)]
|
263
|
+
else:
|
264
|
+
new_neighbors_set = Set(new_neighbors)
|
265
|
+
angles_out_of_v1 = set()
|
266
|
+
vertices_in_order = []
|
267
|
+
l = []
|
268
|
+
for angle in labels[v1].keys():
|
269
|
+
if len(Set(angle).intersection(new_neighbors_set)) == 2:
|
270
|
+
angles_out_of_v1.add(angle)
|
271
|
+
l = l + list(angle)
|
272
|
+
# find a unique element in l
|
273
|
+
l.sort()
|
274
|
+
i = 0
|
275
|
+
while i < len(l):
|
276
|
+
if l[i] == l[i + 1]:
|
277
|
+
i += 2
|
278
|
+
else:
|
279
|
+
break
|
280
|
+
|
281
|
+
angle_set = Set(angles_out_of_v1)
|
282
|
+
|
283
|
+
vertices_in_order.append(l[i])
|
284
|
+
while angles_out_of_v1:
|
285
|
+
for angle in angles_out_of_v1:
|
286
|
+
if vertices_in_order[-1] in angle:
|
287
|
+
break
|
288
|
+
if angle[0] == vertices_in_order[-1]:
|
289
|
+
vertices_in_order.append(angle[1])
|
290
|
+
else:
|
291
|
+
vertices_in_order.append(angle[0])
|
292
|
+
angles_out_of_v1.remove(angle)
|
293
|
+
|
294
|
+
w = vertices_in_order
|
295
|
+
|
296
|
+
# is w[0] a 2 or a 3?
|
297
|
+
top_label = labels[w[0]][tuple(sorted((v1, w[1])))]
|
298
|
+
if top_label == 3:
|
299
|
+
bottom_label = 2
|
300
|
+
else:
|
301
|
+
bottom_label = 3
|
302
|
+
i = 0
|
303
|
+
while i < len(w) - 1:
|
304
|
+
labels[v][tuple(sorted((w[i], w[i + 1])))] = 1
|
305
|
+
labels[w[i]][tuple(sorted((w[i + 1], v)))] = top_label
|
306
|
+
labels[w[i + 1]][tuple(sorted((w[i], v)))] = bottom_label
|
307
|
+
i += 1
|
308
|
+
|
309
|
+
labels[v][tuple(sorted((v1, w[0])))] = bottom_label
|
310
|
+
labels[v][tuple(sorted((v1, w[-1])))] = top_label
|
311
|
+
|
312
|
+
labels[w[0]][tuple(sorted((v1, v)))] = top_label
|
313
|
+
labels[w[-1]][tuple(sorted((v1, v)))] = bottom_label
|
314
|
+
labels[v1][tuple(sorted((w[0], v)))] = 1
|
315
|
+
labels[v1][tuple(sorted((w[-1], v)))] = 1
|
316
|
+
|
317
|
+
# delete all the extra labels
|
318
|
+
|
319
|
+
for angle in angle_set:
|
320
|
+
labels[v1].pop(angle)
|
321
|
+
|
322
|
+
labels[w[0]].pop(tuple(sorted((v1, w[1]))))
|
323
|
+
labels[w[-1]].pop(tuple(sorted((v1, w[-2]))))
|
324
|
+
|
325
|
+
i = 1
|
326
|
+
while i < len(w) - 1:
|
327
|
+
labels[w[i]].pop(tuple(sorted((v1, w[i + 1]))))
|
328
|
+
labels[w[i]].pop(tuple(sorted((v1, w[i - 1]))))
|
329
|
+
i += 1
|
330
|
+
|
331
|
+
for w in new_neighbors:
|
332
|
+
g.add_edge((v, w))
|
333
|
+
|
334
|
+
# Up to this point we did not take the order of the external face into
|
335
|
+
# account. Since the combinatorial embedding of a triangulation is unique up
|
336
|
+
# to the choice of the outer face and reflection, this might lead to a
|
337
|
+
# reflection of the Schnyder drawing resulting from this labeling which is
|
338
|
+
# not conformal with comb_emb any longer. Therefore, we might have to swap
|
339
|
+
# the labels 1 and 2.
|
340
|
+
if (v1, v2) in external_face:
|
341
|
+
for u in labels:
|
342
|
+
for v, w in labels[u]:
|
343
|
+
if labels[u][v, w] == 1:
|
344
|
+
labels[u][v, w] = 2
|
345
|
+
elif labels[u][v, w] == 2:
|
346
|
+
labels[u][v, w] = 1
|
347
|
+
v1, v2 = v2, v1
|
348
|
+
|
349
|
+
return labels, (v1, v2, v3)
|
350
|
+
|
351
|
+
|
352
|
+
def _realizer(g, x, example=False):
|
353
|
+
"""
|
354
|
+
Given a triangulated graph g and a normal labeling constructs the
|
355
|
+
realizer and returns a dictionary of three trees determined by the
|
356
|
+
realizer, each spanning all interior vertices and rooted at one of
|
357
|
+
the three external vertices.
|
358
|
+
|
359
|
+
A realizer is a directed graph with edge labels that span all interior
|
360
|
+
vertices from each external vertex. It is determined by giving direction
|
361
|
+
to the edges that have the same angle label on both sides at a vertex.
|
362
|
+
(Thus the direction actually points to the parent in the tree.) The
|
363
|
+
edge label is set as whatever the matching angle label is. Then from
|
364
|
+
any interior vertex, following the directed edges by label will
|
365
|
+
give a path to each of the three external vertices.
|
366
|
+
|
367
|
+
INPUT:
|
368
|
+
|
369
|
+
- ``g`` -- the graph to compute the realizer of
|
370
|
+
- ``x`` -- tuple with entries
|
371
|
+
|
372
|
+
x[0] = dict of dicts representing a normal labeling of g. For
|
373
|
+
each vertex of g and each adjacent neighbors u,v (u < v) of
|
374
|
+
vertex: { vertex : { (u,v): angle_label } }
|
375
|
+
|
376
|
+
x[1] = (v1, v2, v3) tuple of the three external vertices (also
|
377
|
+
the roots of each tree)
|
378
|
+
|
379
|
+
OUTPUT: x; tuple with entries
|
380
|
+
|
381
|
+
x[0] = dict of lists of TreeNodes:
|
382
|
+
|
383
|
+
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
|
384
|
+
|
385
|
+
x[1] = (v1,v2,v3) tuple of the three external vertices (also the
|
386
|
+
roots of each tree)
|
387
|
+
|
388
|
+
EXAMPLES::
|
389
|
+
|
390
|
+
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer
|
391
|
+
sage: g = Graph(graphs.CycleGraph(7))
|
392
|
+
sage: g.is_planar(set_embedding=True)
|
393
|
+
True
|
394
|
+
sage: faces = g.faces(g._embedding)
|
395
|
+
sage: _triangulate(g, g._embedding)
|
396
|
+
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
|
397
|
+
sage: tn = _normal_label(g, g._embedding, faces[0])
|
398
|
+
sage: _realizer(g, tn)
|
399
|
+
({0: [<sage.graphs.schnyder.TreeNode object at ...>]},
|
400
|
+
(1, 0, 2))
|
401
|
+
"""
|
402
|
+
normal_labeling, (v1, v2, v3) = x
|
403
|
+
realizer = DiGraph()
|
404
|
+
|
405
|
+
tree_nodes = {}
|
406
|
+
for v in g:
|
407
|
+
tree_nodes[v] = [TreeNode(label=v, children=[]),
|
408
|
+
TreeNode(label=v, children=[]),
|
409
|
+
TreeNode(label=v, children=[])]
|
410
|
+
|
411
|
+
for v in g:
|
412
|
+
ones = []
|
413
|
+
twos = []
|
414
|
+
threes = []
|
415
|
+
l = [ones, twos, threes]
|
416
|
+
for angle, value in normal_labeling[v].items():
|
417
|
+
l[value - 1] += list(angle)
|
418
|
+
|
419
|
+
ones.sort()
|
420
|
+
twos.sort()
|
421
|
+
threes.sort()
|
422
|
+
|
423
|
+
i = 0
|
424
|
+
while i < len(ones) - 1:
|
425
|
+
if ones[i] == ones[i + 1]:
|
426
|
+
realizer.add_edge((ones[i], v), label=1)
|
427
|
+
tree_nodes[v][0].append_child(tree_nodes[ones[i]][0])
|
428
|
+
i += 1
|
429
|
+
i += 1
|
430
|
+
i = 0
|
431
|
+
while i < len(twos) - 1:
|
432
|
+
if twos[i] == twos[i + 1]:
|
433
|
+
realizer.add_edge((twos[i], v), label=2)
|
434
|
+
tree_nodes[v][1].append_child(tree_nodes[twos[i]][1])
|
435
|
+
i += 1
|
436
|
+
i += 1
|
437
|
+
i = 0
|
438
|
+
while i < len(threes) - 1:
|
439
|
+
if threes[i] == threes[i + 1]:
|
440
|
+
realizer.add_edge((threes[i], v), label=3)
|
441
|
+
tree_nodes[v][2].append_child(tree_nodes[threes[i]][2])
|
442
|
+
i += 1
|
443
|
+
i += 1
|
444
|
+
|
445
|
+
_compute_coordinates(realizer, (tree_nodes, (v1, v2, v3)))
|
446
|
+
|
447
|
+
if example:
|
448
|
+
realizer.show(talk=True, edge_labels=True)
|
449
|
+
|
450
|
+
return tree_nodes, (v1, v2, v3)
|
451
|
+
|
452
|
+
|
453
|
+
def _compute_coordinates(g, x):
|
454
|
+
r"""
|
455
|
+
Given a triangulated graph g with a dict of trees given by the
|
456
|
+
realizer and tuple of the external vertices, we compute the
|
457
|
+
coordinates of a planar geometric embedding in the grid.
|
458
|
+
|
459
|
+
The coordinates will be set to the ``_pos`` attribute of g.
|
460
|
+
|
461
|
+
INPUT:
|
462
|
+
|
463
|
+
- ``g`` -- the graph to compute the coordinates of
|
464
|
+
- ``x`` -- tuple with entries
|
465
|
+
|
466
|
+
x[0] = dict of tree nodes for the three trees with each external
|
467
|
+
vertex as root:
|
468
|
+
|
469
|
+
{ root_vertex : [ list of all TreeNodes under root_vertex ] }
|
470
|
+
|
471
|
+
x[1] = (v1, v2, v3) tuple of the three external vertices (also
|
472
|
+
the roots of each tree)
|
473
|
+
|
474
|
+
EXAMPLES::
|
475
|
+
|
476
|
+
sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer, _compute_coordinates
|
477
|
+
sage: g = Graph(graphs.CycleGraph(7))
|
478
|
+
sage: g.is_planar(set_embedding=True)
|
479
|
+
True
|
480
|
+
sage: faces = g.faces(g._embedding)
|
481
|
+
sage: _triangulate(g, g._embedding)
|
482
|
+
[(2, 0), (4, 2), (6, 4), (5, 0), (3, 5), (1, 3), (4, 0), (3, 0)]
|
483
|
+
sage: tn = _normal_label(g, g._embedding, faces[0])
|
484
|
+
sage: r = _realizer(g, tn)
|
485
|
+
sage: _compute_coordinates(g,r)
|
486
|
+
sage: g.get_pos()
|
487
|
+
{0: [0, 5], 1: [5, 1], 2: [1, 0], 3: [4, 1], 4: [1, 1], 5: [2, 2], 6: [1, 2]}
|
488
|
+
"""
|
489
|
+
|
490
|
+
tree_nodes, (v1, v2, v3) = x
|
491
|
+
# find the roots of each tree:
|
492
|
+
t1, t2, t3 = tree_nodes[v1][0], tree_nodes[v2][1], tree_nodes[v3][2]
|
493
|
+
|
494
|
+
# Compute the number of descendants and depth of each node in
|
495
|
+
# each tree.
|
496
|
+
t1.compute_number_of_descendants()
|
497
|
+
t2.compute_number_of_descendants()
|
498
|
+
t3.compute_number_of_descendants()
|
499
|
+
|
500
|
+
t1.compute_depth_of_self_and_children()
|
501
|
+
t2.compute_depth_of_self_and_children()
|
502
|
+
t3.compute_depth_of_self_and_children()
|
503
|
+
|
504
|
+
coordinates = {} # the dict to pass to g.set_pos()
|
505
|
+
|
506
|
+
# Setting coordinates for external vertices
|
507
|
+
coordinates[t1.label] = [g.order() - 2, 1]
|
508
|
+
coordinates[t2.label] = [0, g.order() - 2]
|
509
|
+
coordinates[t3.label] = [1, 0]
|
510
|
+
|
511
|
+
for v in g.vertices(sort=False):
|
512
|
+
if v not in [t1.label, t2.label, t3.label]:
|
513
|
+
# Computing coordinates for v
|
514
|
+
r = [0, 0, 0]
|
515
|
+
|
516
|
+
for i in [0, 1, 2]:
|
517
|
+
# Computing size of region i:
|
518
|
+
|
519
|
+
# Tracing up tree (i + 1) % 3
|
520
|
+
p = tree_nodes[v][(i + 1) % 3]
|
521
|
+
while p is not None:
|
522
|
+
q = tree_nodes[p.label][i].number_of_descendants
|
523
|
+
# Adding number of descendants from Tree i nodes with
|
524
|
+
# labels on path up tree (i + 1) % 3
|
525
|
+
r[i] += q
|
526
|
+
p = p.parent
|
527
|
+
|
528
|
+
# Tracing up tree (i - 1) % 3
|
529
|
+
p = tree_nodes[v][(i - 1) % 3]
|
530
|
+
while p is not None:
|
531
|
+
q = tree_nodes[p.label][i].number_of_descendants
|
532
|
+
# Adding number of descendants from Tree i nodes with
|
533
|
+
# labels on path up tree (i - 1) % 3
|
534
|
+
r[i] += q
|
535
|
+
p = p.parent
|
536
|
+
|
537
|
+
q = tree_nodes[v][i].number_of_descendants
|
538
|
+
# Subtracting
|
539
|
+
r[i] -= q
|
540
|
+
|
541
|
+
# Subtracting
|
542
|
+
q = tree_nodes[v][(i - 1) % 3].depth
|
543
|
+
r[i] -= q
|
544
|
+
|
545
|
+
if sum(r) != g.order() - 1:
|
546
|
+
raise RuntimeError("Computing coordinates failed: vertex %s's coordinates sum to %s. Expected %s" % (v, sum(r), g.order() - 1))
|
547
|
+
|
548
|
+
coordinates[v] = r[:-1]
|
549
|
+
|
550
|
+
g.set_pos(coordinates) # Setting _pos attribute to store coordinates
|
551
|
+
|
552
|
+
|
553
|
+
class TreeNode:
|
554
|
+
"""
|
555
|
+
A class to represent each node in the trees used by ``_realizer`` and
|
556
|
+
``_compute_coordinates`` when finding a planar geometric embedding in
|
557
|
+
the grid.
|
558
|
+
|
559
|
+
Each tree node is doubly linked to its parent and children.
|
560
|
+
|
561
|
+
INPUT:
|
562
|
+
|
563
|
+
- ``parent`` -- the parent TreeNode of ``self``
|
564
|
+
- ``children`` -- list of TreeNode children of ``self``
|
565
|
+
- ``label`` -- the associated realizer vertex label
|
566
|
+
|
567
|
+
EXAMPLES::
|
568
|
+
|
569
|
+
sage: from sage.graphs.schnyder import TreeNode
|
570
|
+
sage: tn = TreeNode(label=5)
|
571
|
+
sage: tn2 = TreeNode(label=2,parent=tn)
|
572
|
+
sage: tn3 = TreeNode(label=3)
|
573
|
+
sage: tn.append_child(tn3)
|
574
|
+
sage: tn.compute_number_of_descendants()
|
575
|
+
2
|
576
|
+
sage: tn.number_of_descendants
|
577
|
+
2
|
578
|
+
sage: tn3.number_of_descendants
|
579
|
+
1
|
580
|
+
sage: tn.compute_depth_of_self_and_children()
|
581
|
+
sage: tn3.depth
|
582
|
+
2
|
583
|
+
"""
|
584
|
+
def __init__(self, parent=None, children=None, label=None):
|
585
|
+
"""
|
586
|
+
INPUT:
|
587
|
+
|
588
|
+
- ``parent`` -- the parent TreeNode of ``self``
|
589
|
+
- ``children`` -- list of TreeNode children of ``self``
|
590
|
+
- ``label`` -- the associated realizer vertex label
|
591
|
+
|
592
|
+
EXAMPLES::
|
593
|
+
|
594
|
+
sage: from sage.graphs.schnyder import TreeNode
|
595
|
+
sage: tn = TreeNode(label=5)
|
596
|
+
sage: tn2 = TreeNode(label=2,parent=tn)
|
597
|
+
sage: tn3 = TreeNode(label=3)
|
598
|
+
sage: tn.append_child(tn3)
|
599
|
+
sage: tn.compute_number_of_descendants()
|
600
|
+
2
|
601
|
+
sage: tn.number_of_descendants
|
602
|
+
2
|
603
|
+
sage: tn3.number_of_descendants
|
604
|
+
1
|
605
|
+
sage: tn.compute_depth_of_self_and_children()
|
606
|
+
sage: tn3.depth
|
607
|
+
2
|
608
|
+
"""
|
609
|
+
if children is None:
|
610
|
+
children = []
|
611
|
+
self.parent = parent
|
612
|
+
self.children = children
|
613
|
+
self.label = label
|
614
|
+
self.number_of_descendants = 1
|
615
|
+
|
616
|
+
def compute_number_of_descendants(self):
|
617
|
+
"""
|
618
|
+
Compute the number of descendants of ``self`` and all descendants.
|
619
|
+
|
620
|
+
For each TreeNode, sets result as attribute ``self.number_of_descendants``.
|
621
|
+
|
622
|
+
EXAMPLES::
|
623
|
+
|
624
|
+
sage: from sage.graphs.schnyder import TreeNode
|
625
|
+
sage: tn = TreeNode(label=5)
|
626
|
+
sage: tn2 = TreeNode(label=2,parent=tn)
|
627
|
+
sage: tn3 = TreeNode(label=3)
|
628
|
+
sage: tn.append_child(tn3)
|
629
|
+
sage: tn.compute_number_of_descendants()
|
630
|
+
2
|
631
|
+
sage: tn.number_of_descendants
|
632
|
+
2
|
633
|
+
sage: tn3.number_of_descendants
|
634
|
+
1
|
635
|
+
sage: tn.compute_depth_of_self_and_children()
|
636
|
+
sage: tn3.depth
|
637
|
+
2
|
638
|
+
"""
|
639
|
+
n = 1
|
640
|
+
for child in self.children:
|
641
|
+
n += child.compute_number_of_descendants()
|
642
|
+
self.number_of_descendants = n
|
643
|
+
return n
|
644
|
+
|
645
|
+
def compute_depth_of_self_and_children(self):
|
646
|
+
"""
|
647
|
+
Compute the depth of ``self`` and all descendants.
|
648
|
+
|
649
|
+
For each TreeNode, sets result as ``attribute self.depth``.
|
650
|
+
|
651
|
+
EXAMPLES::
|
652
|
+
|
653
|
+
sage: from sage.graphs.schnyder import TreeNode
|
654
|
+
sage: tn = TreeNode(label=5)
|
655
|
+
sage: tn2 = TreeNode(label=2,parent=tn)
|
656
|
+
sage: tn3 = TreeNode(label=3)
|
657
|
+
sage: tn.append_child(tn3)
|
658
|
+
sage: tn.compute_number_of_descendants()
|
659
|
+
2
|
660
|
+
sage: tn.number_of_descendants
|
661
|
+
2
|
662
|
+
sage: tn3.number_of_descendants
|
663
|
+
1
|
664
|
+
sage: tn.compute_depth_of_self_and_children()
|
665
|
+
sage: tn3.depth
|
666
|
+
2
|
667
|
+
"""
|
668
|
+
if self.parent is None:
|
669
|
+
self.depth = 1
|
670
|
+
else:
|
671
|
+
self.depth = self.parent.depth + 1
|
672
|
+
for child in self.children:
|
673
|
+
child.compute_depth_of_self_and_children()
|
674
|
+
|
675
|
+
def append_child(self, child):
|
676
|
+
"""
|
677
|
+
Add a child to list of children.
|
678
|
+
|
679
|
+
EXAMPLES::
|
680
|
+
|
681
|
+
sage: from sage.graphs.schnyder import TreeNode
|
682
|
+
sage: tn = TreeNode(label=5)
|
683
|
+
sage: tn2 = TreeNode(label=2,parent=tn)
|
684
|
+
sage: tn3 = TreeNode(label=3)
|
685
|
+
sage: tn.append_child(tn3)
|
686
|
+
sage: tn.compute_number_of_descendants()
|
687
|
+
2
|
688
|
+
sage: tn.number_of_descendants
|
689
|
+
2
|
690
|
+
sage: tn3.number_of_descendants
|
691
|
+
1
|
692
|
+
sage: tn.compute_depth_of_self_and_children()
|
693
|
+
sage: tn3.depth
|
694
|
+
2
|
695
|
+
"""
|
696
|
+
if child in self.children:
|
697
|
+
return
|
698
|
+
self.children.append(child)
|
699
|
+
child.parent = self
|
700
|
+
|
701
|
+
|
702
|
+
def minimal_schnyder_wood(graph, root_edge=None, minimal=True, check=True):
|
703
|
+
"""
|
704
|
+
Return the minimal Schnyder wood of a planar rooted triangulation.
|
705
|
+
|
706
|
+
INPUT:
|
707
|
+
|
708
|
+
- ``graph`` -- a planar triangulation, given by a graph with an embedding
|
709
|
+
|
710
|
+
- ``root_edge`` -- a pair of vertices (default: from ``-1`` to ``-2``);
|
711
|
+
the third boundary vertex is then determined using the orientation and
|
712
|
+
will be labelled ``-3``
|
713
|
+
|
714
|
+
- ``minimal`` -- boolean (default: ``True``); whether to return a
|
715
|
+
minimal or a maximal Schnyder wood
|
716
|
+
|
717
|
+
- ``check`` -- boolean (default: ``True``); whether to check if the input
|
718
|
+
is a planar triangulation
|
719
|
+
|
720
|
+
OUTPUT:
|
721
|
+
|
722
|
+
A planar graph, with edges oriented and colored. The three outer
|
723
|
+
edges of the initial graph are removed. For the three outer vertices the
|
724
|
+
list of the neighbors stored in the combinatorial embedding is in the order
|
725
|
+
of the incident edges between the two incident (and removed) outer edges,
|
726
|
+
and not a cyclic shift of it.
|
727
|
+
|
728
|
+
The algorithm is taken from [Bre2000]_ (section 4.2).
|
729
|
+
|
730
|
+
EXAMPLES::
|
731
|
+
|
732
|
+
sage: from sage.graphs.schnyder import minimal_schnyder_wood
|
733
|
+
sage: g = Graph([(0,-1),(0,-2),(0,-3),(-1,-2),(-2,-3),
|
734
|
+
....: (-3,-1)], format='list_of_edges')
|
735
|
+
sage: g.set_embedding({-1:[-2,0,-3],-2:[-3,0,-1],
|
736
|
+
....: -3:[-1,0,-2],0:[-1,-2,-3]})
|
737
|
+
sage: newg = minimal_schnyder_wood(g)
|
738
|
+
sage: newg.edges(sort=True)
|
739
|
+
[(0, -3, 'red'), (0, -2, 'blue'), (0, -1, 'green')]
|
740
|
+
sage: newg.plot(color_by_label={'red':'red','blue':'blue', # needs sage.plot
|
741
|
+
....: 'green':'green',None:'black'})
|
742
|
+
Graphics object consisting of 8 graphics primitives
|
743
|
+
|
744
|
+
A larger example::
|
745
|
+
|
746
|
+
sage: g = Graph([(0,-1),(0,2),(0,1),(0,-3),(-1,-3),(-1,2),
|
747
|
+
....: (-1,-2),(1,2),(1,-3),(2,-2),(1,-2),(-2,-3)], format='list_of_edges')
|
748
|
+
sage: g.set_embedding({-1:[-2,2,0,-3],-2:[-3,1,2,-1],
|
749
|
+
....: -3:[-1,0,1,-2],0:[-1,2,1,-3],1:[-2,-3,0,2],2:[-1,-2,1,0]})
|
750
|
+
sage: newg = minimal_schnyder_wood(g)
|
751
|
+
sage: newg.edges(sort=True, key=lambda e:(str(e[0]),str(e[1])))
|
752
|
+
[(0, -1, 'green'),
|
753
|
+
(0, -3, 'red'),
|
754
|
+
(0, 2, 'blue'),
|
755
|
+
(1, -2, 'blue'),
|
756
|
+
(1, -3, 'red'),
|
757
|
+
(1, 0, 'green'),
|
758
|
+
(2, -1, 'green'),
|
759
|
+
(2, -2, 'blue'),
|
760
|
+
(2, 1, 'red')]
|
761
|
+
sage: newg2 = minimal_schnyder_wood(g, minimal=False)
|
762
|
+
sage: newg2.edges(sort=True, key=lambda e:(str(e[0]),str(e[1])))
|
763
|
+
[(0, -1, 'green'),
|
764
|
+
(0, -3, 'red'),
|
765
|
+
(0, 1, 'blue'),
|
766
|
+
(1, -2, 'blue'),
|
767
|
+
(1, -3, 'red'),
|
768
|
+
(1, 2, 'green'),
|
769
|
+
(2, -1, 'green'),
|
770
|
+
(2, -2, 'blue'),
|
771
|
+
(2, 0, 'red')]
|
772
|
+
|
773
|
+
TESTS::
|
774
|
+
|
775
|
+
sage: minimal_schnyder_wood(graphs.RandomTriangulation(5))
|
776
|
+
Digraph on 5 vertices
|
777
|
+
sage: minimal_schnyder_wood(graphs.CompleteGraph(5))
|
778
|
+
Traceback (most recent call last):
|
779
|
+
...
|
780
|
+
ValueError: not a planar graph
|
781
|
+
sage: minimal_schnyder_wood(graphs.WheelGraph(5))
|
782
|
+
Traceback (most recent call last):
|
783
|
+
...
|
784
|
+
ValueError: not a triangulation
|
785
|
+
sage: minimal_schnyder_wood(graphs.OctahedralGraph(),root_edge=(0,5))
|
786
|
+
Traceback (most recent call last):
|
787
|
+
...
|
788
|
+
ValueError: not a valid root edge
|
789
|
+
"""
|
790
|
+
if root_edge is None:
|
791
|
+
a = -1
|
792
|
+
b = -2
|
793
|
+
else:
|
794
|
+
a, b = root_edge
|
795
|
+
|
796
|
+
if check:
|
797
|
+
if not graph.is_planar():
|
798
|
+
raise ValueError('not a planar graph')
|
799
|
+
if not all(len(u) == 3 for u in graph.faces()):
|
800
|
+
raise ValueError('not a triangulation')
|
801
|
+
if a not in graph.neighbors(b):
|
802
|
+
raise ValueError('not a valid root edge')
|
803
|
+
|
804
|
+
new_g = DiGraph()
|
805
|
+
emb = graph.get_embedding()
|
806
|
+
|
807
|
+
# finding the third outer vertex c
|
808
|
+
emb_b = emb[b]
|
809
|
+
idx_a = emb_b.index(a)
|
810
|
+
c = emb_b[(idx_a + 1) % len(emb_b)]
|
811
|
+
|
812
|
+
# initialisation
|
813
|
+
for i in emb[c]:
|
814
|
+
if i != a and i != b:
|
815
|
+
new_g.add_edge((i, -3, 'red'))
|
816
|
+
|
817
|
+
path = list(emb[c])
|
818
|
+
idxa = path.index(a)
|
819
|
+
path = path[idxa:] + path[:idxa]
|
820
|
+
neighbors_in_path = {i: len([u for u in graph.neighbors(i) if u in path])
|
821
|
+
for i in graph}
|
822
|
+
removable_nodes = [u for u in path if neighbors_in_path[u] == 2 and
|
823
|
+
u != a and u != b]
|
824
|
+
|
825
|
+
# iterated path shortening
|
826
|
+
while len(path) > 2:
|
827
|
+
if minimal:
|
828
|
+
v = removable_nodes[-1] # node to be removed from path
|
829
|
+
else:
|
830
|
+
v = removable_nodes[0] # node to be removed from path
|
831
|
+
idx_v = path.index(v)
|
832
|
+
left = path[idx_v - 1]
|
833
|
+
new_g.add_edge((v, left, 'green'))
|
834
|
+
right = path[idx_v + 1]
|
835
|
+
new_g.add_edge((v, right, 'blue'))
|
836
|
+
neighbors_v = emb[v]
|
837
|
+
idx_left = neighbors_v.index(left)
|
838
|
+
neighbors_v = neighbors_v[idx_left:] + neighbors_v[:idx_left]
|
839
|
+
idx_right = neighbors_v.index(right)
|
840
|
+
inside = neighbors_v[1:idx_right]
|
841
|
+
new_g.add_edges([(w, v, 'red') for w in inside])
|
842
|
+
path = path[:idx_v] + inside + path[idx_v + 1:]
|
843
|
+
# updating the table of neighbors_in_path
|
844
|
+
for w in inside:
|
845
|
+
for x in graph.neighbors(w):
|
846
|
+
neighbors_in_path[x] += 1
|
847
|
+
for x in graph.neighbors(v):
|
848
|
+
neighbors_in_path[x] -= 1
|
849
|
+
# updating removable nodes
|
850
|
+
removable_nodes = [u for u in path if neighbors_in_path[u] == 2 and
|
851
|
+
u != a and u != b]
|
852
|
+
|
853
|
+
def relabel(w):
|
854
|
+
return -3 if w == c else w
|
855
|
+
|
856
|
+
emb = {relabel(v): [relabel(u) for u in emb[v]] for v in graph}
|
857
|
+
for u, v, w in (a, b, -3), (b, -3, a), (-3, a, b):
|
858
|
+
idx = emb[u].index(v)
|
859
|
+
if idx == 0:
|
860
|
+
emb[u] = emb[u][1:-1]
|
861
|
+
else:
|
862
|
+
emb[u] = emb[u][idx+1:] + emb[u][:idx-1]
|
863
|
+
|
864
|
+
new_g.set_embedding(emb)
|
865
|
+
return new_g
|