Path: blob/master/src/sage/graphs/convexity_properties.pyx
8815 views
r"""1Convexity properties of graphs23This class gathers the algorithms related to convexity in a graph. It implements4the following methods:56.. csv-table::7:class: contentstable8:widths: 30, 709:delim: |1011:meth:`ConvexityProperties.hull` | Returns the convex hull of a set of vertices12:meth:`ConvexityProperties.hull_number` | Computes the hull number of a graph and a corresponding generating set.1314These methods can be used through the :class:`ConvexityProperties` object15returned by :meth:`Graph.convexity_properties`.1617AUTHORS:1819- Nathann Cohen2021Methods22-------23"""2425##############################################################################26# Copyright (C) 2011 Nathann Cohen <[email protected]>27# Distributed under the terms of the GNU General Public License (GPL)28# The full text of the GPL is available at:29# http://www.gnu.org/licenses/30##############################################################################3132include "sage/misc/bitset.pxi"33from sage.numerical.backends.generic_backend cimport GenericBackend34from sage.numerical.backends.generic_backend import get_solver3536cdef class ConvexityProperties:37r"""38This class gathers the algorithms related to convexity in a graph.3940**Definitions**4142A set `S \subseteq V(G)` of vertices is said to be convex if for all `u,v\in43S` the set `S` contains all the vertices located on a shortest path between44`u` and `v`. Alternatively, a set `S` is said to be convex if the distances45satisfy `\forall u,v\in S, \forall w\in V\backslash S : d_{G}(u,w) +46d_{G}(w,v) > d_{G}(u,v)`.4748The convex hull `h(S)` of a set `S` of vertices is defined as the smallest49convex set containing `S`.5051It is a closure operator, as trivially `S\subseteq h(S)` and `h(h(S)) =52h(S)`.5354**What this class contains**5556As operations on convex sets generally involve the computation of distances57between vertices, this class' purpose is to cache that information so that58computing the convex hulls of several different sets of vertices does not59imply recomputing several times the distances between the vertices.6061In order to compute the convex hull of a set `S` it is possible to write the62following algorithm.6364*For any pair `u,v` of elements in the set `S`, and for any vertex `w`*65*outside of it, add `w` to `S` if `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`.*66*When no vertex can be added anymore, the set `S` is convex*6768The distances are not actually that relevant. The same algorithm can be69implemented by remembering for each pair `u, v` of vertices the list of70elements `w` satisfying the condition, and this is precisely what this class71remembers, encoded as bitsets to make storage and union operations more72efficient.7374.. NOTE::7576* This class is useful if you compute the convex hulls of many sets in77the same graph, or if you want to compute the hull number itself as it78involves many calls to :meth:`hull`7980* Using this class on non-conected graphs is a waste of space and81efficiency ! If your graph is disconnected, the best for you is to82deal independently with each connected component, whatever you are83doing.8485**Possible improvements**8687When computing a convex set, all the pairs of elements belonging to the set88`S` are enumerated several times.8990* There should be a smart way to avoid enumerating pairs of vertices which91have already been tested. The cost of each of them is not very high, so92keeping track of those which have been tested already may be too expensive93to gain any efficiency.9495* The ordering in which they are visited is currently purely lexicographic,96while there is a Poset structure to exploit. In particular, when two97vertices `u, v` are far apart and generate a set `h(\{u,v\})` of vertices,98all the pairs of vertices `u', v'\in h(\{u,v\})` satisfy `h(\{u',v'\})99\subseteq h(\{u,v\})`, and so it is useless to test the pair `u', v'` when100both `u` and `v` where present.101102* The information cached is for any pair `u,v` of vertices the list of103elements `z` with `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`. This is not in104general equal to `h(\{u,v\})` !105106Nothing says these recommandations will actually lead to any actual107improvements. There are just some ideas remembered while writing this108code. Trying to optimize may well lead to lost in efficiency on many109instances.110111EXAMPLE::112113sage: from sage.graphs.convexity_properties import ConvexityProperties114sage: g = graphs.PetersenGraph()115sage: CP = ConvexityProperties(g)116sage: CP.hull([1,3])117[1, 2, 3]118sage: CP.hull_number()1193120121TESTS::122123sage: ConvexityProperties(digraphs.Circuit(5))124Traceback (most recent call last):125...126ValueError: This is currenly implemented for Graphs only.Only minor updates are needed if you want to makeit support DiGraphs too.127"""128129def __init__(self, G):130r"""131Constructor132133EXAMPLE::134135sage: from sage.graphs.convexity_properties import ConvexityProperties136sage: g = graphs.PetersenGraph()137sage: ConvexityProperties(g)138<sage.graphs.convexity_properties.ConvexityProperties object at ...>139"""140from sage.graphs.digraph import DiGraph141if isinstance(G, DiGraph):142raise ValueError("This is currenly implemented for Graphs only."+143"Only minor updates are needed if you want to make"+144"it support DiGraphs too.")145146# Cached number of vertices147cdef int n = G.order()148self._n = n149150cdef int i = 0151cdef int j,k152153# Temporary variables154cdef dict d_i155cdef dict d_j156cdef int d_ij157self._dict_vertices_to_integers = {}158self._list_integers_to_vertices = []159160# Remembering integers instead of the labels, and building dictionaries161# in both directions.162for v in G:163self._dict_vertices_to_integers[v] = i164self._list_integers_to_vertices.append(v)165i = i + 1166167168# Computation of distances between all pairs. Costly.169cdef dict distances = G.distance_all_pairs()170171# _cache_hull_pairs[u*n + v] is a bitset whose 1 bits are the vertices located on a shortest path from vertex u to v172#173# Note that u < v174self._cache_hull_pairs = <bitset_t *> sage_malloc(((n*(n-1))>>1)*sizeof(bitset_t))175cdef bitset_t * p_bitset = self._cache_hull_pairs176177# Filling the cache178#179# The p_bitset variable iterates over the successive elements of the cache180#181# For any pair i,j of vertices (i<j), we built the bitset of all the182# elements k which are on a shortest path from i to j183184for 0<= i < n-1:185# Caching the distances from i to the other vertices186d_i = distances[self._list_integers_to_vertices[i]]187188for i < j < n:189# Caching the distances from j to the other vertices190d_j = distances[self._list_integers_to_vertices[j]]191192# Caching the distance between i and j193d_ij = d_i[self._list_integers_to_vertices[j]]194195# Initializing the new bitset196bitset_init(p_bitset[0], n)197bitset_set_first_n(p_bitset[0], 0)198199# Filling it200for 0<= k < n:201if ((d_i[self._list_integers_to_vertices[k]]202+ d_j[self._list_integers_to_vertices[k]])203== d_ij):204bitset_add(p_bitset[0], k)205206# Next bitset !207p_bitset = p_bitset + 1208209210def __destruct__(self):211r"""212Destructor213214EXAMPLE::215216sage: from sage.graphs.convexity_properties import ConvexityProperties217sage: g = graphs.PetersenGraph()218sage: ConvexityProperties(g)219<sage.graphs.convexity_properties.ConvexityProperties object at ...>220221"""222cdef bitset_t * p_bitset = self._cache_hull_pairs223cdef int i224225for 0 <= i < ((self._n*(self._n-1))>>1):226bitset_free(p_bitset[0])227p_bitset = p_bitset + 1228229sage_free(self._cache_hull_pairs)230231cdef list _vertices_to_integers(self, vertices):232r"""233Converts a list of vertices to a list of integers with the cached data.234"""235cdef list answer = []236for v in v:237answer.append(self._dict_vertices_to_integers[v])238return answer239240cdef list _integers_to_vertices(self, integers):241r"""242Converts a list of integers to a list of vertices with the cached data.243"""244245cdef list answer = []246for v in integers:247answer.append(self._list_integers_to_vertices[v])248return answer249250cdef _bitset_convex_hull(self, bitset_t hull):251r"""252Computes the convex hull of a list of vertices given as a bitset.253254(this method returns nothing and modifies the input)255"""256cdef int count257cdef int tmp_count258cdef int i,j259260cdef bitset_t * p_bitset261262# Current size of the set263count = bitset_len(hull)264265while True:266267# Iterating over all the elements in the cache268p_bitset = self._cache_hull_pairs269270# For any vertex i271for 0 <= i < self._n-1:272273# If i is not in the current set, we skip it !274if not bitset_in(hull, i):275p_bitset = p_bitset + (self._n-1-i)276continue277278# If it is, we iterate over all the elements j279for i < j < self._n:280281# If both i and j are inside, we add all the (cached)282# vertices on a shortest ij-path283284if bitset_in(hull, j):285bitset_union(hull, hull, p_bitset[0])286287# Next bitset !288p_bitset = p_bitset + 1289290291tmp_count = bitset_len(hull)292293# If we added nothing new during the previous loop, our set is294# convex !295if tmp_count == count:296return297298# Otherwise, update and back to the loop299count = tmp_count300301cpdef hull(self, list vertices):302r"""303Returns the convex hull of a set of vertices.304305INPUT:306307* ``vertices`` -- A list of vertices.308309EXAMPLE::310311sage: from sage.graphs.convexity_properties import ConvexityProperties312sage: g = graphs.PetersenGraph()313sage: CP = ConvexityProperties(g)314sage: CP.hull([1,3])315[1, 2, 3]316"""317cdef bitset_t bs318bitset_init(bs, self._n)319bitset_set_first_n(bs, 0)320321for v in vertices:322bitset_add(bs, self._dict_vertices_to_integers[v])323324self._bitset_convex_hull(bs)325326#cdef list answer = bitset_list(bs)327cdef list answer = self._integers_to_vertices(bitset_list(bs))328329bitset_free(bs)330331return answer332333cdef _greedy_increase(self, bitset_t bs):334r"""335Given a bitset whose hull is not the whole set, greedily add vertices336and stop before its hull is the whole set.337338NOTE:339340* Counting the bits at each turn is not the best way...341"""342cdef bitset_t tmp343bitset_init(tmp, self._n)344345346for 0<= i < self._n:347if not bitset_in(bs, i):348bitset_copy(tmp, bs)349bitset_add(tmp, i)350self._bitset_convex_hull(tmp)351if bitset_len(tmp) < self._n:352bitset_add(bs, i)353354355cpdef hull_number(self, value_only = True, verbose = False):356r"""357Computes the hull number and a corresponding generating set.358359The hull number `hn(G)` of a graph `G` is the cardinality of a smallest360set of vertices `S` such that `h(S)=V(G)`.361362INPUT:363364* ``value_only`` (boolean) -- whether to return only the hull number365(default) or a minimum set whose convex hull is the whole graph.366367* ``verbose`` (boolean) -- whether to display information on the LP.368369**COMPLEXITY:**370371This problem is NP-Hard [CHZ02]_, but seems to be of the "nice"372kind. Update this comment if you fall on hard instances `:-)`373374**ALGORITHM:**375376This is solved by linear programming.377378As the function `h(S)` associating to each set `S` its convex hull is a379closure operator, it is clear that any set `S_G` of vertices such that380`h(S_G)=V(G)` must satisfy `S_G \not \subseteq C` for any *proper*381convex set `C \subsetneq V(G)`. The following formulation is hence382correct383384.. MATH::385386\text{Minimize :}& \sum_{v\in G}b_v\\387\text{Such that :}&\\388&\forall C\subsetneq V(G)\text{ a proper convex set }\\389&\sum_{v\in V(G)\backslash C} b_v \geq 1390391Of course, the number of convex sets -- and so the number of constraints392-- can be huge, and hard to enumerate, so at first an incomplete393formulation is solved (it is missing some constraints). If the answer394returned by the LP solver is a set `S` generating the whole graph, then395it is optimal and so is returned. Otherwise, the constraint396corresponding to the set `h(S)` can be added to the LP, which makes the397answer `S` infeasible, and another solution computed.398399This being said, simply adding the constraint corresponding to `h(S)` is400a bit slow, as these sets can be large (and the corresponding constrait401a bit weak). To improve it a bit, before being added, the set `h(S)` is402"greedily enriched" to a set `S'` with vertices for as long as403`h(S')\neq V(G)`. This way, we obtain a set `S'` with `h(S)\subseteq404h(S')\subsetneq V(G)`, and the constraint corresponding to `h(S')` --405which is stronger than the one corresponding to `h(S)` -- is added.406407This can actually be seen as a hitting set problem on the complement of408convex sets.409410EXAMPLE:411412The Hull number of Petersen's graph::413414sage: from sage.graphs.convexity_properties import ConvexityProperties415sage: g = graphs.PetersenGraph()416sage: CP = ConvexityProperties(g)417sage: CP.hull_number()4183419sage: generating_set = CP.hull_number(value_only = False)420sage: CP.hull(generating_set)421[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]422423REFERENCE:424425.. [CHZ02] F. Harary, E. Loukakis, C. Tsouros426The geodetic number of a graph427Mathematical and computer modelling428vol. 17 n11 pp.89--95, 1993429"""430431cdef int i432cdef list constraint # temporary variable to add constraints to the LP433434if self._n <= 2:435if value_only:436return self._n437else:438return self._list_integers_to_vertices439440cdef GenericBackend p = <GenericBackend> get_solver(constraint_generation = True)441442# Minimization443p.set_sense(False)444445# We have exactly n binary variables, all of them with a coefficient of446# 1 in the objective function447p.add_variables(self._n, 0, None, True, False, False, 1, None)448449# We know that at least 2 vertices are required to cover the whole graph450p.add_linear_constraint(zip(range(self._n), [1]*self._n), 2, None)451452# The set of vertices generated by the current LP solution453cdef bitset_t current_hull454bitset_init(current_hull, self._n)455456# Which is at first empty457bitset_set_first_n(current_hull,1)458459while True:460461# Greedily increase it to obtain a better constraint462self._greedy_increase(current_hull)463464if verbose:465print "Adding a constraint corresponding to convex set ",466print bitset_list(current_hull)467468# Building the corresponding constraint469constraint = []470for 0 <= i < self._n:471if not bitset_in(current_hull, i):472constraint.append((i,1))473474p.add_linear_constraint(constraint, 1, None)475476p.solve()477478# Computing the current solution's convex hull479bitset_set_first_n(current_hull,0)480481for 0 <= i < self._n:482if p.get_variable_value(i) > .5:483bitset_add(current_hull, i)484485self._bitset_convex_hull(current_hull)486487# Are we done ?488if bitset_len(current_hull) == self._n:489break490491bitset_free(current_hull)492493if value_only:494return <int> p.get_objective_value()495496constraint = []497for 0 <= i < self._n:498if p.get_variable_value(i) > .5:499constraint.append(i)500501return self._integers_to_vertices(constraint)502503504