Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/linearextensions.py
4057 views
1
"""
2
Linear Extensions of Directed Acyclic Graphs.
3
4
A linear extension of a directed acyclic graph is a total (linear) ordering on
5
the vertices that is compatible with the graph in the following sense:
6
if there is a path from x to y in the graph, the x appears before y in the
7
linear extension.
8
9
The algorithm implemented in this module is from "Generating Linear Extensions
10
Fast" by Preusse and Ruskey, which can be found at
11
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.52.3057 . The algorithm
12
generates the extensions in constant amortized time (CAT) -- a constant amount
13
of time per extension generated, or linear in the number of extensions
14
generated.
15
16
EXAMPLES:
17
18
Here we generate the 5 linear extensions of the following directed
19
acyclic graph::
20
21
sage: from sage.graphs.linearextensions import LinearExtensions
22
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
23
sage: D.is_directed_acyclic()
24
True
25
sage: LinearExtensions(D).list()
26
[[0, 1, 2, 3, 4],
27
[0, 1, 2, 4, 3],
28
[0, 2, 1, 3, 4],
29
[0, 2, 1, 4, 3],
30
[0, 2, 4, 1, 3]]
31
32
Notice how all of the total orders are compatible with the ordering
33
induced from the graph.
34
35
We can also get at the linear extensions directly from the graph. From
36
the graph, the linear extensions are known as topological sorts ::
37
38
sage: D.topological_sort_generator()
39
[[0, 1, 2, 3, 4],
40
[0, 1, 2, 4, 3],
41
[0, 2, 1, 3, 4],
42
[0, 2, 1, 4, 3],
43
[0, 2, 4, 1, 3]]
44
45
46
"""
47
#*****************************************************************************
48
# Copyright (C) 2008 Mike Hansen <[email protected]>
49
#
50
# Distributed under the terms of the GNU General Public License (GPL)
51
# http://www.gnu.org/licenses/
52
#*****************************************************************************
53
from sage.combinat.combinat import CombinatorialClass
54
import sys
55
56
class LinearExtensions(CombinatorialClass):
57
def __init__(self, dag):
58
r"""
59
Creates an object representing the class of all linear extensions
60
of the directed acyclic graph \code{dag}.
61
62
Note that upon construction of this object some pre-computation is
63
done. This is the "preprocessing routine" found in Figure 7 of
64
"Generating Linear Extensions Fast" by Preusse and Ruskey.
65
66
This is an in-place algorithm and the list self.le keeps track
67
of the current linear extensions. The boolean variable self.is_plus
68
keeps track of the "sign".
69
70
EXAMPLES::
71
72
sage: from sage.graphs.linearextensions import LinearExtensions
73
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
74
sage: l = LinearExtensions(D)
75
sage: l == loads(dumps(l))
76
True
77
78
"""
79
################
80
#Precomputation#
81
################
82
from copy import copy
83
dag_copy = copy(dag)
84
le = []
85
a = []
86
b = []
87
88
#The preprocessing routine found in Figure 7 of
89
#"Generating Linear Extensions Fast" by
90
#Pruesse and Ruskey
91
while dag_copy.num_verts() != 0:
92
#Find all the minimal elements of dag_copy
93
minimial_elements = []
94
for node in dag_copy.vertices():
95
if len(dag_copy.incoming_edges(node)) == 0:
96
minimial_elements.append(node)
97
if len(minimial_elements) == 1:
98
le.append(minimial_elements[0])
99
dag_copy.delete_vertex(minimial_elements[0])
100
else:
101
ap = minimial_elements[0]
102
bp = minimial_elements[1]
103
a.append(ap)
104
b.append(bp)
105
le.append(ap)
106
le.append(bp)
107
dag_copy.delete_vertex(ap)
108
dag_copy.delete_vertex(bp)
109
self.max_pair = len(a) - 1
110
111
self.le = le
112
self.a = a
113
self.b = b
114
self.dag = dag
115
self.mrb = 0
116
self.mra = 0
117
self.is_plus = True
118
self.linear_extensions = None
119
self._name = "Linear extensions of %s"%dag
120
121
122
def switch(self, i):
123
"""
124
This implements the Switch procedure described on page 7
125
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
126
127
If i == -1, then the sign is changed. If i > 0, then self.a[i]
128
and self.b[i] are transposed.
129
130
Note that this meant to be called by the generate_linear_extensions
131
method and is not meant to be used directly.
132
133
EXAMPLES::
134
135
sage: from sage.graphs.linearextensions import LinearExtensions
136
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
137
sage: l = LinearExtensions(D)
138
sage: _ = l.list()
139
sage: l.le = [0, 1, 2, 3, 4]
140
sage: l.is_plus
141
True
142
sage: l.switch(-1)
143
sage: l.is_plus
144
False
145
sage: l.a
146
[1, 4]
147
sage: l.b
148
[2, 3]
149
sage: l.switch(0)
150
sage: l.le
151
[0, 2, 1, 3, 4]
152
sage: l.a
153
[2, 4]
154
sage: l.b
155
[1, 3]
156
157
158
"""
159
if i == -1:
160
self.is_plus = not self.is_plus
161
if i >= 0:
162
a_index = self.le.index(self.a[i])
163
b_index = self.le.index(self.b[i])
164
self.le[a_index] = self.b[i]
165
self.le[b_index] = self.a[i]
166
167
self.b[i], self.a[i] = self.a[i], self.b[i]
168
169
if self.is_plus:
170
self.linear_extensions.append(self.le[:])
171
172
173
def move(self, element, direction):
174
"""
175
This implements the Move procedure described on page 7
176
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
177
178
If direction is "left", then this transposes element with the
179
element on its left. If the direction is "right", then this
180
transposes element with the element on its right.
181
182
Note that this is meant to be called by the generate_linear_extensions
183
method and is not meant to be used directly.
184
185
EXAMPLES::
186
187
sage: from sage.graphs.linearextensions import LinearExtensions
188
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
189
sage: l = LinearExtensions(D)
190
sage: _ = l.list()
191
sage: l.le = [0, 1, 2, 3, 4]
192
sage: l.move(1, "left")
193
sage: l.le
194
[1, 0, 2, 3, 4]
195
sage: l.move(1, "right")
196
sage: l.le
197
[0, 1, 2, 3, 4]
198
199
"""
200
index = self.le.index(element)
201
if direction == "right":
202
self.le[index] = self.le[index+1]
203
self.le[index+1] = element
204
elif direction == "left":
205
self.le[index] = self.le[index-1]
206
self.le[index-1] = element
207
else:
208
print "Bad direction!"
209
sys.exit()
210
if self.is_plus:
211
self.linear_extensions.append(self.le[:])
212
213
214
def right(self, i, letter):
215
"""
216
If letter =="b", then this returns True if and only if
217
self.b[i] is incomparable with the elements to its right
218
in self.le. If letter == "a", then it returns True if
219
and only if self.a[i] is incomparable with the element to its
220
right in self.le and the element to the right is not
221
self.b[i]
222
223
This is the Right function described on page 8 of
224
"Generating Linear Extensions Fast" by Pruesse and Ruskey.
225
226
Note that this is meant to be called by the generate_linear_extensions
227
method and is not meant to be used directly.
228
229
EXAMPLES::
230
231
sage: from sage.graphs.linearextensions import LinearExtensions
232
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
233
sage: l = LinearExtensions(D)
234
sage: _ = l.list()
235
sage: l.le
236
[0, 1, 2, 4, 3]
237
sage: l.a
238
[1, 4]
239
sage: l.b
240
[2, 3]
241
sage: l.right(0, "a")
242
False
243
sage: l.right(1, "a")
244
False
245
sage: l.right(0, "b")
246
False
247
sage: l.right(1, "b")
248
False
249
250
"""
251
if letter == "a":
252
x = self.a[i]
253
yindex = self.le.index(x) + 1
254
if yindex >= len(self.le):
255
return False
256
y = self.le[ yindex ]
257
if self.incomparable(x,y) and y != self.b[i]:
258
return True
259
return False
260
elif letter == "b":
261
x = self.b[i]
262
yindex = self.le.index(x) + 1
263
if yindex >= len(self.le):
264
return False
265
y = self.le[ yindex ]
266
if self.incomparable(x,y):
267
return True
268
return False
269
else:
270
raise ValueError, "Bad letter!"
271
272
def generate_linear_extensions(self, i):
273
"""
274
This a Python version of the GenLE routine found in Figure 8
275
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
276
277
Note that this is meant to be called by the list
278
method and is not meant to be used directly.
279
280
EXAMPLES::
281
282
sage: from sage.graphs.linearextensions import LinearExtensions
283
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
284
sage: l = LinearExtensions(D)
285
sage: l.linear_extensions = []
286
sage: l.linear_extensions.append(l.le[:])
287
sage: l.generate_linear_extensions(l.max_pair)
288
sage: l.linear_extensions
289
[[0, 1, 2, 3, 4], [0, 2, 1, 3, 4]]
290
291
"""
292
if i >= 0:
293
self.generate_linear_extensions(i-1)
294
mrb = 0
295
typical = False
296
while self.right(i, "b"):
297
mrb += 1
298
self.move(self.b[i], "right")
299
self.generate_linear_extensions(i-1)
300
mra = 0
301
if self.right(i, "a"):
302
typical = True
303
cont = True
304
while cont:
305
mra += 1
306
self.move(self.a[i], "right")
307
self.generate_linear_extensions(i-1)
308
cont = self.right(i, "a")
309
if typical:
310
self.switch(i-1)
311
self.generate_linear_extensions(i-1)
312
if mrb % 2 == 1:
313
mla = mra -1
314
else:
315
mla = mra + 1
316
for x in range(mla):
317
self.move(self.a[i], "left")
318
self.generate_linear_extensions(i-1)
319
320
if typical and (mrb % 2 == 1):
321
self.move(self.a[i], "left")
322
else:
323
self.switch(i-1)
324
self.generate_linear_extensions(i-1)
325
for x in range(mrb):
326
self.move(self.b[i], "left")
327
self.generate_linear_extensions(i-1)
328
329
def list(self):
330
"""
331
Returns a list of the linear extensions of the directed acyclic graph.
332
333
Note that once they are computed, the linear extensions are
334
cached in this object.
335
336
EXAMPLES::
337
338
sage: from sage.graphs.linearextensions import LinearExtensions
339
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
340
sage: LinearExtensions(D).list()
341
[[0, 1, 2, 3, 4],
342
[0, 1, 2, 4, 3],
343
[0, 2, 1, 3, 4],
344
[0, 2, 1, 4, 3],
345
[0, 2, 4, 1, 3]]
346
"""
347
if self.linear_extensions is not None:
348
return self.linear_extensions[:]
349
350
self.linear_extensions = []
351
self.linear_extensions.append(self.le[:])
352
self.generate_linear_extensions(self.max_pair)
353
self.switch(self.max_pair)
354
self.generate_linear_extensions(self.max_pair)
355
self.linear_extensions.sort()
356
return self.linear_extensions[:]
357
358
359
def incomparable(self, x, y):
360
"""
361
Returns True if vertices x and y are incomparable in the directed
362
acyclic graph when thought of as a poset.
363
364
EXAMPLES::
365
366
sage: from sage.graphs.linearextensions import LinearExtensions
367
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
368
sage: l = LinearExtensions(D)
369
sage: l.incomparable(0,1)
370
False
371
sage: l.incomparable(1,2)
372
True
373
"""
374
if (not self.dag.shortest_path(x, y)) and (not self.dag.shortest_path(y, x)):
375
return True
376
return False
377
378