Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/graphs/convexity_properties.pyx
8815 views
1
r"""
2
Convexity properties of graphs
3
4
This class gathers the algorithms related to convexity in a graph. It implements
5
the following methods:
6
7
.. csv-table::
8
:class: contentstable
9
:widths: 30, 70
10
:delim: |
11
12
:meth:`ConvexityProperties.hull` | Returns the convex hull of a set of vertices
13
:meth:`ConvexityProperties.hull_number` | Computes the hull number of a graph and a corresponding generating set.
14
15
These methods can be used through the :class:`ConvexityProperties` object
16
returned by :meth:`Graph.convexity_properties`.
17
18
AUTHORS:
19
20
- Nathann Cohen
21
22
Methods
23
-------
24
"""
25
26
##############################################################################
27
# Copyright (C) 2011 Nathann Cohen <[email protected]>
28
# Distributed under the terms of the GNU General Public License (GPL)
29
# The full text of the GPL is available at:
30
# http://www.gnu.org/licenses/
31
##############################################################################
32
33
include "sage/misc/bitset.pxi"
34
from sage.numerical.backends.generic_backend cimport GenericBackend
35
from sage.numerical.backends.generic_backend import get_solver
36
37
cdef class ConvexityProperties:
38
r"""
39
This class gathers the algorithms related to convexity in a graph.
40
41
**Definitions**
42
43
A set `S \subseteq V(G)` of vertices is said to be convex if for all `u,v\in
44
S` the set `S` contains all the vertices located on a shortest path between
45
`u` and `v`. Alternatively, a set `S` is said to be convex if the distances
46
satisfy `\forall u,v\in S, \forall w\in V\backslash S : d_{G}(u,w) +
47
d_{G}(w,v) > d_{G}(u,v)`.
48
49
The convex hull `h(S)` of a set `S` of vertices is defined as the smallest
50
convex set containing `S`.
51
52
It is a closure operator, as trivially `S\subseteq h(S)` and `h(h(S)) =
53
h(S)`.
54
55
**What this class contains**
56
57
As operations on convex sets generally involve the computation of distances
58
between vertices, this class' purpose is to cache that information so that
59
computing the convex hulls of several different sets of vertices does not
60
imply recomputing several times the distances between the vertices.
61
62
In order to compute the convex hull of a set `S` it is possible to write the
63
following algorithm.
64
65
*For any pair `u,v` of elements in the set `S`, and for any vertex `w`*
66
*outside of it, add `w` to `S` if `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`.*
67
*When no vertex can be added anymore, the set `S` is convex*
68
69
The distances are not actually that relevant. The same algorithm can be
70
implemented by remembering for each pair `u, v` of vertices the list of
71
elements `w` satisfying the condition, and this is precisely what this class
72
remembers, encoded as bitsets to make storage and union operations more
73
efficient.
74
75
.. NOTE::
76
77
* This class is useful if you compute the convex hulls of many sets in
78
the same graph, or if you want to compute the hull number itself as it
79
involves many calls to :meth:`hull`
80
81
* Using this class on non-conected graphs is a waste of space and
82
efficiency ! If your graph is disconnected, the best for you is to
83
deal independently with each connected component, whatever you are
84
doing.
85
86
**Possible improvements**
87
88
When computing a convex set, all the pairs of elements belonging to the set
89
`S` are enumerated several times.
90
91
* There should be a smart way to avoid enumerating pairs of vertices which
92
have already been tested. The cost of each of them is not very high, so
93
keeping track of those which have been tested already may be too expensive
94
to gain any efficiency.
95
96
* The ordering in which they are visited is currently purely lexicographic,
97
while there is a Poset structure to exploit. In particular, when two
98
vertices `u, v` are far apart and generate a set `h(\{u,v\})` of vertices,
99
all the pairs of vertices `u', v'\in h(\{u,v\})` satisfy `h(\{u',v'\})
100
\subseteq h(\{u,v\})`, and so it is useless to test the pair `u', v'` when
101
both `u` and `v` where present.
102
103
* The information cached is for any pair `u,v` of vertices the list of
104
elements `z` with `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`. This is not in
105
general equal to `h(\{u,v\})` !
106
107
Nothing says these recommandations will actually lead to any actual
108
improvements. There are just some ideas remembered while writing this
109
code. Trying to optimize may well lead to lost in efficiency on many
110
instances.
111
112
EXAMPLE::
113
114
sage: from sage.graphs.convexity_properties import ConvexityProperties
115
sage: g = graphs.PetersenGraph()
116
sage: CP = ConvexityProperties(g)
117
sage: CP.hull([1,3])
118
[1, 2, 3]
119
sage: CP.hull_number()
120
3
121
122
TESTS::
123
124
sage: ConvexityProperties(digraphs.Circuit(5))
125
Traceback (most recent call last):
126
...
127
ValueError: This is currenly implemented for Graphs only.Only minor updates are needed if you want to makeit support DiGraphs too.
128
"""
129
130
def __init__(self, G):
131
r"""
132
Constructor
133
134
EXAMPLE::
135
136
sage: from sage.graphs.convexity_properties import ConvexityProperties
137
sage: g = graphs.PetersenGraph()
138
sage: ConvexityProperties(g)
139
<sage.graphs.convexity_properties.ConvexityProperties object at ...>
140
"""
141
from sage.graphs.digraph import DiGraph
142
if isinstance(G, DiGraph):
143
raise ValueError("This is currenly implemented for Graphs only."+
144
"Only minor updates are needed if you want to make"+
145
"it support DiGraphs too.")
146
147
# Cached number of vertices
148
cdef int n = G.order()
149
self._n = n
150
151
cdef int i = 0
152
cdef int j,k
153
154
# Temporary variables
155
cdef dict d_i
156
cdef dict d_j
157
cdef int d_ij
158
self._dict_vertices_to_integers = {}
159
self._list_integers_to_vertices = []
160
161
# Remembering integers instead of the labels, and building dictionaries
162
# in both directions.
163
for v in G:
164
self._dict_vertices_to_integers[v] = i
165
self._list_integers_to_vertices.append(v)
166
i = i + 1
167
168
169
# Computation of distances between all pairs. Costly.
170
cdef dict distances = G.distance_all_pairs()
171
172
# _cache_hull_pairs[u*n + v] is a bitset whose 1 bits are the vertices located on a shortest path from vertex u to v
173
#
174
# Note that u < v
175
self._cache_hull_pairs = <bitset_t *> sage_malloc(((n*(n-1))>>1)*sizeof(bitset_t))
176
cdef bitset_t * p_bitset = self._cache_hull_pairs
177
178
# Filling the cache
179
#
180
# The p_bitset variable iterates over the successive elements of the cache
181
#
182
# For any pair i,j of vertices (i<j), we built the bitset of all the
183
# elements k which are on a shortest path from i to j
184
185
for 0<= i < n-1:
186
# Caching the distances from i to the other vertices
187
d_i = distances[self._list_integers_to_vertices[i]]
188
189
for i < j < n:
190
# Caching the distances from j to the other vertices
191
d_j = distances[self._list_integers_to_vertices[j]]
192
193
# Caching the distance between i and j
194
d_ij = d_i[self._list_integers_to_vertices[j]]
195
196
# Initializing the new bitset
197
bitset_init(p_bitset[0], n)
198
bitset_set_first_n(p_bitset[0], 0)
199
200
# Filling it
201
for 0<= k < n:
202
if ((d_i[self._list_integers_to_vertices[k]]
203
+ d_j[self._list_integers_to_vertices[k]])
204
== d_ij):
205
bitset_add(p_bitset[0], k)
206
207
# Next bitset !
208
p_bitset = p_bitset + 1
209
210
211
def __destruct__(self):
212
r"""
213
Destructor
214
215
EXAMPLE::
216
217
sage: from sage.graphs.convexity_properties import ConvexityProperties
218
sage: g = graphs.PetersenGraph()
219
sage: ConvexityProperties(g)
220
<sage.graphs.convexity_properties.ConvexityProperties object at ...>
221
222
"""
223
cdef bitset_t * p_bitset = self._cache_hull_pairs
224
cdef int i
225
226
for 0 <= i < ((self._n*(self._n-1))>>1):
227
bitset_free(p_bitset[0])
228
p_bitset = p_bitset + 1
229
230
sage_free(self._cache_hull_pairs)
231
232
cdef list _vertices_to_integers(self, vertices):
233
r"""
234
Converts a list of vertices to a list of integers with the cached data.
235
"""
236
cdef list answer = []
237
for v in v:
238
answer.append(self._dict_vertices_to_integers[v])
239
return answer
240
241
cdef list _integers_to_vertices(self, integers):
242
r"""
243
Converts a list of integers to a list of vertices with the cached data.
244
"""
245
246
cdef list answer = []
247
for v in integers:
248
answer.append(self._list_integers_to_vertices[v])
249
return answer
250
251
cdef _bitset_convex_hull(self, bitset_t hull):
252
r"""
253
Computes the convex hull of a list of vertices given as a bitset.
254
255
(this method returns nothing and modifies the input)
256
"""
257
cdef int count
258
cdef int tmp_count
259
cdef int i,j
260
261
cdef bitset_t * p_bitset
262
263
# Current size of the set
264
count = bitset_len(hull)
265
266
while True:
267
268
# Iterating over all the elements in the cache
269
p_bitset = self._cache_hull_pairs
270
271
# For any vertex i
272
for 0 <= i < self._n-1:
273
274
# If i is not in the current set, we skip it !
275
if not bitset_in(hull, i):
276
p_bitset = p_bitset + (self._n-1-i)
277
continue
278
279
# If it is, we iterate over all the elements j
280
for i < j < self._n:
281
282
# If both i and j are inside, we add all the (cached)
283
# vertices on a shortest ij-path
284
285
if bitset_in(hull, j):
286
bitset_union(hull, hull, p_bitset[0])
287
288
# Next bitset !
289
p_bitset = p_bitset + 1
290
291
292
tmp_count = bitset_len(hull)
293
294
# If we added nothing new during the previous loop, our set is
295
# convex !
296
if tmp_count == count:
297
return
298
299
# Otherwise, update and back to the loop
300
count = tmp_count
301
302
cpdef hull(self, list vertices):
303
r"""
304
Returns the convex hull of a set of vertices.
305
306
INPUT:
307
308
* ``vertices`` -- A list of vertices.
309
310
EXAMPLE::
311
312
sage: from sage.graphs.convexity_properties import ConvexityProperties
313
sage: g = graphs.PetersenGraph()
314
sage: CP = ConvexityProperties(g)
315
sage: CP.hull([1,3])
316
[1, 2, 3]
317
"""
318
cdef bitset_t bs
319
bitset_init(bs, self._n)
320
bitset_set_first_n(bs, 0)
321
322
for v in vertices:
323
bitset_add(bs, self._dict_vertices_to_integers[v])
324
325
self._bitset_convex_hull(bs)
326
327
#cdef list answer = bitset_list(bs)
328
cdef list answer = self._integers_to_vertices(bitset_list(bs))
329
330
bitset_free(bs)
331
332
return answer
333
334
cdef _greedy_increase(self, bitset_t bs):
335
r"""
336
Given a bitset whose hull is not the whole set, greedily add vertices
337
and stop before its hull is the whole set.
338
339
NOTE:
340
341
* Counting the bits at each turn is not the best way...
342
"""
343
cdef bitset_t tmp
344
bitset_init(tmp, self._n)
345
346
347
for 0<= i < self._n:
348
if not bitset_in(bs, i):
349
bitset_copy(tmp, bs)
350
bitset_add(tmp, i)
351
self._bitset_convex_hull(tmp)
352
if bitset_len(tmp) < self._n:
353
bitset_add(bs, i)
354
355
356
cpdef hull_number(self, value_only = True, verbose = False):
357
r"""
358
Computes the hull number and a corresponding generating set.
359
360
The hull number `hn(G)` of a graph `G` is the cardinality of a smallest
361
set of vertices `S` such that `h(S)=V(G)`.
362
363
INPUT:
364
365
* ``value_only`` (boolean) -- whether to return only the hull number
366
(default) or a minimum set whose convex hull is the whole graph.
367
368
* ``verbose`` (boolean) -- whether to display information on the LP.
369
370
**COMPLEXITY:**
371
372
This problem is NP-Hard [CHZ02]_, but seems to be of the "nice"
373
kind. Update this comment if you fall on hard instances `:-)`
374
375
**ALGORITHM:**
376
377
This is solved by linear programming.
378
379
As the function `h(S)` associating to each set `S` its convex hull is a
380
closure operator, it is clear that any set `S_G` of vertices such that
381
`h(S_G)=V(G)` must satisfy `S_G \not \subseteq C` for any *proper*
382
convex set `C \subsetneq V(G)`. The following formulation is hence
383
correct
384
385
.. MATH::
386
387
\text{Minimize :}& \sum_{v\in G}b_v\\
388
\text{Such that :}&\\
389
&\forall C\subsetneq V(G)\text{ a proper convex set }\\
390
&\sum_{v\in V(G)\backslash C} b_v \geq 1
391
392
Of course, the number of convex sets -- and so the number of constraints
393
-- can be huge, and hard to enumerate, so at first an incomplete
394
formulation is solved (it is missing some constraints). If the answer
395
returned by the LP solver is a set `S` generating the whole graph, then
396
it is optimal and so is returned. Otherwise, the constraint
397
corresponding to the set `h(S)` can be added to the LP, which makes the
398
answer `S` infeasible, and another solution computed.
399
400
This being said, simply adding the constraint corresponding to `h(S)` is
401
a bit slow, as these sets can be large (and the corresponding constrait
402
a bit weak). To improve it a bit, before being added, the set `h(S)` is
403
"greedily enriched" to a set `S'` with vertices for as long as
404
`h(S')\neq V(G)`. This way, we obtain a set `S'` with `h(S)\subseteq
405
h(S')\subsetneq V(G)`, and the constraint corresponding to `h(S')` --
406
which is stronger than the one corresponding to `h(S)` -- is added.
407
408
This can actually be seen as a hitting set problem on the complement of
409
convex sets.
410
411
EXAMPLE:
412
413
The Hull number of Petersen's graph::
414
415
sage: from sage.graphs.convexity_properties import ConvexityProperties
416
sage: g = graphs.PetersenGraph()
417
sage: CP = ConvexityProperties(g)
418
sage: CP.hull_number()
419
3
420
sage: generating_set = CP.hull_number(value_only = False)
421
sage: CP.hull(generating_set)
422
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
423
424
REFERENCE:
425
426
.. [CHZ02] F. Harary, E. Loukakis, C. Tsouros
427
The geodetic number of a graph
428
Mathematical and computer modelling
429
vol. 17 n11 pp.89--95, 1993
430
"""
431
432
cdef int i
433
cdef list constraint # temporary variable to add constraints to the LP
434
435
if self._n <= 2:
436
if value_only:
437
return self._n
438
else:
439
return self._list_integers_to_vertices
440
441
cdef GenericBackend p = <GenericBackend> get_solver(constraint_generation = True)
442
443
# Minimization
444
p.set_sense(False)
445
446
# We have exactly n binary variables, all of them with a coefficient of
447
# 1 in the objective function
448
p.add_variables(self._n, 0, None, True, False, False, 1, None)
449
450
# We know that at least 2 vertices are required to cover the whole graph
451
p.add_linear_constraint(zip(range(self._n), [1]*self._n), 2, None)
452
453
# The set of vertices generated by the current LP solution
454
cdef bitset_t current_hull
455
bitset_init(current_hull, self._n)
456
457
# Which is at first empty
458
bitset_set_first_n(current_hull,1)
459
460
while True:
461
462
# Greedily increase it to obtain a better constraint
463
self._greedy_increase(current_hull)
464
465
if verbose:
466
print "Adding a constraint corresponding to convex set ",
467
print bitset_list(current_hull)
468
469
# Building the corresponding constraint
470
constraint = []
471
for 0 <= i < self._n:
472
if not bitset_in(current_hull, i):
473
constraint.append((i,1))
474
475
p.add_linear_constraint(constraint, 1, None)
476
477
p.solve()
478
479
# Computing the current solution's convex hull
480
bitset_set_first_n(current_hull,0)
481
482
for 0 <= i < self._n:
483
if p.get_variable_value(i) > .5:
484
bitset_add(current_hull, i)
485
486
self._bitset_convex_hull(current_hull)
487
488
# Are we done ?
489
if bitset_len(current_hull) == self._n:
490
break
491
492
bitset_free(current_hull)
493
494
if value_only:
495
return <int> p.get_objective_value()
496
497
constraint = []
498
for 0 <= i < self._n:
499
if p.get_variable_value(i) > .5:
500
constraint.append(i)
501
502
return self._integers_to_vertices(constraint)
503
504