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