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,743 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
r"""
|
4
|
+
Line graphs
|
5
|
+
|
6
|
+
This module gather everything which is related to line graphs. Right now, this
|
7
|
+
amounts to the following functions :
|
8
|
+
|
9
|
+
.. csv-table::
|
10
|
+
:class: contentstable
|
11
|
+
:widths: 30, 70
|
12
|
+
:delim: |
|
13
|
+
|
14
|
+
:meth:`line_graph` | Return the line graph of a given graph
|
15
|
+
:meth:`is_line_graph` | Check whether a graph is a line graph
|
16
|
+
:meth:`root_graph` | Return the root graph corresponding to the given graph
|
17
|
+
|
18
|
+
Author:
|
19
|
+
|
20
|
+
- Nathann Cohen (01-2013), :meth:`root_graph` method and module documentation.
|
21
|
+
Written while listening to Nina Simone *"I wish I knew how it would feel to be
|
22
|
+
free"*. Crazy good song. And *"Prendre ta douleur"*, too.
|
23
|
+
|
24
|
+
- David Coudert (10-2018), use maximal cliques iterator in :meth:`root_graph`,
|
25
|
+
and use :meth:`root_graph` instead of forbidden subgraph search in
|
26
|
+
:meth:`is_line_graph` (:issue:`26444`).
|
27
|
+
|
28
|
+
Definition
|
29
|
+
-----------
|
30
|
+
|
31
|
+
Given a graph `G`, the *line graph* `L(G)` of `G` is the graph such that
|
32
|
+
|
33
|
+
.. MATH::
|
34
|
+
|
35
|
+
V(L(G)) =& E(G)\\
|
36
|
+
E(L(G)) =& \{(e,e'):\text{ and }e,e'\text{ have a common endpoint in }G\}\\
|
37
|
+
|
38
|
+
The definition is extended to directed graphs. In this situation, there is an
|
39
|
+
arc `(e,e')` in `L(G)` if the destination of `e` is the origin of `e'`.
|
40
|
+
|
41
|
+
For more information, see the :wikipedia:`Line_graph`.
|
42
|
+
|
43
|
+
Root graph
|
44
|
+
----------
|
45
|
+
|
46
|
+
A graph whose line graph is `LG` is called the *root graph* of `LG`. The root
|
47
|
+
graph of a (connected) graph is unique ([Whi1932]_, [Har1969]_), except when
|
48
|
+
`LG=K_3`, as both `L(K_3)` and `L(K_{1,3})` are equal to `K_3`.
|
49
|
+
|
50
|
+
Here is how we can *"see"* `G` by staring (very intently) at `LG` :
|
51
|
+
|
52
|
+
A graph `LG` is the line graph of `G` if there exists a collection
|
53
|
+
`(S_v)_{v\in G}` of subsets of `V(LG)` such that :
|
54
|
+
|
55
|
+
* Every `S_v` is a complete subgraph of `LG`.
|
56
|
+
|
57
|
+
* Every `v\in LG` belongs to exactly two sets of the family `(S_v)_{v\in G}`.
|
58
|
+
|
59
|
+
* Any two sets of `(S_v)_{v\in G}` have at most one common elements
|
60
|
+
|
61
|
+
* For any edge `(u,v)\in LG` there exists a set of `(S_v)_{v\in G}` containing
|
62
|
+
both `u` and `v`.
|
63
|
+
|
64
|
+
In this family, each set `S_v` represent a vertex of `G`, and contains "the
|
65
|
+
set of edges incident to `v` in `G`". Two elements `S_v,S_{v'}` have a
|
66
|
+
nonempty intersection whenever `vv'` is an edge of `G`.
|
67
|
+
|
68
|
+
Hence, finding the root graph of `LG` is the job of finding this collection of
|
69
|
+
sets.
|
70
|
+
|
71
|
+
In particular, what we know for sure is that a maximal clique `S` of size `2` or
|
72
|
+
`\geq 4` in `LG` corresponds to a vertex of degree `|S|` in `G`, whose incident
|
73
|
+
edges are the elements of `S` itself.
|
74
|
+
|
75
|
+
The main problem lies with maximal cliques of size 3, i.e. triangles. Those we
|
76
|
+
have to split into two categories, *even* and *odd* triangles :
|
77
|
+
|
78
|
+
A triangle `\{e_1,e_2,e_3\}\subseteq V(LG)` is said to be an *odd* triangle if
|
79
|
+
there exists a vertex `e\in V(G)` incident to exactly *one* or *all* of
|
80
|
+
`\{e_1,e_2,e_3\}`, and it is said to be *even* otherwise.
|
81
|
+
|
82
|
+
The very good point of this definition is that an inclusionwise maximal clique
|
83
|
+
which is an odd triangle will always correspond to a vertex of degree 3 in `G`,
|
84
|
+
while an even triangle could result from either a vertex of degree 3 in `G` or a
|
85
|
+
triangle in `G`. And in order to build the root graph we obviously have to
|
86
|
+
decide *which*.
|
87
|
+
|
88
|
+
Beineke proves in [Bei1970]_ that the collection of sets we are looking for
|
89
|
+
can be easily found. Indeed it turns out that it is the union of :
|
90
|
+
|
91
|
+
#. The family of all maximal cliques of `LG` of size 2 or `\geq 4`, as well as
|
92
|
+
all odd triangles.
|
93
|
+
|
94
|
+
#. The family of all pairs of adjacent vertices which appear in exactly *one*
|
95
|
+
maximal clique which is an even triangle.
|
96
|
+
|
97
|
+
There are actually four special cases to which the decomposition above does not
|
98
|
+
apply, i.e. graphs containing an edge which belongs to exactly two even
|
99
|
+
triangles. We deal with those independently.
|
100
|
+
|
101
|
+
* The :meth:`Complete graph
|
102
|
+
<sage.graphs.graph_generators.GraphGenerators.CompleteGraph>` `K_3`.
|
103
|
+
|
104
|
+
* The :meth:`Diamond graph
|
105
|
+
<sage.graphs.graph_generators.GraphGenerators.DiamondGraph>` -- the line graph
|
106
|
+
of `K_{1,3}` plus an edge.
|
107
|
+
|
108
|
+
* The :meth:`Wheel graph
|
109
|
+
<sage.graphs.graph_generators.GraphGenerators.WheelGraph>` on `4+1` vertices
|
110
|
+
-- the line graph of the :meth:`Diamond graph
|
111
|
+
<sage.graphs.graph_generators.GraphGenerators.DiamondGraph>`
|
112
|
+
|
113
|
+
* The :meth:`Octahedron
|
114
|
+
<sage.graphs.graph_generators.GraphGenerators.OctahedralGraph>` -- the line
|
115
|
+
graph of `K_4`.
|
116
|
+
|
117
|
+
This decomposition turns out to be very easy to implement :-)
|
118
|
+
|
119
|
+
.. WARNING::
|
120
|
+
|
121
|
+
Even though the root graph is *NOT UNIQUE* for the triangle, this method
|
122
|
+
returns `K_{1,3}` (and not `K_3`) in this case. Pay *very close* attention
|
123
|
+
to that, for this answer is not theoretically correct : there is no unique
|
124
|
+
answer in this case, and we deal with it by returning one of the two
|
125
|
+
possible answers.
|
126
|
+
|
127
|
+
Functions
|
128
|
+
---------
|
129
|
+
"""
|
130
|
+
|
131
|
+
from sage.structure.element cimport parent
|
132
|
+
|
133
|
+
|
134
|
+
def is_line_graph(g, certificate=False):
|
135
|
+
r"""
|
136
|
+
Check whether the graph `g` is a line graph.
|
137
|
+
|
138
|
+
INPUT:
|
139
|
+
|
140
|
+
- ``certificate`` -- boolean; whether to return a certificate along with
|
141
|
+
the boolean result. Here is what happens when ``certificate = True``:
|
142
|
+
|
143
|
+
- If the graph is not a line graph, the method returns a pair ``(b,
|
144
|
+
subgraph)`` where ``b`` is ``False`` and ``subgraph`` is a subgraph
|
145
|
+
isomorphic to one of the 9 forbidden induced subgraphs of a line graph.
|
146
|
+
|
147
|
+
- If the graph is a line graph, the method returns a triple ``(b,R,isom)``
|
148
|
+
where ``b`` is ``True``, ``R`` is a graph whose line graph is the graph
|
149
|
+
given as input, and ``isom`` is a map associating an edge of ``R`` to
|
150
|
+
each vertex of the graph.
|
151
|
+
|
152
|
+
.. NOTE::
|
153
|
+
|
154
|
+
This method wastes a bit of time when the input graph is not connected.
|
155
|
+
If you have performance in mind, it is probably better to only feed it
|
156
|
+
with connected graphs only.
|
157
|
+
|
158
|
+
.. SEEALSO::
|
159
|
+
|
160
|
+
- The :mod:`line_graph <sage.graphs.line_graph>` module.
|
161
|
+
|
162
|
+
- :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs`
|
163
|
+
-- the forbidden subgraphs of a line graph.
|
164
|
+
|
165
|
+
- :meth:`~sage.graphs.generic_graph.GenericGraph.line_graph`
|
166
|
+
|
167
|
+
EXAMPLES:
|
168
|
+
|
169
|
+
A complete graph is always the line graph of a star::
|
170
|
+
|
171
|
+
sage: graphs.CompleteGraph(5).is_line_graph()
|
172
|
+
True
|
173
|
+
|
174
|
+
The Petersen Graph not being claw-free, it is not a line
|
175
|
+
graph::
|
176
|
+
|
177
|
+
sage: graphs.PetersenGraph().is_line_graph()
|
178
|
+
False
|
179
|
+
|
180
|
+
This is indeed the subgraph returned::
|
181
|
+
|
182
|
+
sage: C = graphs.PetersenGraph().is_line_graph(certificate=True)[1] # needs sage.modules
|
183
|
+
sage: C.is_isomorphic(graphs.ClawGraph()) # needs sage.modules
|
184
|
+
True
|
185
|
+
|
186
|
+
The house graph is a line graph::
|
187
|
+
|
188
|
+
sage: g = graphs.HouseGraph()
|
189
|
+
sage: g.is_line_graph()
|
190
|
+
True
|
191
|
+
|
192
|
+
But what is the graph whose line graph is the house ?::
|
193
|
+
|
194
|
+
sage: # needs sage.modules
|
195
|
+
sage: is_line, R, isom = g.is_line_graph(certificate=True)
|
196
|
+
sage: R.sparse6_string()
|
197
|
+
':DaHI~'
|
198
|
+
sage: R.show() # needs sage.plot
|
199
|
+
sage: isom
|
200
|
+
{0: (0, 1), 1: (0, 2), 2: (1, 3), 3: (2, 3), 4: (3, 4)}
|
201
|
+
|
202
|
+
TESTS:
|
203
|
+
|
204
|
+
Disconnected graphs::
|
205
|
+
|
206
|
+
sage: g = 2 * graphs.CycleGraph(3)
|
207
|
+
sage: gl = g.line_graph().relabel(inplace=False)
|
208
|
+
sage: new_g = gl.is_line_graph(certificate=True)[1] # needs sage.modules
|
209
|
+
sage: g.line_graph().is_isomorphic(gl) # needs sage.modules
|
210
|
+
True
|
211
|
+
|
212
|
+
Verify that :issue:`29740` is fixed::
|
213
|
+
|
214
|
+
sage: g = Graph('O{e[{}^~z`MDZBZBkXzE^')
|
215
|
+
sage: g.is_line_graph()
|
216
|
+
False
|
217
|
+
"""
|
218
|
+
g._scream_if_not_simple()
|
219
|
+
|
220
|
+
def get_certificate(gg):
|
221
|
+
"""
|
222
|
+
Assuming that gg is not a line graph, return a forbidden induced
|
223
|
+
subgraph.
|
224
|
+
"""
|
225
|
+
from sage.graphs.graph_generators import graphs
|
226
|
+
for fg in graphs.line_graph_forbidden_subgraphs():
|
227
|
+
h = gg.subgraph_search(fg, induced=True)
|
228
|
+
if h is not None:
|
229
|
+
return h
|
230
|
+
|
231
|
+
if g.is_connected():
|
232
|
+
try:
|
233
|
+
# To check whether g is a line graph, we try to construct its root
|
234
|
+
# graph
|
235
|
+
R, isom = root_graph(g)
|
236
|
+
if certificate:
|
237
|
+
return True, R, isom
|
238
|
+
return True
|
239
|
+
except ValueError as VE:
|
240
|
+
if str(VE) == "this graph is not a line graph !":
|
241
|
+
# g is not a line graph
|
242
|
+
if certificate:
|
243
|
+
return False, get_certificate(g)
|
244
|
+
return False
|
245
|
+
raise VE
|
246
|
+
|
247
|
+
# g is not connected, so we apply the above procedure to each connected
|
248
|
+
# component
|
249
|
+
from sage.graphs.graph import Graph
|
250
|
+
R = Graph()
|
251
|
+
for gg in g.connected_components_subgraphs():
|
252
|
+
try:
|
253
|
+
RR, isom = root_graph(gg)
|
254
|
+
R += RR
|
255
|
+
except ValueError as VE:
|
256
|
+
if str(VE) == "this graph is not a line graph !":
|
257
|
+
# gg is not a line graph
|
258
|
+
if certificate:
|
259
|
+
return False, get_certificate(gg)
|
260
|
+
return False
|
261
|
+
raise VE
|
262
|
+
|
263
|
+
if certificate:
|
264
|
+
_, isom = g.is_isomorphic(R.line_graph(labels=False), certificate=True)
|
265
|
+
return True, R, isom
|
266
|
+
return True
|
267
|
+
|
268
|
+
|
269
|
+
def line_graph(g, labels=True, return_labels=False, immutable=None):
|
270
|
+
"""
|
271
|
+
Return the line graph of the (di)graph ``g`` (multiedges and loops allowed).
|
272
|
+
|
273
|
+
INPUT:
|
274
|
+
|
275
|
+
- ``labels`` -- boolean (default: ``True``); whether edge labels should be
|
276
|
+
taken in consideration. If ``labels=True``, the vertices of the line graph
|
277
|
+
will be triples ``(u,v,label)``, and pairs of vertices otherwise. In case
|
278
|
+
of multiple edges, the vertices of the line graph will be triples
|
279
|
+
``(u,v,an integer)``.
|
280
|
+
|
281
|
+
- ``return_labels`` -- boolean (default: ``False``); whether edge labels should
|
282
|
+
be stored or not. If g has multiple edges and if ``return_labels=True``, the
|
283
|
+
method returns a list the first element of which is the line-graph of g
|
284
|
+
and the second element is a dictionary {vertex of the line-graph: former
|
285
|
+
corresponding edge with its original label}. If ``return_labels=False``,
|
286
|
+
the method returns only the line-graph.
|
287
|
+
|
288
|
+
- ``immutable`` -- boolean (default: ``None``); whether to create a
|
289
|
+
mutable/immutable (di)graph. ``immutable=None`` (default) means that the
|
290
|
+
(di)graph and its line (di)graph will behave the same way.
|
291
|
+
|
292
|
+
The line graph of an undirected graph G is an undirected simple graph H such
|
293
|
+
that the vertices of H are the edges of G and two vertices e and f of H are
|
294
|
+
adjacent if e and f share a common vertex in G. In other words, an edge in H
|
295
|
+
represents a path of length 2 in G.
|
296
|
+
|
297
|
+
Loops are not adjacent to themselves.
|
298
|
+
|
299
|
+
The line graph of a directed graph G is a directed graph H such that the
|
300
|
+
vertices of H are the edges of G and two vertices e and f of H are adjacent
|
301
|
+
if e and f share a common vertex in G and the terminal vertex of e is the
|
302
|
+
initial vertex of f. In other words, an edge in H represents a (directed)
|
303
|
+
path of length 2 in G.
|
304
|
+
|
305
|
+
.. NOTE::
|
306
|
+
|
307
|
+
As a :class:`Graph` object only accepts hashable objects as vertices
|
308
|
+
(and as the vertices of the line graph are the edges of the graph), this
|
309
|
+
code will fail if edge labels are not hashable. You can also set the
|
310
|
+
argument ``labels=False`` to ignore labels.
|
311
|
+
|
312
|
+
.. SEEALSO::
|
313
|
+
|
314
|
+
- The :mod:`line_graph <sage.graphs.line_graph>` module
|
315
|
+
|
316
|
+
- :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs`
|
317
|
+
-- the forbidden subgraphs of a line graph
|
318
|
+
|
319
|
+
- :meth:`~Graph.is_line_graph` -- tests whether a graph is a line graph
|
320
|
+
|
321
|
+
EXAMPLES::
|
322
|
+
|
323
|
+
sage: g = graphs.CompleteGraph(4)
|
324
|
+
sage: h = g.line_graph()
|
325
|
+
sage: h.vertices(sort=True)
|
326
|
+
[(0, 1, None),
|
327
|
+
(0, 2, None),
|
328
|
+
(0, 3, None),
|
329
|
+
(1, 2, None),
|
330
|
+
(1, 3, None),
|
331
|
+
(2, 3, None)]
|
332
|
+
sage: h.am() # needs sage.modules
|
333
|
+
[0 1 1 1 1 0]
|
334
|
+
[1 0 1 1 0 1]
|
335
|
+
[1 1 0 0 1 1]
|
336
|
+
[1 1 0 0 1 1]
|
337
|
+
[1 0 1 1 0 1]
|
338
|
+
[0 1 1 1 1 0]
|
339
|
+
sage: h2 = g.line_graph(labels=False)
|
340
|
+
sage: h2.vertices(sort=True)
|
341
|
+
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
|
342
|
+
sage: h2.am() == h.am() # needs sage.modules
|
343
|
+
True
|
344
|
+
sage: g = DiGraph([[1..4], lambda i,j: i < j])
|
345
|
+
sage: h = g.line_graph()
|
346
|
+
sage: h.vertices(sort=True)
|
347
|
+
[(1, 2, None),
|
348
|
+
(1, 3, None),
|
349
|
+
(1, 4, None),
|
350
|
+
(2, 3, None),
|
351
|
+
(2, 4, None),
|
352
|
+
(3, 4, None)]
|
353
|
+
sage: h.edges(sort=True)
|
354
|
+
[((1, 2, None), (2, 3, None), None),
|
355
|
+
((1, 2, None), (2, 4, None), None),
|
356
|
+
((1, 3, None), (3, 4, None), None),
|
357
|
+
((2, 3, None), (3, 4, None), None)]
|
358
|
+
|
359
|
+
Examples with multiple edges::
|
360
|
+
|
361
|
+
sage: L = Graph([(0,1),(0,1),(1,2)],multiedges=True).line_graph()
|
362
|
+
sage: L.edges()
|
363
|
+
[((0, 1, 0), (0, 1, 1), None), ((0, 1, 1), (1, 2, 2), None),
|
364
|
+
((0, 1, 0), (1, 2, 2), None)]
|
365
|
+
sage: G = Graph([(0,1),(0,1,'a'),(0,1,'b'),(0,2),(1,2,'c')],
|
366
|
+
....: multiedges=True)
|
367
|
+
sage: L = G.line_graph(False,True)
|
368
|
+
sage: L[0].edges()
|
369
|
+
[((0, 1, 1), (0, 1, 2), None), ((0, 1, 0), (0, 1, 2), None),
|
370
|
+
((0, 1, 2), (0, 2, 3), None), ((0, 1, 2), (1, 2, 4), None), ((0, 1, 0),
|
371
|
+
(0, 1, 1), None), ((0, 1, 1), (0, 2, 3), None), ((0, 1, 1),
|
372
|
+
(1, 2, 4), None), ((0, 1, 0), (0, 2, 3), None), ((0, 1, 0),
|
373
|
+
(1, 2, 4), None), ((0, 2, 3), (1, 2, 4), None)]
|
374
|
+
sage: L[1]
|
375
|
+
{(0, 1, 0): (0, 1, None),
|
376
|
+
(0, 1, 1): (0, 1, 'b'),
|
377
|
+
(0, 1, 2): (0, 1, 'a'),
|
378
|
+
(0, 2, 3): (0, 2, None),
|
379
|
+
(1, 2, 4): (1, 2, 'c')}
|
380
|
+
sage: g = DiGraph([(0,1),(0,1),(1,2)],multiedges=True)
|
381
|
+
sage: g.line_graph().edges()
|
382
|
+
[((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)]
|
383
|
+
|
384
|
+
An example with a loop::
|
385
|
+
|
386
|
+
sage: g = Graph([(0,0),(0,1),(0,2),(1,2)],multiedges=True,loops=True)
|
387
|
+
sage: L = g.line_graph()
|
388
|
+
sage: L.edges()
|
389
|
+
[((0, 0, None), (0, 1, None), None), ((0, 0, None), (0, 2, None), None),
|
390
|
+
((0, 1, None), (0, 2, None), None), ((0, 1, None), (1, 2, None), None),
|
391
|
+
((0, 2, None), (1, 2, None), None)]
|
392
|
+
|
393
|
+
TESTS:
|
394
|
+
|
395
|
+
:issue:`13787`::
|
396
|
+
|
397
|
+
sage: g = graphs.KneserGraph(7,1)
|
398
|
+
sage: C = graphs.CompleteGraph(7)
|
399
|
+
sage: C.is_isomorphic(g)
|
400
|
+
True
|
401
|
+
sage: C.line_graph().is_isomorphic(g.line_graph())
|
402
|
+
True
|
403
|
+
|
404
|
+
Check the behavior of parameter ``immutable``::
|
405
|
+
|
406
|
+
sage: G = Graph([(0, 1), (1, 2)])
|
407
|
+
sage: G.line_graph().is_immutable()
|
408
|
+
False
|
409
|
+
sage: G.line_graph(immutable=True).is_immutable()
|
410
|
+
True
|
411
|
+
sage: G = Graph([(0, 1), (1, 2)], immutable=True)
|
412
|
+
sage: G.line_graph().is_immutable()
|
413
|
+
True
|
414
|
+
sage: G.line_graph(immutable=False).is_immutable()
|
415
|
+
False
|
416
|
+
sage: G = Graph([(0, 1), (0, 1), (1, 2)], multiedges=True)
|
417
|
+
sage: G.line_graph().is_immutable()
|
418
|
+
False
|
419
|
+
sage: G.line_graph(immutable=True).is_immutable()
|
420
|
+
True
|
421
|
+
sage: G = Graph([(0, 1), (0, 1), (1, 2)], multiedges=True, immutable=True)
|
422
|
+
sage: G.line_graph().is_immutable()
|
423
|
+
True
|
424
|
+
sage: G.line_graph(immutable=False).is_immutable()
|
425
|
+
False
|
426
|
+
sage: G = DiGraph([(0, 1), (1, 2)])
|
427
|
+
sage: G.line_graph().is_immutable()
|
428
|
+
False
|
429
|
+
sage: G.line_graph(immutable=True).is_immutable()
|
430
|
+
True
|
431
|
+
"""
|
432
|
+
cdef dict conflicts = {}
|
433
|
+
cdef dict origlabels_dic = {} # stores original labels of edges in case of multiple edges
|
434
|
+
|
435
|
+
multiple = g.has_multiple_edges()
|
436
|
+
|
437
|
+
if immutable is None:
|
438
|
+
immutable = g.is_immutable()
|
439
|
+
|
440
|
+
if multiple:
|
441
|
+
# As the edges of g are the vertices of its line graph, we need to distinguish between the multiple edges of g.
|
442
|
+
# To this aim, we assign to each edge of g an integer label (between 0 and g.size() - 1) and set labels to True
|
443
|
+
# in order to keep these labels during the construction of the line graph.
|
444
|
+
labels = True
|
445
|
+
origlabels_dic = {(u, v, id): (u, v, label)
|
446
|
+
for id, (u, v, label) in enumerate(g.edge_iterator())}
|
447
|
+
g = parent(g)([g, origlabels_dic.keys()], format='vertices_and_edges', multiedges=True)
|
448
|
+
|
449
|
+
if g._directed:
|
450
|
+
from sage.graphs.digraph import DiGraph
|
451
|
+
# Connect appropriate incident edges of each vertex v
|
452
|
+
arcs = ((e, f) for v in g
|
453
|
+
for e in g.incoming_edge_iterator(v, labels=labels)
|
454
|
+
for f in g.outgoing_edge_iterator(v, labels=labels))
|
455
|
+
G = DiGraph([g.edge_iterator(labels=labels), arcs],
|
456
|
+
format='vertices_and_edges', immutable=immutable)
|
457
|
+
if return_labels and multiple:
|
458
|
+
return [G, origlabels_dic]
|
459
|
+
return G
|
460
|
+
|
461
|
+
# We must sort the edges' endpoints so that (1,2,None) is seen as the
|
462
|
+
# same edge as (2,1,None).
|
463
|
+
#
|
464
|
+
# We do so by comparing hashes, just in case all the natural order (<)
|
465
|
+
# on vertices would not be a total order (for instance when vertices are
|
466
|
+
# sets). If two adjacent vertices have the same hash, then we store the
|
467
|
+
# pair in the dictionary of conflicts
|
468
|
+
|
469
|
+
# 1) List of vertices in the line graph
|
470
|
+
cdef list vertices = []
|
471
|
+
for e in g.edge_iterator(labels=labels):
|
472
|
+
if hash(e[0]) < hash(e[1]):
|
473
|
+
vertices.append(e)
|
474
|
+
elif hash(e[0]) > hash(e[1]):
|
475
|
+
vertices.append((e[1], e[0]) + e[2:])
|
476
|
+
else:
|
477
|
+
# Settle the conflict arbitrarily
|
478
|
+
conflicts[e] = e
|
479
|
+
conflicts[(e[1], e[0]) + e[2:]] = e
|
480
|
+
vertices.append(e)
|
481
|
+
|
482
|
+
# 2) adjacencies in the line graph
|
483
|
+
cdef list edges = []
|
484
|
+
cdef list elist
|
485
|
+
from itertools import combinations
|
486
|
+
for v in g:
|
487
|
+
elist = []
|
488
|
+
|
489
|
+
# Add the edge to the list, according to hashes, as previously
|
490
|
+
for e in g.edge_iterator(v, labels=labels): # iterates over the edges incident to v
|
491
|
+
if hash(e[0]) < hash(e[1]):
|
492
|
+
elist.append(e)
|
493
|
+
elif hash(e[0]) > hash(e[1]):
|
494
|
+
elist.append((e[1], e[0]) + e[2:])
|
495
|
+
else:
|
496
|
+
elist.append(conflicts[e])
|
497
|
+
|
498
|
+
# All pairs of elements in elist are edges of the line graph
|
499
|
+
# if g has multiple edges, some pairs appear more than once but as G is defined as simple,
|
500
|
+
# the corresponding edges are not added as multiedges (as it should be).
|
501
|
+
edges.extend(combinations(elist, 2))
|
502
|
+
|
503
|
+
from sage.graphs.graph import Graph
|
504
|
+
G = Graph([vertices, edges], format='vertices_and_edges',
|
505
|
+
immutable=immutable)
|
506
|
+
if return_labels and multiple:
|
507
|
+
return [G, origlabels_dic]
|
508
|
+
return G
|
509
|
+
|
510
|
+
|
511
|
+
def root_graph(g, verbose=False, immutable=None):
|
512
|
+
r"""
|
513
|
+
Return the root graph corresponding to the given graph ``g``.
|
514
|
+
|
515
|
+
See the documentation of :mod:`sage.graphs.line_graph` to know how it works.
|
516
|
+
|
517
|
+
INPUT:
|
518
|
+
|
519
|
+
- ``g`` -- a graph
|
520
|
+
|
521
|
+
- ``verbose`` -- boolean (default: ``False``); display some information
|
522
|
+
about what is happening inside of the algorithm
|
523
|
+
|
524
|
+
- ``immutable`` -- boolean (default: ``None``); whether to create a
|
525
|
+
mutable/immutable (di)graph. ``immutable=None`` (default) means that the
|
526
|
+
(di)graph and its root (di)graph will behave the same way.
|
527
|
+
|
528
|
+
.. WARNING::
|
529
|
+
|
530
|
+
This code assumes that `g` is a line graph, and is a connected,
|
531
|
+
undirected graph without multiple edges.
|
532
|
+
|
533
|
+
TESTS:
|
534
|
+
|
535
|
+
All connected graphs on 6 vertices::
|
536
|
+
|
537
|
+
sage: from sage.graphs.line_graph import root_graph
|
538
|
+
sage: def test(g):
|
539
|
+
....: gl = g.line_graph(labels=False)
|
540
|
+
....: d = root_graph(gl)
|
541
|
+
sage: for i,g in enumerate(graphs(6)): # long time
|
542
|
+
....: if not g.is_connected():
|
543
|
+
....: continue
|
544
|
+
....: test(g)
|
545
|
+
|
546
|
+
Non line-graphs::
|
547
|
+
|
548
|
+
sage: root_graph(graphs.PetersenGraph())
|
549
|
+
Traceback (most recent call last):
|
550
|
+
...
|
551
|
+
ValueError: this graph is not a line graph !
|
552
|
+
|
553
|
+
sage: root_graph(Graph('O{e[{}^~z`MDZBZBkXzE^'))
|
554
|
+
Traceback (most recent call last):
|
555
|
+
...
|
556
|
+
ValueError: this graph is not a line graph !
|
557
|
+
|
558
|
+
Small corner-cases::
|
559
|
+
|
560
|
+
sage: from sage.graphs.line_graph import root_graph
|
561
|
+
sage: root_graph(graphs.CompleteGraph(3))
|
562
|
+
(Complete bipartite graph of order 1+3: Graph on 4 vertices,
|
563
|
+
{0: (0, 1), 1: (0, 2), 2: (0, 3)})
|
564
|
+
sage: G, D = root_graph(graphs.OctahedralGraph()); G
|
565
|
+
Complete graph: Graph on 4 vertices
|
566
|
+
sage: G, D = root_graph(graphs.DiamondGraph()); G
|
567
|
+
Graph on 4 vertices
|
568
|
+
sage: G, D = root_graph(graphs.WheelGraph(5)); G
|
569
|
+
Diamond Graph: Graph on 4 vertices
|
570
|
+
|
571
|
+
Check the behavior of parameter ``immutable``::
|
572
|
+
|
573
|
+
sage: G = graphs.CycleGraph(4)
|
574
|
+
sage: root_graph(G)[0].is_immutable()
|
575
|
+
False
|
576
|
+
sage: root_graph(G, immutable=True)[0].is_immutable()
|
577
|
+
True
|
578
|
+
sage: G = graphs.CycleGraph(4, immutable=True)
|
579
|
+
sage: root_graph(G)[0].is_immutable()
|
580
|
+
True
|
581
|
+
sage: root_graph(G, immutable=True)[0].is_immutable()
|
582
|
+
True
|
583
|
+
"""
|
584
|
+
from sage.graphs.digraph import DiGraph
|
585
|
+
|
586
|
+
if isinstance(g, DiGraph):
|
587
|
+
raise ValueError("g cannot be a DiGraph !")
|
588
|
+
if g.has_multiple_edges():
|
589
|
+
raise ValueError("g cannot have multiple edges !")
|
590
|
+
if not g.is_connected():
|
591
|
+
raise ValueError("g is not connected !")
|
592
|
+
# is_line_graph expects a particular error message when g is not a line graph
|
593
|
+
not_line_graph = "this graph is not a line graph !"
|
594
|
+
|
595
|
+
if immutable is None:
|
596
|
+
immutable = g.is_immutable()
|
597
|
+
|
598
|
+
# Complete Graph ?
|
599
|
+
if g.is_clique():
|
600
|
+
from sage.graphs.generators.basic import CompleteBipartiteGraph
|
601
|
+
return (CompleteBipartiteGraph(1, g.order(), immutable=immutable),
|
602
|
+
{v: (0, 1 + i) for i, v in enumerate(g)})
|
603
|
+
|
604
|
+
# Diamond Graph ?
|
605
|
+
elif g.order() == 4 and g.size() == 5:
|
606
|
+
from sage.graphs.graph import Graph
|
607
|
+
root = Graph([(0, 1), (1, 2), (2, 0), (0, 3)], immutable=immutable)
|
608
|
+
return (root,
|
609
|
+
g.is_isomorphic(root.line_graph(labels=False), certificate=True)[1])
|
610
|
+
|
611
|
+
# Wheel on 5 vertices ?
|
612
|
+
elif g.order() == 5 and g.size() == 8 and min(g.degree()) == 3:
|
613
|
+
from sage.graphs.generators.basic import DiamondGraph
|
614
|
+
root = DiamondGraph(immutable=immutable)
|
615
|
+
return (root,
|
616
|
+
g.is_isomorphic(root.line_graph(labels=False), certificate=True)[1])
|
617
|
+
|
618
|
+
# Octahedron ?
|
619
|
+
elif g.order() == 6 and g.size() == 12 and g.is_regular(k=4):
|
620
|
+
from sage.graphs.generators.platonic_solids import OctahedralGraph
|
621
|
+
if g.is_isomorphic(OctahedralGraph()):
|
622
|
+
from sage.graphs.generators.basic import CompleteGraph
|
623
|
+
root = CompleteGraph(4, immutable=immutable)
|
624
|
+
return (root,
|
625
|
+
g.is_isomorphic(root.line_graph(labels=False), certificate=True)[1])
|
626
|
+
|
627
|
+
# From now on we can assume (thanks to Beineke) that no edge belongs to two
|
628
|
+
# even triangles at once.
|
629
|
+
|
630
|
+
# Better to work on integers... Everything takes more time otherwise.
|
631
|
+
G = g.relabel(inplace=False)
|
632
|
+
|
633
|
+
# Dictionary of (pairs of) cliques, i.e. the two cliques associated with
|
634
|
+
# each vertex.
|
635
|
+
cdef dict v_cliques = {v: [] for v in G}
|
636
|
+
# All the even triangles we meet
|
637
|
+
cdef list even_triangles = []
|
638
|
+
|
639
|
+
# We iterate over all maximal cliques of the graph.
|
640
|
+
|
641
|
+
# As method G.cliques_maximal() returns the list of all maximal cliques
|
642
|
+
# (which takes an exponential time on general graphs, while it is obviously
|
643
|
+
# polynomial on line graphs), we instead use IndependentSet to iterate over
|
644
|
+
# all the maximal cliques. This way, we can say early that the graph is not
|
645
|
+
# a line graph.
|
646
|
+
|
647
|
+
from sage.graphs.independent_sets import IndependentSets
|
648
|
+
for S in IndependentSets(G, maximal=True, complement=True):
|
649
|
+
|
650
|
+
# Triangles... even or odd ?
|
651
|
+
if len(S) == 3:
|
652
|
+
|
653
|
+
# If a vertex of G has an odd number of neighbors among the vertices
|
654
|
+
# of S, then the triangle is odd. We compute the list of such
|
655
|
+
# vertices by taking the symmetric difference of the neighborhood of
|
656
|
+
# our three vertices.
|
657
|
+
#
|
658
|
+
# Note that the elements of S do not appear in this set as they are
|
659
|
+
# all seen exactly twice.
|
660
|
+
|
661
|
+
odd_neighbors = set(G.neighbor_iterator(S[0]))
|
662
|
+
odd_neighbors.symmetric_difference_update(G.neighbor_iterator(S[1]))
|
663
|
+
odd_neighbors.symmetric_difference_update(G.neighbor_iterator(S[2]))
|
664
|
+
|
665
|
+
# Even triangles
|
666
|
+
if not odd_neighbors:
|
667
|
+
even_triangles.append(tuple(S))
|
668
|
+
continue
|
669
|
+
|
670
|
+
# We manage odd triangles the same way we manage other cliques ...
|
671
|
+
|
672
|
+
# We now associate the clique to all the vertices it contains.
|
673
|
+
for v in S:
|
674
|
+
if len(v_cliques[v]) == 2:
|
675
|
+
raise ValueError(not_line_graph)
|
676
|
+
v_cliques[v].append(tuple(S))
|
677
|
+
|
678
|
+
if verbose:
|
679
|
+
print("Added clique", S)
|
680
|
+
|
681
|
+
# Deal with even triangles
|
682
|
+
for u, v, w in even_triangles:
|
683
|
+
|
684
|
+
# According to Beineke, we must go through all even triangles, and for
|
685
|
+
# each triangle uvw consider its three pairs of adjacent vertices uv,
|
686
|
+
# vw, wu. For all pairs xy among those such that xy do not appear
|
687
|
+
# together in any clique we have found so far, we add xy to the list of
|
688
|
+
# cliques describing our covering.
|
689
|
+
|
690
|
+
for x, y in [(u, v), (v, w), (w, u)]:
|
691
|
+
|
692
|
+
# If edge xy does not appear in any of the cliques associated with y
|
693
|
+
if all(x not in C for C in v_cliques[y]):
|
694
|
+
if len(v_cliques[y]) >= 2 or len(v_cliques[x]) >= 2:
|
695
|
+
raise ValueError("this graph is not a line graph !")
|
696
|
+
|
697
|
+
v_cliques[x].append((x, y))
|
698
|
+
v_cliques[y].append((x, y))
|
699
|
+
|
700
|
+
if verbose:
|
701
|
+
print("Adding pair", (x, y),
|
702
|
+
"appearing in the even triangle", (u, v, w))
|
703
|
+
|
704
|
+
# Deal with vertices contained in only one clique. All edges must be defined
|
705
|
+
# by TWO endpoints, so we add a fake clique.
|
706
|
+
for x, clique_list in v_cliques.items():
|
707
|
+
if len(clique_list) == 1:
|
708
|
+
clique_list.append((x,))
|
709
|
+
|
710
|
+
# We now have all our cliques. Let's build the root graph to check that it
|
711
|
+
# all fits !
|
712
|
+
|
713
|
+
# Associates an integer to each clique
|
714
|
+
cdef dict relabel = {}
|
715
|
+
|
716
|
+
# Associates to each vertex of G its pair of coordinates in R
|
717
|
+
cdef dict vertex_to_map = {}
|
718
|
+
|
719
|
+
for v, L in v_cliques.items():
|
720
|
+
|
721
|
+
# Add cliques to relabel dictionary
|
722
|
+
for S in L:
|
723
|
+
if S not in relabel:
|
724
|
+
relabel[S] = len(relabel)
|
725
|
+
|
726
|
+
# The coordinates of edge v
|
727
|
+
vertex_to_map[v] = relabel[L[0]], relabel[L[1]]
|
728
|
+
|
729
|
+
if verbose:
|
730
|
+
print("Final associations :")
|
731
|
+
for v, L in v_cliques.items():
|
732
|
+
print(v, L)
|
733
|
+
|
734
|
+
# We now build R
|
735
|
+
from sage.graphs.graph import Graph
|
736
|
+
R = Graph(vertex_to_map.values(), format='list_of_edges', immutable=immutable)
|
737
|
+
|
738
|
+
# If g is a line graph, then it is isomorphic to the line graph of the graph
|
739
|
+
# R that we have constructed, so we return R (and the isomorphism).
|
740
|
+
is_isom, isom = g.is_isomorphic(R.line_graph(labels=False), certificate=True)
|
741
|
+
if is_isom:
|
742
|
+
return R, isom
|
743
|
+
raise ValueError(not_line_graph)
|