Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/linearextensions.py
8815 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
dag_copy = dag.copy(immutable=False)
83
le = []
84
a = []
85
b = []
86
87
#The preprocessing routine found in Figure 7 of
88
#"Generating Linear Extensions Fast" by
89
#Pruesse and Ruskey
90
while dag_copy.num_verts() != 0:
91
#Find all the minimal elements of dag_copy
92
minimial_elements = []
93
for node in dag_copy.vertices():
94
if len(dag_copy.incoming_edges(node)) == 0:
95
minimial_elements.append(node)
96
if len(minimial_elements) == 1:
97
le.append(minimial_elements[0])
98
dag_copy.delete_vertex(minimial_elements[0])
99
else:
100
ap = minimial_elements[0]
101
bp = minimial_elements[1]
102
a.append(ap)
103
b.append(bp)
104
le.append(ap)
105
le.append(bp)
106
dag_copy.delete_vertex(ap)
107
dag_copy.delete_vertex(bp)
108
self.max_pair = len(a) - 1
109
110
self.le = le
111
self.a = a
112
self.b = b
113
self.dag = dag
114
self.mrb = 0
115
self.mra = 0
116
self.is_plus = True
117
self.linear_extensions = None
118
self._name = "Linear extensions of %s"%dag
119
120
121
def switch(self, i):
122
"""
123
This implements the Switch procedure described on page 7
124
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
125
126
If i == -1, then the sign is changed. If i > 0, then self.a[i]
127
and self.b[i] are transposed.
128
129
Note that this meant to be called by the generate_linear_extensions
130
method and is not meant to be used directly.
131
132
EXAMPLES::
133
134
sage: from sage.graphs.linearextensions import LinearExtensions
135
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
136
sage: l = LinearExtensions(D)
137
sage: _ = l.list()
138
sage: l.le = [0, 1, 2, 3, 4]
139
sage: l.is_plus
140
True
141
sage: l.switch(-1)
142
sage: l.is_plus
143
False
144
sage: l.a
145
[1, 4]
146
sage: l.b
147
[2, 3]
148
sage: l.switch(0)
149
sage: l.le
150
[0, 2, 1, 3, 4]
151
sage: l.a
152
[2, 4]
153
sage: l.b
154
[1, 3]
155
156
157
"""
158
if i == -1:
159
self.is_plus = not self.is_plus
160
if i >= 0:
161
a_index = self.le.index(self.a[i])
162
b_index = self.le.index(self.b[i])
163
self.le[a_index] = self.b[i]
164
self.le[b_index] = self.a[i]
165
166
self.b[i], self.a[i] = self.a[i], self.b[i]
167
168
if self.is_plus:
169
self.linear_extensions.append(self.le[:])
170
171
172
def move(self, element, direction):
173
"""
174
This implements the Move procedure described on page 7
175
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
176
177
If direction is "left", then this transposes element with the
178
element on its left. If the direction is "right", then this
179
transposes element with the element on its right.
180
181
Note that this is meant to be called by the generate_linear_extensions
182
method and is not meant to be used directly.
183
184
EXAMPLES::
185
186
sage: from sage.graphs.linearextensions import LinearExtensions
187
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
188
sage: l = LinearExtensions(D)
189
sage: _ = l.list()
190
sage: l.le = [0, 1, 2, 3, 4]
191
sage: l.move(1, "left")
192
sage: l.le
193
[1, 0, 2, 3, 4]
194
sage: l.move(1, "right")
195
sage: l.le
196
[0, 1, 2, 3, 4]
197
198
"""
199
index = self.le.index(element)
200
if direction == "right":
201
self.le[index] = self.le[index+1]
202
self.le[index+1] = element
203
elif direction == "left":
204
self.le[index] = self.le[index-1]
205
self.le[index-1] = element
206
else:
207
print "Bad direction!"
208
sys.exit()
209
if self.is_plus:
210
self.linear_extensions.append(self.le[:])
211
212
213
def right(self, i, letter):
214
"""
215
If letter =="b", then this returns True if and only if
216
self.b[i] is incomparable with the elements to its right
217
in self.le. If letter == "a", then it returns True if
218
and only if self.a[i] is incomparable with the element to its
219
right in self.le and the element to the right is not
220
self.b[i]
221
222
This is the Right function described on page 8 of
223
"Generating Linear Extensions Fast" by Pruesse and Ruskey.
224
225
Note that this is meant to be called by the generate_linear_extensions
226
method and is not meant to be used directly.
227
228
EXAMPLES::
229
230
sage: from sage.graphs.linearextensions import LinearExtensions
231
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
232
sage: l = LinearExtensions(D)
233
sage: _ = l.list()
234
sage: l.le
235
[0, 1, 2, 4, 3]
236
sage: l.a
237
[1, 4]
238
sage: l.b
239
[2, 3]
240
sage: l.right(0, "a")
241
False
242
sage: l.right(1, "a")
243
False
244
sage: l.right(0, "b")
245
False
246
sage: l.right(1, "b")
247
False
248
249
"""
250
if letter == "a":
251
x = self.a[i]
252
yindex = self.le.index(x) + 1
253
if yindex >= len(self.le):
254
return False
255
y = self.le[ yindex ]
256
if self.incomparable(x,y) and y != self.b[i]:
257
return True
258
return False
259
elif letter == "b":
260
x = self.b[i]
261
yindex = self.le.index(x) + 1
262
if yindex >= len(self.le):
263
return False
264
y = self.le[ yindex ]
265
if self.incomparable(x,y):
266
return True
267
return False
268
else:
269
raise ValueError, "Bad letter!"
270
271
def generate_linear_extensions(self, i):
272
"""
273
This a Python version of the GenLE routine found in Figure 8
274
of "Generating Linear Extensions Fast" by Pruesse and Ruskey.
275
276
Note that this is meant to be called by the list
277
method and is not meant to be used directly.
278
279
EXAMPLES::
280
281
sage: from sage.graphs.linearextensions import LinearExtensions
282
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
283
sage: l = LinearExtensions(D)
284
sage: l.linear_extensions = []
285
sage: l.linear_extensions.append(l.le[:])
286
sage: l.generate_linear_extensions(l.max_pair)
287
sage: l.linear_extensions
288
[[0, 1, 2, 3, 4], [0, 2, 1, 3, 4]]
289
290
"""
291
if i >= 0:
292
self.generate_linear_extensions(i-1)
293
mrb = 0
294
typical = False
295
while self.right(i, "b"):
296
mrb += 1
297
self.move(self.b[i], "right")
298
self.generate_linear_extensions(i-1)
299
mra = 0
300
if self.right(i, "a"):
301
typical = True
302
cont = True
303
while cont:
304
mra += 1
305
self.move(self.a[i], "right")
306
self.generate_linear_extensions(i-1)
307
cont = self.right(i, "a")
308
if typical:
309
self.switch(i-1)
310
self.generate_linear_extensions(i-1)
311
if mrb % 2 == 1:
312
mla = mra -1
313
else:
314
mla = mra + 1
315
for x in range(mla):
316
self.move(self.a[i], "left")
317
self.generate_linear_extensions(i-1)
318
319
if typical and (mrb % 2 == 1):
320
self.move(self.a[i], "left")
321
else:
322
self.switch(i-1)
323
self.generate_linear_extensions(i-1)
324
for x in range(mrb):
325
self.move(self.b[i], "left")
326
self.generate_linear_extensions(i-1)
327
328
def list(self):
329
"""
330
Returns a list of the linear extensions of the directed acyclic graph.
331
332
Note that once they are computed, the linear extensions are
333
cached in this object.
334
335
EXAMPLES::
336
337
sage: from sage.graphs.linearextensions import LinearExtensions
338
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
339
sage: LinearExtensions(D).list()
340
[[0, 1, 2, 3, 4],
341
[0, 1, 2, 4, 3],
342
[0, 2, 1, 3, 4],
343
[0, 2, 1, 4, 3],
344
[0, 2, 4, 1, 3]]
345
"""
346
if self.linear_extensions is not None:
347
return self.linear_extensions[:]
348
349
self.linear_extensions = []
350
self.linear_extensions.append(self.le[:])
351
self.generate_linear_extensions(self.max_pair)
352
self.switch(self.max_pair)
353
self.generate_linear_extensions(self.max_pair)
354
self.linear_extensions.sort()
355
return self.linear_extensions[:]
356
357
358
def incomparable(self, x, y):
359
"""
360
Returns True if vertices x and y are incomparable in the directed
361
acyclic graph when thought of as a poset.
362
363
EXAMPLES::
364
365
sage: from sage.graphs.linearextensions import LinearExtensions
366
sage: D = DiGraph({ 0:[1,2], 1:[3], 2:[3,4] })
367
sage: l = LinearExtensions(D)
368
sage: l.incomparable(0,1)
369
False
370
sage: l.incomparable(1,2)
371
True
372
"""
373
if (not self.dag.shortest_path(x, y)) and (not self.dag.shortest_path(y, x)):
374
return True
375
return False
376
377