Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/graph_decompositions/vertex_separation.pyx
8815 views
1
r"""
2
Vertex separation
3
4
This module implements several algorithms to compute the vertex separation of a
5
digraph and the corresponding ordering of the vertices. It also implements tests
6
functions for evaluation the width of a linear ordering.
7
8
Given an ordering
9
`v_1,\cdots, v_n` of the vertices of `V(G)`, its *cost* is defined as:
10
11
.. MATH::
12
13
c(v_1, ..., v_n) = \max_{1\leq i \leq n} c'(\{v_1, ..., v_i\})
14
15
Where
16
17
.. MATH::
18
19
c'(S) = |N^+_G(S)\backslash S|
20
21
The *vertex separation* of a digraph `G` is equal to the minimum cost of an
22
ordering of its vertices.
23
24
**Vertex separation and pathwidth**
25
26
The vertex separation is defined on a digraph, but one can obtain from a graph
27
`G` a digraph `D` with the same vertex set, and in which each edge `uv` of `G`
28
is replaced by two edges `uv` and `vu` in `D`. The vertex separation of `D` is
29
equal to the pathwidth of `G`, and the corresponding ordering of the vertices of
30
`D`, also called a *layout*, encodes an optimal path-decomposition of `G`.
31
This is a result of Kinnersley [Kin92]_ and Bodlaender [Bod98]_.
32
33
34
**This module contains the following methods**
35
36
.. csv-table::
37
:class: contentstable
38
:widths: 30, 70
39
:delim: |
40
41
:meth:`path_decomposition` | Returns the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition
42
:meth:`vertex_separation` | Returns an optimal ordering of the vertices and its cost for vertex-separation
43
:meth:`vertex_separation_MILP` | Computes the vertex separation of `G` and the optimal ordering of its vertices using an MILP formulation
44
:meth:`lower_bound` | Returns a lower bound on the vertex separation of `G`
45
:meth:`is_valid_ordering` | Test if the linear vertex ordering `L` is valid for (di)graph `G`
46
:meth:`width_of_path_decomposition` | Returns the width of the path decomposition induced by the linear ordering `L` of the vertices of `G`
47
48
49
Exponential algorithm for vertex separation
50
-------------------------------------------
51
52
In order to find an optimal ordering of the vertices for the vertex separation,
53
this algorithm tries to save time by computing the function `c'(S)` **at most
54
once** once for each of the sets `S\subseteq V(G)`. These values are stored in
55
an array of size `2^n` where reading the value of `c'(S)` or updating it can be
56
done in constant (and small) time.
57
58
Assuming that we can compute the cost of a set `S` and remember it, finding an
59
optimal ordering is an easy task. Indeed, we can think of the sequence `v_1,
60
..., v_n` of vertices as a sequence of *sets* `\{v_1\}, \{v_1,v_2\}, ...,
61
\{v_1,...,v_n\}`, whose cost is precisely `\max c'(\{v_1\}), c'(\{v_1,v_2\}),
62
... , c'(\{v_1,...,v_n\})`. Hence, when considering the digraph on the `2^n`
63
sets `S\subseteq V(G)` where there is an arc from `S` to `S'` if `S'=S\cap
64
\{v\}` for some `v` (that is, if the sets `S` and `S'` can be consecutive in a
65
sequence), an ordering of the vertices of `G` corresponds to a *path* from
66
`\emptyset` to `\{v_1,...,v_n\}`. In this setting, checking whether there exists
67
a ordering of cost less than `k` can be achieved by checking whether there
68
exists a directed path `\emptyset` to `\{v_1,...,v_n\}` using only sets of cost
69
less than `k`. This is just a depth-first-search, for each `k`.
70
71
**Lazy evaluation of** `c'`
72
73
In the previous algorithm, most of the time is actually spent on the computation
74
of `c'(S)` for each set `S\subseteq V(G)` -- i.e. `2^n` computations of
75
neighborhoods. This can be seen as a huge waste of time when noticing that it is
76
useless to know that the value `c'(S)` for a set `S` is less than `k` if all the
77
paths leading to `S` have a cost greater than `k`. For this reason, the value of
78
`c'(S)` is computed lazily during the depth-first search. Explanation :
79
80
When the depth-first search discovers a set of size less than `k`, the costs of
81
its out-neighbors (the potential sets that could follow it in the optimal
82
ordering) are evaluated. When an out-neighbor is found that has a cost smaller
83
than `k`, the depth-first search continues with this set, which is explored with
84
the hope that it could lead to a path toward `\{v_1,...,v_n\}`. On the other
85
hand, if an out-neighbour has a cost larger than `k` it is useless to attempt to
86
build a cheap sequence going though this set, and the exploration stops
87
there. This way, a large number of sets will never be evaluated and *a lot* of
88
computational time is saved this way.
89
90
Besides, some improvement is also made by "improving" the values found by
91
`c'`. Indeed, `c'(S)` is a lower bound on the cost of a sequence containing the
92
set `S`, but if all out-neighbors of `S` have a cost of `c'(S) + 5` then one
93
knows that having `S` in a sequence means a total cost of at least `c'(S) +
94
5`. For this reason, for each set `S` we store the value of `c'(S)`, and replace
95
it by `\max (c'(S), \min_{\text{next}})` (where `\min_{\text{next}}` is the
96
minimum of the costs of the out-neighbors of `S`) once the costs of these
97
out-neighbors have been evaluated by the algrithm.
98
99
.. NOTE::
100
101
Because of its current implementation, this algorithm only works on graphs
102
on less than 32 vertices. This can be changed to 64 if necessary, but 32
103
vertices already require 4GB of memory. Running it on 64 bits is not
104
expected to be doable by the computers of the next decade `:-D`
105
106
**Lower bound on the vertex separation**
107
108
One can obtain a lower bound on the vertex separation of a graph in exponential
109
time but *small* memory by computing once the cost of each set `S`. Indeed, the
110
cost of a sequence `v_1, ..., v_n` corresponding to sets `\{v_1\}, \{v_1,v_2\},
111
..., \{v_1,...,v_n\}` is
112
113
.. MATH::
114
115
\max c'(\{v_1\}),c'(\{v_1,v_2\}),...,c'(\{v_1,...,v_n\})\geq\max c'_1,...,c'_n
116
117
where `c_i` is the minimum cost of a set `S` on `i` vertices. Evaluating the
118
`c_i` can take time (and in particular more than the previous exact algorithm),
119
but it does not need much memory to run.
120
121
122
MILP formulation for the vertex separation
123
------------------------------------------
124
125
We describe below a mixed integer linear program (MILP) for determining an
126
optimal layout for the vertex separation of `G`, which is an improved version of
127
the formulation proposed in [SP10]_. It aims at building a sequence `S_t` of
128
sets such that an ordering `v_1, ..., v_n` of the vertices correspond to
129
`S_0=\{v_1\}, S_2=\{v_1,v_2\}, ..., S_{n-1}=\{v_1,...,v_n\}`.
130
131
**Variables:**
132
133
134
- `y_v^t` -- Variable set to 1 if `v\in S_t`, and 0 otherwise. The order of
135
`v` in the layout is the smallest `t` such that `y_v^t==1`.
136
137
- `u_v^t` -- Variable set to 1 if `v\not \in S_t` and `v` has an in-neighbor in
138
`S_t`. It is set to 0 otherwise.
139
140
- `x_v^t` -- Variable set to 1 if either `v\in S_t` or if `v` has an in-neighbor
141
in `S_t`. It is set to 0 otherwise.
142
143
- `z` -- Objective value to minimize. It is equal to the maximum over all step
144
`t` of the number of vertices such that `u_v^t==1`.
145
146
**MILP formulation:**
147
148
.. MATH::
149
:nowrap:
150
151
\begin{alignat}{2}
152
\intertext{Minimize:}
153
&z&\\
154
\intertext{Such that:}
155
x_v^t &\leq x_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
156
y_v^t &\leq y_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
157
y_v^t &\leq x_w^t& \forall v\in V,\ \forall w\in N^+(v),\ 0\leq t\leq n-1\\
158
\sum_{v \in V} y_v^{t} &= t+1& 0\leq t\leq n-1\\
159
x_v^t-y_v^t&\leq u_v^t & \forall v \in V,\ 0\leq t\leq n-1\\
160
\sum_{v \in V} u_v^t &\leq z& 0\leq t\leq n-1\\
161
0 \leq x_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
162
0 \leq u_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
163
y_v^t &\in \{0,1\}& \forall v\in V,\ 0\leq t\leq n-1\\
164
0 \leq z &\leq n&
165
\end{alignat}
166
167
The vertex separation of `G` is given by the value of `z`, and the order of
168
vertex `v` in the optimal layout is given by the smallest `t` for which
169
`y_v^t==1`.
170
171
REFERENCES
172
----------
173
174
.. [Bod98] *A partial k-arboretum of graphs with bounded treewidth*, Hans
175
L. Bodlaender, Theoretical Computer Science 209(1-2):1-45, 1998.
176
177
.. [Kin92] *The vertex separation number of a graph equals its path-width*,
178
Nancy G. Kinnersley, Information Processing Letters 42(6):345-350, 1992.
179
180
.. [SP10] *Lightpath Reconfiguration in WDM networks*, Fernando Solano and
181
Michal Pioro, IEEE/OSA Journal of Optical Communication and Networking
182
2(12):1010-1021, 2010.
183
184
185
AUTHORS
186
-------
187
188
- Nathann Cohen (2011-10): Initial version and exact exponential algorithm
189
190
- David Coudert (2012-04): MILP formulation and tests functions
191
192
193
194
METHODS
195
-------
196
"""
197
198
include 'sage/ext/stdsage.pxi'
199
include 'sage/ext/cdefs.pxi'
200
include 'sage/ext/interrupt.pxi'
201
include 'fast_digraph.pyx'
202
from libc.stdint cimport uint8_t, int8_t
203
204
#*****************************************************************************
205
# Copyright (C) 2011 Nathann Cohen <[email protected]>
206
#
207
# Distributed under the terms of the GNU General Public License (GPL)
208
# http://www.gnu.org/licenses/
209
#*****************************************************************************
210
211
###############
212
# Lower Bound #
213
###############
214
215
def lower_bound(G):
216
r"""
217
Returns a lower bound on the vertex separation of `G`
218
219
INPUT:
220
221
- ``G`` -- a digraph
222
223
OUTPUT:
224
225
A lower bound on the vertex separation of `D` (see the module's
226
documentation).
227
228
.. NOTE::
229
230
This method runs in exponential time but has no memory constraint.
231
232
233
EXAMPLE:
234
235
On a circuit::
236
237
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
238
sage: g = digraphs.Circuit(6)
239
sage: lower_bound(g)
240
1
241
242
TEST:
243
244
Given anything else than a DiGraph::
245
246
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
247
sage: g = graphs.CycleGraph(5)
248
sage: lower_bound(g)
249
Traceback (most recent call last):
250
...
251
ValueError: The parameter must be a DiGraph.
252
"""
253
from sage.graphs.digraph import DiGraph
254
if not isinstance(G, DiGraph):
255
raise ValueError("The parameter must be a DiGraph.")
256
257
if G.order() >= 32:
258
raise ValueError("The graph can have at most 31 vertices.")
259
260
cdef FastDigraph FD = FastDigraph(G)
261
cdef int * g = FD.graph
262
cdef int n = FD.n
263
264
# minimums[i] is means to store the value of c'_{i+1}
265
minimums = <uint8_t *> sage_malloc(sizeof(uint8_t)* n)
266
cdef unsigned int i
267
268
# They are initialized to n
269
for 0<= i< n:
270
minimums[i] = n
271
272
cdef uint8_t tmp, tmp_count
273
274
# We go through all sets
275
for 1<= i< <unsigned int> (1<<n):
276
tmp_count = <uint8_t> popcount32(i)
277
tmp = <uint8_t> compute_out_neighborhood_cardinality(FD, i)
278
279
# And update the costs
280
minimums[tmp_count-1] = minimum(minimums[tmp_count-1], tmp)
281
282
# We compute the maximum of all those values
283
for 1<= i< n:
284
minimums[0] = maximum(minimums[0], minimums[i])
285
286
cdef int min = minimums[0]
287
288
sage_free(minimums)
289
290
return min
291
292
################################
293
# Exact exponential algorithms #
294
################################
295
296
def path_decomposition(G, algorithm = "exponential", verbose = False):
297
r"""
298
Returns the pathwidth of the given graph and the ordering of the vertices
299
resulting in a corresponding path decomposition.
300
301
INPUT:
302
303
- ``G`` -- a digraph
304
305
- ``algorithm`` -- (default: ``"exponential"``) Specify the algorithm to use
306
among
307
308
- ``exponential`` -- Use an exponential time and space algorithm. This
309
algorithm only works of graphs on less than 32 vertices.
310
311
- ``MILP`` -- Use a mixed integer linear programming formulation. This
312
algorithm has no size restriction but could take a very long time.
313
314
- ``verbose`` (boolean) -- whether to display information on the
315
computations.
316
317
OUTPUT:
318
319
A pair ``(cost, ordering)`` representing the optimal ordering of the
320
vertices and its cost.
321
322
.. NOTE::
323
324
Because of its current implementation, this exponential algorithm only
325
works on graphs on less than 32 vertices. This can be changed to 54 if
326
necessary, but 32 vertices already require 4GB of memory.
327
328
EXAMPLE:
329
330
The vertex separation of a cycle is equal to 2::
331
332
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
333
sage: g = graphs.CycleGraph(6)
334
sage: pw, L = path_decomposition(g); pw
335
2
336
sage: pwm, Lm = path_decomposition(g, algorithm = "MILP"); pwm
337
2
338
339
TEST:
340
341
Given anything else than a Graph::
342
343
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
344
sage: g = digraphs.Circuit(6)
345
sage: path_decomposition(g)
346
Traceback (most recent call last):
347
...
348
ValueError: The parameter must be a Graph.
349
"""
350
from sage.graphs.graph import Graph
351
if not isinstance(G, Graph):
352
raise ValueError("The parameter must be a Graph.")
353
354
from sage.graphs.digraph import DiGraph
355
if algorithm == "exponential":
356
return vertex_separation(DiGraph(G), verbose = verbose)
357
else:
358
return vertex_separation_MILP(DiGraph(G), verbosity = (1 if verbose else 0))
359
360
361
def vertex_separation(G, verbose = False):
362
r"""
363
Returns an optimal ordering of the vertices and its cost for
364
vertex-separation.
365
366
INPUT:
367
368
- ``G`` -- a digraph
369
370
- ``verbose`` (boolean) -- whether to display information on the
371
computations.
372
373
OUTPUT:
374
375
A pair ``(cost, ordering)`` representing the optimal ordering of the
376
vertices and its cost.
377
378
.. NOTE::
379
380
Because of its current implementation, this algorithm only works on
381
graphs on less than 32 vertices. This can be changed to 54 if necessary,
382
but 32 vertices already require 4GB of memory.
383
384
EXAMPLE:
385
386
The vertex separation of a circuit is equal to 1::
387
388
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
389
sage: g = digraphs.Circuit(6)
390
sage: vertex_separation(g)
391
(1, [0, 1, 2, 3, 4, 5])
392
393
TEST:
394
395
Given anything else than a DiGraph::
396
397
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
398
sage: g = graphs.CycleGraph(5)
399
sage: lower_bound(g)
400
Traceback (most recent call last):
401
...
402
ValueError: The parameter must be a DiGraph.
403
404
Graphs with non-integer vertices::
405
406
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
407
sage: D=digraphs.DeBruijn(2,3)
408
sage: vertex_separation(D)
409
(2, ['000', '001', '100', '010', '101', '011', '110', '111'])
410
"""
411
from sage.graphs.digraph import DiGraph
412
if not isinstance(G, DiGraph):
413
raise ValueError("The parameter must be a DiGraph.")
414
415
if G.order() >= 32:
416
raise ValueError("The graph should have at most 31 vertices !")
417
418
cdef FastDigraph g = FastDigraph(G)
419
420
if verbose:
421
print "Memory allocation"
422
g.print_adjacency_matrix()
423
424
sig_on()
425
426
cdef unsigned int mem = 1 << g.n
427
cdef int8_t * neighborhoods = <int8_t *> sage_malloc(mem)
428
429
if neighborhoods == NULL:
430
sig_off()
431
raise MemoryError("Error allocating memory. I just tried to allocate "+str(mem>>10)+"MB, could that be too much ?")
432
433
memset(neighborhoods, <int8_t> -1, mem)
434
435
cdef int i,j , k
436
for 0 <= k <g.n:
437
if verbose:
438
print "Looking for a strategy of cost", str(k)
439
440
if exists(g, neighborhoods, 0, k) <= k:
441
break
442
443
if verbose:
444
print "... Found !"
445
print "Now computing the ordering"
446
447
cdef list order = find_order(g, neighborhoods, k)
448
449
# Relabelling the vertices
450
cdef list vertices = G.vertices()
451
for i, j in enumerate(order):
452
order[i] = vertices[j]
453
454
sage_free(neighborhoods)
455
sig_off()
456
457
return k, order
458
459
##############################################################################
460
# Actual algorithm, breadh-first search and updates of the costs of the sets #
461
##############################################################################
462
463
# Check whether an ordering with the given cost exists, and updates data in the
464
# neighborhoods array at the same time. See the module's documentation
465
466
cdef inline int exists(FastDigraph g, int8_t * neighborhoods, int current, int cost):
467
468
# If this is true, it means the set has not been evaluated yet
469
if neighborhoods[current] < 0:
470
neighborhoods[current] = compute_out_neighborhood_cardinality(g, current)
471
472
# If the cost of this set is too high, there is no point in going further.
473
# Same thing if the current set is the whole vertex set.
474
if neighborhoods[current] > cost or (current == (1<<g.n)-1):
475
return neighborhoods[current]
476
477
# Minimum of the costs of the outneighbors
478
cdef int mini = g.n
479
480
cdef int i
481
cdef int next_set
482
483
484
for 0<= i<g.n:
485
if (current >> i)&1:
486
continue
487
488
# For each of the out-neighbors next_set of current
489
490
next_set = current | 1<<i
491
492
# Check whether there exists a cheap path toward {1..n}, and updated the
493
# cost.
494
mini = minimum(mini, exists(g, neighborhoods, next_set, cost))
495
496
# We have found a path !
497
if mini <= cost:
498
return mini
499
500
# Updating the cost of the current set with the minimum of the cost of its
501
# outneighbors.
502
neighborhoods[current] = mini
503
504
return neighborhoods[current]
505
506
# Returns the ordering once we are sure it exists
507
cdef list find_order(FastDigraph g, int8_t * neighborhoods, int cost):
508
cdef list ordering = []
509
cdef int current = 0
510
cdef int n = g.n
511
cdef int i
512
513
while n:
514
# We look for n vertices
515
516
for 0<= i<g.n:
517
if (current >> i)&1:
518
continue
519
520
# Find the next set with small cost (we know it exists)
521
next_set = current | 1<<i
522
if neighborhoods[next_set] <= cost:
523
ordering.append(i)
524
current = next_set
525
break
526
527
# One less to find
528
n -= 1
529
530
return ordering
531
532
# Min/Max functions
533
534
cdef inline int minimum(int a, int b):
535
if a<b:
536
return a
537
else:
538
return b
539
540
cdef inline int maximum(int a, int b):
541
if a>b:
542
return a
543
else:
544
return b
545
546
547
#################################################################
548
# Function for testing the validity of a linear vertex ordering #
549
#################################################################
550
551
def is_valid_ordering(G, L):
552
r"""
553
Test if the linear vertex ordering `L` is valid for (di)graph `G`.
554
555
A linear ordering `L` of the vertices of a (di)graph `G` is valid if all
556
vertices of `G` are in `L`, and if `L` contains no other vertex and no
557
duplicated vertices.
558
559
INPUT:
560
561
- ``G`` -- a Graph or a DiGraph.
562
563
- ``L`` -- an ordered list of the vertices of ``G``.
564
565
566
OUTPUT:
567
568
Returns ``True`` if `L` is a valid vertex ordering for `G`, and ``False``
569
oterwise.
570
571
572
EXAMPLE:
573
574
Path decomposition of a cycle::
575
576
sage: from sage.graphs.graph_decompositions import vertex_separation
577
sage: G = graphs.CycleGraph(6)
578
sage: L = [u for u in G.vertices()]
579
sage: vertex_separation.is_valid_ordering(G, L)
580
True
581
sage: vertex_separation.is_valid_ordering(G, [1,2])
582
False
583
584
TEST:
585
586
Giving anything else than a Graph or a DiGraph::
587
588
sage: from sage.graphs.graph_decompositions import vertex_separation
589
sage: vertex_separation.is_valid_ordering(2, [])
590
Traceback (most recent call last):
591
...
592
ValueError: The input parameter must be a Graph or a DiGraph.
593
594
Giving anything else than a list::
595
596
sage: from sage.graphs.graph_decompositions import vertex_separation
597
sage: G = graphs.CycleGraph(6)
598
sage: vertex_separation.is_valid_ordering(G, {})
599
Traceback (most recent call last):
600
...
601
ValueError: The second parameter must be of type 'list'.
602
"""
603
from sage.graphs.graph import Graph
604
from sage.graphs.digraph import DiGraph
605
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
606
raise ValueError("The input parameter must be a Graph or a DiGraph.")
607
if not isinstance(L, list):
608
raise ValueError("The second parameter must be of type 'list'.")
609
610
return set(L) == set(G.vertices())
611
612
613
####################################################################
614
# Measurement functions of the widths of some graph decompositions #
615
####################################################################
616
617
def width_of_path_decomposition(G, L):
618
r"""
619
Returns the width of the path decomposition induced by the linear ordering
620
`L` of the vertices of `G`.
621
622
If `G` is an instance of :mod:`Graph <sage.graphs.graph>`, this function
623
returns the width `pw_L(G)` of the path decomposition induced by the linear
624
ordering `L` of the vertices of `G`. If `G` is a :mod:`DiGraph
625
<sage.graphs.digraph>`, it returns instead the width `vs_L(G)` of the
626
directed path decomposition induced by the linear ordering `L` of the
627
vertices of `G`, where
628
629
.. MATH::
630
631
vs_L(G) & = \max_{0\leq i< |V|-1} | N^+(L[:i])\setminus L[:i] |\\
632
pw_L(G) & = \max_{0\leq i< |V|-1} | N(L[:i])\setminus L[:i] |\\
633
634
INPUT:
635
636
- ``G`` -- a Graph or a DiGraph
637
638
- ``L`` -- a linear ordering of the vertices of ``G``
639
640
EXAMPLES:
641
642
Path decomposition of a cycle::
643
644
sage: from sage.graphs.graph_decompositions import vertex_separation
645
sage: G = graphs.CycleGraph(6)
646
sage: L = [u for u in G.vertices()]
647
sage: vertex_separation.width_of_path_decomposition(G, L)
648
2
649
650
Directed path decomposition of a circuit::
651
652
sage: from sage.graphs.graph_decompositions import vertex_separation
653
sage: G = digraphs.Circuit(6)
654
sage: L = [u for u in G.vertices()]
655
sage: vertex_separation.width_of_path_decomposition(G, L)
656
1
657
658
TESTS:
659
660
Path decomposition of a BalancedTree::
661
662
sage: from sage.graphs.graph_decompositions import vertex_separation
663
sage: G = graphs.BalancedTree(3,2)
664
sage: pw, L = vertex_separation.path_decomposition(G)
665
sage: pw == vertex_separation.width_of_path_decomposition(G, L)
666
True
667
sage: L.reverse()
668
sage: pw == vertex_separation.width_of_path_decomposition(G, L)
669
False
670
671
Directed path decomposition of a circuit::
672
673
sage: from sage.graphs.graph_decompositions import vertex_separation
674
sage: G = digraphs.Circuit(8)
675
sage: vs, L = vertex_separation.vertex_separation(G)
676
sage: vs == vertex_separation.width_of_path_decomposition(G, L)
677
True
678
sage: L = [0,4,6,3,1,5,2,7]
679
sage: vs == vertex_separation.width_of_path_decomposition(G, L)
680
False
681
682
Giving a wrong linear ordering::
683
684
sage: from sage.graphs.graph_decompositions import vertex_separation
685
sage: G = Graph()
686
sage: vertex_separation.width_of_path_decomposition(G, ['a','b'])
687
Traceback (most recent call last):
688
...
689
ValueError: The input linear vertex ordering L is not valid for G.
690
"""
691
if not is_valid_ordering(G, L):
692
raise ValueError("The input linear vertex ordering L is not valid for G.")
693
694
vsL = 0
695
S = set()
696
neighbors_of_S_in_V_minus_S = set()
697
698
for u in L:
699
700
# We remove u from the neighbors of S
701
neighbors_of_S_in_V_minus_S.discard(u)
702
703
# We add vertex u to the set S
704
S.add(u)
705
706
if G._directed:
707
Nu = G.neighbors_out(u)
708
else:
709
Nu = G.neighbors(u)
710
711
# We add the (out-)neighbors of u to the neighbors of S
712
for v in Nu:
713
if (not v in S):
714
neighbors_of_S_in_V_minus_S.add(v)
715
716
# We update the cost of the vertex separation
717
vsL = max( vsL, len(neighbors_of_S_in_V_minus_S) )
718
719
return vsL
720
721
722
##########################################
723
# MILP formulation for vertex separation #
724
##########################################
725
726
def vertex_separation_MILP(G, integrality = False, solver = None, verbosity = 0):
727
r"""
728
Computes the vertex separation of `G` and the optimal ordering of its
729
vertices using an MILP formulation.
730
731
This function uses a mixed integer linear program (MILP) for determining an
732
optimal layout for the vertex separation of `G`. This MILP is an improved
733
version of the formulation proposed in [SP10]_. See the :mod:`module's
734
documentation <sage.graphs.graph_decompositions.vertex_separation>` for more
735
details on this MILP formulation.
736
737
INPUTS:
738
739
- ``G`` -- a DiGraph
740
741
- ``integrality`` -- (default: ``False``) Specify if variables `x_v^t` and
742
`u_v^t` must be integral or if they can be relaxed. This has no impact on
743
the validity of the solution, but it is sometimes faster to solve the
744
problem using binary variables only.
745
746
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to
747
be used. If set to ``None``, the default one is used. For more information
748
on LP solvers and which default solver is used, see the method
749
:meth:`solve<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the
750
class
751
:class:`MixedIntegerLinearProgram<sage.numerical.mip.MixedIntegerLinearProgram>`.
752
753
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set
754
to 0 by default, which means quiet.
755
756
OUTPUT:
757
758
A pair ``(cost, ordering)`` representing the optimal ordering of the
759
vertices and its cost.
760
761
EXAMPLE:
762
763
Vertex separation of a De Bruijn digraph::
764
765
sage: from sage.graphs.graph_decompositions import vertex_separation
766
sage: G = digraphs.DeBruijn(2,3)
767
sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs
768
2
769
sage: vs == vertex_separation.width_of_path_decomposition(G, L)
770
True
771
sage: vse, Le = vertex_separation.vertex_separation(G); vse
772
2
773
774
The vertex separation of a circuit is 1::
775
776
sage: from sage.graphs.graph_decompositions import vertex_separation
777
sage: G = digraphs.Circuit(6)
778
sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs
779
1
780
781
TESTS:
782
783
Comparison with exponential algorithm::
784
785
sage: from sage.graphs.graph_decompositions import vertex_separation
786
sage: for i in range(10):
787
... G = digraphs.RandomDirectedGNP(10, 0.2)
788
... ve, le = vertex_separation.vertex_separation(G)
789
... vm, lm = vertex_separation.vertex_separation_MILP(G)
790
... if ve != vm:
791
... print "The solution is not optimal!"
792
793
Comparison with Different values of the integrality parameter::
794
795
sage: from sage.graphs.graph_decompositions import vertex_separation
796
sage: for i in range(10): # long time (11s on sage.math, 2012)
797
... G = digraphs.RandomDirectedGNP(10, 0.2)
798
... va, la = vertex_separation.vertex_separation_MILP(G, integrality = False)
799
... vb, lb = vertex_separation.vertex_separation_MILP(G, integrality = True)
800
... if va != vb:
801
... print "The integrality parameter change the result!"
802
803
Giving anything else than a DiGraph::
804
805
sage: from sage.graphs.graph_decompositions import vertex_separation
806
sage: vertex_separation.vertex_separation_MILP([])
807
Traceback (most recent call last):
808
...
809
ValueError: The first input parameter must be a DiGraph.
810
"""
811
from sage.graphs.digraph import DiGraph
812
if not isinstance(G, DiGraph):
813
raise ValueError("The first input parameter must be a DiGraph.")
814
815
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
816
p = MixedIntegerLinearProgram( maximization = False, solver = solver )
817
818
# Declaration of variables.
819
x = p.new_variable( binary = integrality, dim = 2 )
820
u = p.new_variable( binary = integrality, dim = 2 )
821
y = p.new_variable( binary = True, dim = 2 )
822
z = p.new_variable( integer = True, dim = 1 )
823
824
N = G.num_verts()
825
V = G.vertices()
826
827
# (2) x[v][t] <= x[v][t+1] for all v in V, and for t:=0..N-2
828
# (3) y[v][t] <= y[v][t+1] for all v in V, and for t:=0..N-2
829
for v in V:
830
for t in xrange(N-1):
831
p.add_constraint( x[v][t] - x[v][t+1] <= 0 )
832
p.add_constraint( y[v][t] - y[v][t+1] <= 0 )
833
834
# (4) y[v][t] <= x[w][t] for all v in V, for all w in N^+(v), and for all t:=0..N-1
835
for v in V:
836
for w in G.neighbors_out(v):
837
for t in xrange(N):
838
p.add_constraint( y[v][t] - x[w][t] <= 0 )
839
840
# (5) sum_{v in V} y[v][t] == t+1 for t:=0..N-1
841
for t in xrange(N):
842
p.add_constraint( p.sum([ y[v][t] for v in V ]) == t+1 )
843
844
# (6) u[v][t] >= x[v][t]-y[v][t] for all v in V, and for all t:=0..N-1
845
for v in V:
846
for t in xrange(N):
847
p.add_constraint( x[v][t] - y[v][t] - u[v][t] <= 0 )
848
849
# (7) z >= sum_{v in V} u[v][t] for all t:=0..N-1
850
for t in xrange(N):
851
p.add_constraint( p.sum([ u[v][t] for v in V ]) - z['z'] <= 0 )
852
853
# (8)(9) 0 <= x[v][t] and u[v][t] <= 1
854
if not integrality:
855
for v in V:
856
for t in xrange(N):
857
p.add_constraint( 0 <= x[v][t] <= 1 )
858
p.add_constraint( 0 <= u[v][t] <= 1 )
859
860
# (10) y[v][t] in {0,1}
861
p.set_binary( y )
862
863
# (11) 0 <= z <= |V|
864
p.add_constraint( z['z'] <= N )
865
866
# (1) Minimize z
867
p.set_objective( z['z'] )
868
869
try:
870
obj = p.solve( log=verbosity )
871
except MIPSolverException:
872
if integrality:
873
raise ValueError("Unbounded or unexpected error")
874
else:
875
raise ValueError("Unbounded or unexpected error. Try with 'integrality = True'.")
876
877
taby = p.get_values( y )
878
tabz = p.get_values( z )
879
# since exactly one vertex is processed per step, we can reconstruct the sequence
880
seq = []
881
for t in xrange(N):
882
for v in V:
883
if (taby[v][t] > 0) and (not v in seq):
884
seq.append(v)
885
break
886
vs = int(round( tabz['z'] ))
887
888
return vs, seq
889
890