Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/line_graph.py
8815 views
1
r"""
2
Line graphs
3
4
This module gather everything which is related to line graphs. Right now, this
5
amounts to the following functions :
6
7
.. csv-table::
8
:class: contentstable
9
:widths: 30, 70
10
:delim: |
11
12
:meth:`line_graph` | Computes the line graph of a given graph
13
:meth:`is_line_graph` | Check whether a graph is a line graph
14
:meth:`root_graph` | Computes the root graph corresponding to the given graph
15
16
Author:
17
18
- Nathann Cohen (01-2013), :meth:`root_graph` method and module documentation.
19
Written while listening to Nina Simone *"I wish I knew how it would feel to be
20
free"*. Crazy good song. And *"Prendre ta douleur"*, too.
21
22
Definition
23
-----------
24
25
Given a graph `G`, the *line graph* `L(G)` of `G` is the graph such that
26
27
.. MATH::
28
29
V(L(G)) =& E(G)\\
30
E(L(G)) =& \{(e,e'):\text{ and }e,e'\text{ have a common endpoint in }G\}\\
31
32
The definition is extended to directed graphs. In this situation, there is an
33
arc `(e,e')` in `L(G)` if the destination of `e` is the origin of `e'`.
34
35
For more information, see the :wikipedia:`Wikipedia page on line graphs
36
<Line_graph>`.
37
38
Root graph
39
----------
40
41
A graph whose line graph is `LG` is called the *root graph* of `LG`. The root
42
graph of a (connected) graph is unique ([Whitney32]_, [Harary69]_), except when
43
`LG=K_3`, as both `L(K_3)` and `L(K_{1,3})` are equal to `K_3`.
44
45
Here is how we can *"see"* `G` by staring (very intently) at `LG` :
46
47
A graph `LG` is the line graph of `G` if there exists a collection
48
`(S_v)_{v\in G}` of subsets of `V(LG)` such that :
49
50
* Every `S_v` is a complete subgraph of `LG`.
51
52
* Every `v\in LG` belongs to exactly two sets of the family `(S_v)_{v\in G}`.
53
54
* Any two sets of `(S_v)_{v\in G}` have at most one common elements
55
56
* For any edge `(u,v)\in LG` there exists a set of `(S_v)_{v\in G}` containing
57
both `u` and `v`.
58
59
In this family, each set `S_v` represent a vertex of `G`, and contains "the
60
set of edges incident to `v` in `G`". Two elements `S_v,S_{v'}` have a
61
nonempty intersection whenever `vv'` is an edge of `G`.
62
63
Hence, finding the root graph of `LG` is the job of finding this collection of
64
sets.
65
66
In particular, what we know for sure is that a maximal clique `S` of size `2` or
67
`\geq 4` in `LG` corresponds to a vertex of degree `|S|` in `G`, whose incident
68
edges are the elements of `S` itself.
69
70
The main problem lies with maximal cliques of size 3, i.e. triangles. Those we
71
have to split into two categories, *even* and *odd* triangles :
72
73
A triangle `\{e_1,e_2,e_3\}\subseteq V(LG)` is said to be an *odd* triangle if
74
there exists a vertex `e\in V(G)` incident to exactly *one* or *all* of
75
`\{e_1,e_2,e_3\}`, and it is said to be *even* otherwise.
76
77
The very good point of this definition is that an inclusionwise maximal clique
78
which is an odd triangle will always correspond to a vertex of degree 3 in `G`,
79
while an even triangle could result from either a vertex of degree 3 in `G` or a
80
triangle in `G`. And in order to build the root graph we obviously have to
81
decide *which*.
82
83
Beineke proves in [Beineke70]_ that the collection of sets we are looking for
84
can be easily found. Indeed it turns out that it is the union of :
85
86
#. The family of all maximal cliques of `LG` of size 2 or `\geq 4`, as well as
87
all odd triangles.
88
89
#. The family of all pairs of adjacent vertices which appear in exactly *one*
90
maximal clique which is an even triangle.
91
92
There are actually four special cases to which the decomposition above does not
93
apply, i.e. graphs containing an edge which belongs to exactly two even
94
triangles. We deal with those independently.
95
96
* The :meth:`Complete graph
97
<sage.graphs.graph_generators.GraphGenerators.CompleteGraph>` `K_3`.
98
99
* The :meth:`Diamond graph
100
<sage.graphs.graph_generators.GraphGenerators.DiamondGraph>` -- the line graph
101
of `K_{1,3}` plus an edge.
102
103
* The :meth:`Wheel graph
104
<sage.graphs.graph_generators.GraphGenerators.WheelGraph>` on `4+1` vertices
105
-- the line graph of the :meth:`Diamond graph
106
<sage.graphs.graph_generators.GraphGenerators.DiamondGraph>`
107
108
* The :meth:`Octahedron
109
<sage.graphs.graph_generators.GraphGenerators.OctahedralGraph>` -- the line
110
graph of `K_4`.
111
112
This decomposition turns out to be very easy to implement :-)
113
114
.. WARNING::
115
116
Even though the root graph is *NOT UNIQUE* for the triangle, this method
117
returns `K_{1,3}` (and not `K_3`) in this case. Pay *very close* attention
118
to that, for this answer is not theoretically correct : there is no unique
119
answer in this case, and we deal with it by returning one of the two
120
possible answers.
121
122
.. [Whitney32] Congruent graphs and the connectivity of graphs,
123
Whitney,
124
American Journal of Mathematics,
125
pages 150--168, 1932,
126
`available on JSTOR <http://www.jstor.org/stable/2371086>`_
127
128
.. [Harary69] Graph Theory,
129
Harary,
130
Addison-Wesley, 1969
131
132
.. [Beineke70] Lowell Beineke,
133
Characterizations of derived graphs,
134
Journal of Combinatorial Theory,
135
Vol. 9(2), pages 129-135, 1970
136
http://dx.doi.org/10.1016/S0021-9800(70)80019-9
137
138
Functions
139
---------
140
"""
141
142
def is_line_graph(g, certificate = False):
143
r"""
144
Tests wether the graph is a line graph.
145
146
INPUT:
147
148
- ``certificate`` (boolean) -- whether to return a certificate along with
149
the boolean result. Here is what happens when ``certificate = True``:
150
151
- If the graph is not a line graph, the method returns a pair ``(b,
152
subgraph)`` where ``b`` is ``False`` and ``subgraph`` is a subgraph
153
isomorphic to one of the 9 forbidden induced subgraphs of a line graph.
154
155
- If the graph is a line graph, the method returns a triple ``(b,R,isom)``
156
where ``b`` is ``True``, ``R`` is a graph whose line graph is the graph
157
given as input, and ``isom`` is a map associating an edge of ``R`` to
158
each vertex of the graph.
159
160
.. TODO::
161
162
This method sequentially tests each of the forbidden subgraphs in order
163
to know whether the graph is a line graph, which is a very slow
164
method. It could eventually be replaced by
165
:func:`~sage.graphs.line_graph.root_graph` when this method will not
166
require an exponential time to run on general graphs anymore (see its
167
documentation for more information on this problem)... and if it can be
168
improved to return negative certificates !
169
170
.. NOTE::
171
172
This method wastes a bit of time when the input graph is not
173
connected. If you have performance in mind, it is probably better to
174
only feed it with connected graphs only.
175
176
.. SEEALSO::
177
178
- The :mod:`line_graph <sage.graphs.line_graph>` module.
179
180
- :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs`
181
-- the forbidden subgraphs of a line graph.
182
183
- :meth:`~sage.graphs.generic_graph.GenericGraph.line_graph`
184
185
EXAMPLES:
186
187
A complete graph is always the line graph of a star::
188
189
sage: graphs.CompleteGraph(5).is_line_graph()
190
True
191
192
The Petersen Graph not being claw-free, it is not a line
193
graph:
194
195
sage: graphs.PetersenGraph().is_line_graph()
196
False
197
198
This is indeed the subgraph returned::
199
200
sage: C = graphs.PetersenGraph().is_line_graph(certificate = True)[1]
201
sage: C.is_isomorphic(graphs.ClawGraph())
202
True
203
204
The house graph is a line graph::
205
206
sage: g = graphs.HouseGraph()
207
sage: g.is_line_graph()
208
True
209
210
But what is the graph whose line graph is the house ?::
211
212
sage: is_line, R, isom = g.is_line_graph(certificate = True)
213
sage: R.sparse6_string()
214
':DaHI~'
215
sage: R.show()
216
sage: isom
217
{0: (0, 1), 1: (0, 2), 2: (1, 3), 3: (2, 3), 4: (3, 4)}
218
219
TESTS:
220
221
Disconnected graphs::
222
223
sage: g = 2*graphs.CycleGraph(3)
224
sage: gl = g.line_graph().relabel(inplace = False)
225
sage: new_g = gl.is_line_graph(certificate = True)[1]
226
sage: g.line_graph().is_isomorphic(gl)
227
True
228
"""
229
g._scream_if_not_simple()
230
from sage.graphs.graph_generators import graphs
231
232
for fg in graphs.line_graph_forbidden_subgraphs():
233
h = g.subgraph_search(fg, induced = True)
234
if h is not None:
235
if certificate:
236
return (False,h)
237
else:
238
return False
239
240
if not certificate:
241
return True
242
243
if g.is_connected():
244
R, isom = root_graph(g)
245
else:
246
from sage.graphs.graph import Graph
247
R = Graph()
248
249
for gg in g.connected_components_subgraphs():
250
RR, _ = root_graph(gg)
251
R = R + RR
252
253
_, isom = g.is_isomorphic(R.line_graph(labels = False), certify = True)
254
255
return (True, R, isom)
256
257
def line_graph(self, labels=True):
258
"""
259
Returns the line graph of the (di)graph.
260
261
INPUT:
262
263
- ``labels`` (boolean) -- whether edge labels should be taken in
264
consideration. If ``labels=True``, the vertices of the line graph will be
265
triples ``(u,v,label)``, and pairs of vertices otherwise.
266
267
This is set to ``True`` by default.
268
269
The line graph of an undirected graph G is an undirected graph H such that
270
the vertices of H are the edges of G and two vertices e and f of H are
271
adjacent if e and f share a common vertex in G. In other words, an edge in H
272
represents a path of length 2 in G.
273
274
The line graph of a directed graph G is a directed graph H such that the
275
vertices of H are the edges of G and two vertices e and f of H are adjacent
276
if e and f share a common vertex in G and the terminal vertex of e is the
277
initial vertex of f. In other words, an edge in H represents a (directed)
278
path of length 2 in G.
279
280
.. NOTE::
281
282
As a :class:`Graph` object only accepts hashable objects as vertices
283
(and as the vertices of the line graph are the edges of the graph), this
284
code will fail if edge labels are not hashable. You can also set the
285
argument ``labels=False`` to ignore labels.
286
287
.. SEEALSO::
288
289
- The :mod:`line_graph <sage.graphs.line_graph>` module.
290
291
- :meth:`~sage.graphs.graph_generators.GraphGenerators.line_graph_forbidden_subgraphs`
292
-- the forbidden subgraphs of a line graph.
293
294
- :meth:`~Graph.is_line_graph` -- tests whether a graph is a line graph.
295
296
EXAMPLES::
297
298
sage: g = graphs.CompleteGraph(4)
299
sage: h = g.line_graph()
300
sage: h.vertices()
301
[(0, 1, None),
302
(0, 2, None),
303
(0, 3, None),
304
(1, 2, None),
305
(1, 3, None),
306
(2, 3, None)]
307
sage: h.am()
308
[0 1 1 1 1 0]
309
[1 0 1 1 0 1]
310
[1 1 0 0 1 1]
311
[1 1 0 0 1 1]
312
[1 0 1 1 0 1]
313
[0 1 1 1 1 0]
314
sage: h2 = g.line_graph(labels=False)
315
sage: h2.vertices()
316
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
317
sage: h2.am() == h.am()
318
True
319
sage: g = DiGraph([[1..4],lambda i,j: i<j])
320
sage: h = g.line_graph()
321
sage: h.vertices()
322
[(1, 2, None),
323
(1, 3, None),
324
(1, 4, None),
325
(2, 3, None),
326
(2, 4, None),
327
(3, 4, None)]
328
sage: h.edges()
329
[((1, 2, None), (2, 3, None), None),
330
((1, 2, None), (2, 4, None), None),
331
((1, 3, None), (3, 4, None), None),
332
((2, 3, None), (3, 4, None), None)]
333
334
Tests:
335
336
:trac:`13787`::
337
338
sage: g = graphs.KneserGraph(7,1)
339
sage: C = graphs.CompleteGraph(7)
340
sage: C.is_isomorphic(g)
341
True
342
sage: C.line_graph().is_isomorphic(g.line_graph())
343
True
344
"""
345
self._scream_if_not_simple()
346
if self._directed:
347
from sage.graphs.digraph import DiGraph
348
G=DiGraph()
349
G.add_vertices(self.edges(labels=labels))
350
for v in self:
351
# Connect appropriate incident edges of the vertex v
352
G.add_edges([(e,f) for e in self.incoming_edge_iterator(v, labels=labels) \
353
for f in self.outgoing_edge_iterator(v, labels=labels)])
354
return G
355
else:
356
from sage.graphs.all import Graph
357
G=Graph()
358
359
# We must sort the edges' endpoints so that (1,2,None) is seen as
360
# the same edge as (2,1,None).
361
#
362
# We do so by comparing hashes, just in case all the natural order
363
# (<) on vertices would not be a total order (for instance when
364
# vertices are sets). If two adjacent vertices have the same hash,
365
# then we store the pair in the dictionary of conflicts
366
367
conflicts = {}
368
369
# 1) List of vertices in the line graph
370
elist = []
371
for e in self.edge_iterator(labels = labels):
372
if hash(e[0]) < hash(e[1]):
373
elist.append(e)
374
elif hash(e[0]) > hash(e[1]):
375
elist.append((e[1],e[0])+e[2:])
376
else:
377
# Settle the conflict arbitrarily
378
conflicts[e] = e
379
conflicts[(e[1],e[0])+e[2:]] = e
380
elist.append(e)
381
382
G.add_vertices(elist)
383
384
# 2) adjacencies in the line graph
385
for v in self:
386
elist = []
387
388
# Add the edge to the list, according to hashes, as previously
389
for e in self.edge_iterator(v, labels=labels):
390
if hash(e[0]) < hash(e[1]):
391
elist.append(e)
392
elif hash(e[0]) > hash(e[1]):
393
elist.append((e[1],e[0])+e[2:])
394
else:
395
elist.append(conflicts[e])
396
397
# Alls pairs of elements in elist are edges of the
398
# line graph
399
while elist:
400
x = elist.pop()
401
for y in elist:
402
G.add_edge(x,y)
403
404
return G
405
406
def root_graph(g, verbose = False):
407
r"""
408
Computes the root graph corresponding to the given graph
409
410
See the documentation of :mod:`sage.graphs.line_graph` to know how it works.
411
412
INPUT:
413
414
- ``g`` -- a graph
415
416
- ``verbose`` (boolean) -- display some information about what is happening
417
inside of the algorithm.
418
419
.. NOTE::
420
421
It is best to use this code through
422
:meth:`~sage.graphs.graph.Graph.is_line_graph`, which first checks that
423
the graph is indeed a line graph, and deals with the disconnected
424
case. But if you are sure of yourself, dig in !
425
426
.. WARNING::
427
428
* This code assumes that the graph is connected.
429
430
* If the graph is *not* a line graph, this implementation will take a
431
loooooong time to run. Its first step is to enumerate all maximal
432
cliques, and that can take a while for general graphs. As soon as
433
there is a way to iterate over maximal cliques without first building
434
the (long) list of them this implementation can be updated, and will
435
deal reasonably with non-line graphs too !
436
437
TESTS:
438
439
All connected graphs on 6 vertices::
440
441
sage: from sage.graphs.line_graph import root_graph
442
sage: def test(g):
443
... gl = g.line_graph(labels = False)
444
... d=root_graph(gl)
445
sage: for i,g in enumerate(graphs(6)): # long time
446
... if not g.is_connected(): # long time
447
... continue # long time
448
... test(g) # long time
449
450
Non line-graphs::
451
452
sage: root_graph(graphs.PetersenGraph())
453
Traceback (most recent call last):
454
...
455
ValueError: This graph is not a line graph !
456
457
Small corner-cases::
458
459
sage: from sage.graphs.line_graph import root_graph
460
sage: root_graph(graphs.CompleteGraph(3))
461
(Complete bipartite graph: Graph on 4 vertices, {0: (0, 1), 1: (0, 2), 2: (0, 3)})
462
sage: root_graph(graphs.OctahedralGraph())
463
(Complete graph: Graph on 4 vertices, {0: (0, 1), 1: (0, 2), 2: (0, 3), 3: (1, 2), 4: (1, 3), 5: (2, 3)})
464
sage: root_graph(graphs.DiamondGraph())
465
(Graph on 4 vertices, {0: (0, 3), 1: (0, 1), 2: (0, 2), 3: (1, 2)})
466
sage: root_graph(graphs.WheelGraph(5))
467
(Diamond Graph: Graph on 4 vertices, {0: (1, 2), 1: (0, 1), 2: (0, 2), 3: (2, 3), 4: (1, 3)})
468
"""
469
from sage.graphs.digraph import DiGraph
470
471
if isinstance(g, DiGraph):
472
raise ValueError("g cannot be a DiGraph !")
473
if g.has_multiple_edges():
474
raise ValueError("g cannot have multiple edges !")
475
if not g.is_connected():
476
raise ValueError("g is not connected !")
477
478
# Complete Graph ?
479
if g.is_clique():
480
from sage.graphs.generators.basic import CompleteBipartiteGraph
481
return (CompleteBipartiteGraph(1,g.order()),
482
{v : (0,1+i) for i,v in enumerate(g)})
483
484
# Diamond Graph ?
485
elif g.order() == 4 and g.size() == 5:
486
from sage.graphs.graph import Graph
487
root = Graph([(0,1),(1,2),(2,0),(0,3)])
488
return (root,
489
g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
490
491
# Wheel on 5 vertices ?
492
elif g.order() == 5 and g.size() == 8 and min(g.degree()) == 3:
493
from sage.graphs.generators.basic import DiamondGraph
494
root = DiamondGraph()
495
return (root,
496
g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
497
498
# Octahedron ?
499
elif g.order() == 6 and g.size() == 12 and g.is_regular(k=4):
500
from sage.graphs.generators.platonic_solids import OctahedralGraph
501
if g.is_isomorphic(OctahedralGraph()):
502
from sage.graphs.generators.basic import CompleteGraph
503
root = CompleteGraph(4)
504
return (root,
505
g.is_isomorphic(root.line_graph(labels = False), certify = True)[1])
506
507
# From now on we can assume (thanks to Beineke) that no edge belongs to two
508
# even triangles at once.
509
510
error_message = ("It looks like there is a problem somewhere. You"
511
"found a bug here ! Please report it on sage-devel,"
512
"our google group !")
513
514
# Better to work on integers... Everything takes more time
515
# otherwise.
516
G = g.relabel(inplace = False)
517
518
# Dictionary of (pairs of) cliques, i.e. the two cliques
519
# associated with each vertex.
520
v_cliques = {v:[] for v in G}
521
522
# All the even triangles we meet
523
even_triangles = []
524
525
526
# Here is THE "problem" of this implementation. Listing all maximal cliques
527
# takes an exponential time on general graphs (while it is obviously
528
# polynomial on line graphs). The problem is that this implementation cannot
529
# be used to *recognise* line graphs for as long as cliques_maximal returns
530
# a list and does not ITERATE on the maximal cliques : if there are too many
531
# cliques in the graph, this implementation will notice it and answer that
532
# the graph is not a line graph. If, on the other hand, the first thing it
533
# does is enumerate ALL maximal cliques, then there is no way to say early
534
# that the graph is not a line graph.
535
#
536
# If this cliques_maximal thing is replaced by an iterator that does not
537
# build the list of all cliques before returning them, then this method is a
538
# good recognition algorithm.
539
540
for S in G.cliques_maximal():
541
542
# Triangles... even or odd ?
543
if len(S) == 3:
544
545
# If a vertex of G has an odd number of neighbors among the vertices
546
# of S, then the triangle is odd. We compute the list of such
547
# vertices by taking the symmetric difference of the neighborhood of
548
# our three vertices.
549
#
550
# Note that the elements of S do not appear in this set as they are
551
# all seen exactly twice.
552
553
odd_neighbors = set(G.neighbors(S[0]))
554
odd_neighbors.symmetric_difference_update(G.neighbors(S[1]))
555
odd_neighbors.symmetric_difference_update(G.neighbors(S[2]))
556
557
# Even triangles
558
if not odd_neighbors:
559
even_triangles.append(tuple(S))
560
continue
561
562
# We manage odd triangles the same way we manage other cliques ...
563
564
# We now associate the clique to all the vertices it contains.
565
for v in S:
566
if len(v_cliques[v]) == 2:
567
raise ValueError("This graph is not a line graph !")
568
v_cliques[v].append(tuple(S))
569
570
if verbose:
571
print "Added clique", S
572
573
# Deal with even triangles
574
for u,v,w in even_triangles:
575
576
# According to Beineke, we must go through all even triangles, and for
577
# each triangle uvw consider its three pairs of adjacent verties uv, vw,
578
# wu. For all pairs xy among those such that xy do not appear together
579
# in any clique we have found so far, we add xy to the list of cliques
580
# describing our covering.
581
582
for x,y in [(u,v), (v,w), (w,u)]:
583
584
# If edge xy does not appear in any of the cliques associated with y
585
if all([not x in C for C in v_cliques[y]]):
586
if len(v_cliques[y]) >= 2 or len(v_cliques[x]) >= 2:
587
raise ValueError("This graph is not a line graph !")
588
589
v_cliques[x].append((x,y))
590
v_cliques[y].append((x,y))
591
592
if verbose:
593
print "Adding pair",(x,y),"appearing in the even triangle", (u,v,w)
594
595
# Deal with vertices contained in only one clique. All edges must be defined
596
# by TWO endpoints, so we add a fake clique.
597
for x, clique_list in v_cliques.iteritems():
598
if len(clique_list) == 1:
599
clique_list.append((x,))
600
601
# We now have all our cliques. Let's build the root graph to check that it
602
# all fits !
603
from sage.graphs.graph import Graph
604
R = Graph()
605
606
# Associates an integer to each clique
607
relabel = {}
608
609
# Associates to each vertex of G its pair of coordinates in R
610
vertex_to_map = {}
611
612
for v,L in v_cliques.iteritems():
613
614
# Add cliques to relabel dictionary
615
for S in L:
616
if not S in relabel:
617
relabel[S] = len(relabel)
618
619
# The coordinates of edge v
620
vertex_to_map[v] = relabel[L[0]], relabel[L[1]]
621
622
if verbose:
623
print "Final associations :"
624
for v,L in v_cliques.iteritems():
625
print v,L
626
627
# We now build R
628
R.add_edges(vertex_to_map.values())
629
630
# Even if whatever is written above is complete nonsense, here we
631
# make sure that we do not return gibberish. Is the line graph of
632
# R isomorphic to the input ? If so, we return R, and the
633
# isomorphism. Else, we panic and scream.
634
#
635
# It's actually "just to make sure twice". This can be removed later if it
636
# turns out to be too costly.
637
is_isom, isom = g.is_isomorphic(R.line_graph(labels = False), certify = True)
638
639
if not is_isom:
640
raise Exception(error_message)
641
642
return R, isom
643
644
645
646
647