Path: blob/master/sage/graphs/convexity_properties.pyx
4079 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"""2425include "../misc/bitset.pxi"26from sage.numerical.backends.generic_backend cimport GenericBackend27from sage.numerical.backends.generic_backend import get_solver2829cdef class ConvexityProperties:30r"""31This class gathers the algorithms related to convexity in a graph.3233**Definitions**3435A set `S \subseteq V(G)` of vertices is said to be convex if for all `u,v\in36S` the set `S` contains all the vertices located on a shortest path between37`u` and `v`. Alternatively, a set `S` is said to be convex if the distances38satisfy `\forall u,v\in S, \forall w\in V\backslash S : d_{G}(u,w) +39d_{G}(w,v) > d_{G}(u,v)`.4041The convex hull `h(S)` of a set `S` of vertices is defined as the smallest42convex set containing `S`.4344It is a closure operator, as trivially `S\subseteq h(S)` and `h(h(S)) =45h(S)`.4647**What this class contains**4849As operations on convex sets generally involve the computation of distances50between vertices, this class' purpose is to cache that information so that51computing the convex hulls of several different sets of vertices does not52imply recomputing several times the distances between the vertices.5354In order to compute the convex hull of a set `S` it is possible to write the55following algorithm.5657*For any pair `u,v` of elements in the set `S`, and for any vertex `w`*58*outside of it, add `w` to `S` if `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`.*59*When no vertex can be added anymore, the set `S` is convex*6061The distances are not actually that relevant. The same algorithm can be62implemented by remembering for each pair `u, v` of vertices the list of63elements `w` satisfying the condition, and this is precisely what this class64remembers, encoded as bitsets to make storage and union operations more65efficient.6667.. NOTE::6869* This class is useful if you compute the convex hulls of many sets in70the same graph, or if you want to compute the hull number itself as it71involves many calls to :meth:`hull`7273* Using this class on non-conected graphs is a waste of space and74efficiency ! If your graph is disconnected, the best for you is to75deal independently with each connected component, whatever you are76doing.7778**Possible improvements**7980When computing a convex set, all the pairs of elements belonging to the set81`S` are enumerated several times.8283* There should be a smart way to avoid enumerating pairs of vertices which84have already been tested. The cost of each of them is not very high, so85keeping track of those which have been tested already may be too expensive86to gain any efficiency.8788* The ordering in which they are visited is currently purely lexicographic,89while there is a Poset structure to exploit. In particular, when two90vertices `u, v` are far apart and generate a set `h(\{u,v\})` of vertices,91all the pairs of vertices `u', v'\in h(\{u,v\})` satisfy `h(\{u',v'\})92\subseteq h(\{u,v\})`, and so it is useless to test the pair `u', v'` when93both `u` and `v` where present.9495* The information cached is for any pair `u,v` of vertices the list of96elements `z` with `d_{G}(u,w) + d_{G}(w,v) = d_{G}(u,v)`. This is not in97general equal to `h(\{u,v\})` !9899Nothing says these recommandations will actually lead to any actual100improvements. There are just some ideas remembered while writing this101code. Trying to optimize may well lead to lost in efficiency on many102instances.103104EXAMPLE::105106sage: from sage.graphs.convexity_properties import ConvexityProperties107sage: g = graphs.PetersenGraph()108sage: CP = ConvexityProperties(g)109sage: CP.hull([1,3])110[1, 2, 3]111sage: CP.hull_number()1123113114TESTS::115116sage: ConvexityProperties(digraphs.Circuit(5))117Traceback (most recent call last):118...119ValueError: This is currenly implemented for Graphs only.Only minor updates are needed if you want to makeit support DiGraphs too.120"""121122def __init__(self, G):123r"""124Constructor125126EXAMPLE::127128sage: from sage.graphs.convexity_properties import ConvexityProperties129sage: g = graphs.PetersenGraph()130sage: ConvexityProperties(g)131<sage.graphs.convexity_properties.ConvexityProperties object at ...>132"""133from sage.graphs.digraph import DiGraph134if isinstance(G, DiGraph):135raise ValueError("This is currenly implemented for Graphs only."+136"Only minor updates are needed if you want to make"+137"it support DiGraphs too.")138139# Cached number of vertices140cdef int n = G.order()141self._n = n142143cdef int i = 0144cdef int j,k145146# Temporary variables147cdef dict d_i148cdef dict d_j149cdef int d_ij150self._dict_vertices_to_integers = {}151self._list_integers_to_vertices = []152153# Remembering integers instead of the labels, and building dictionaries154# in both directions.155for v in G:156self._dict_vertices_to_integers[v] = i157self._list_integers_to_vertices.append(v)158i = i + 1159160161# Computation of distances between all pairs. Costly.162cdef dict distances = G.distance_all_pairs()163164# _cache_hull_pairs[u*n + v] is a bitset whose 1 bits are the vertices located on a shortest path from vertex u to v165#166# Note that u < v167self._cache_hull_pairs = <bitset_t *> sage_malloc(((n*(n-1))>>1)*sizeof(bitset_t))168cdef bitset_t * p_bitset = self._cache_hull_pairs169170# Filling the cache171#172# The p_bitset variable iterates over the successive elements of the cache173#174# For any pair i,j of vertices (i<j), we built the bitset of all the175# elements k which are on a shortest path from i to j176177for 0<= i < n-1:178# Caching the distances from i to the other vertices179d_i = distances[self._list_integers_to_vertices[i]]180181for i < j < n:182# Caching the distances from j to the other vertices183d_j = distances[self._list_integers_to_vertices[j]]184185# Caching the distance between i and j186d_ij = d_i[self._list_integers_to_vertices[j]]187188# Initializing the new bitset189bitset_init(p_bitset[0], n)190bitset_set_first_n(p_bitset[0], 0)191192# Filling it193for 0<= k < n:194if ((d_i[self._list_integers_to_vertices[k]]195+ d_j[self._list_integers_to_vertices[k]])196== d_ij):197bitset_add(p_bitset[0], k)198199# Next bitset !200p_bitset = p_bitset + 1201202203def __destruct__(self):204r"""205Destructor206207EXAMPLE::208209sage: from sage.graphs.convexity_properties import ConvexityProperties210sage: g = graphs.PetersenGraph()211sage: ConvexityProperties(g)212<sage.graphs.convexity_properties.ConvexityProperties object at ...>213214"""215cdef bitset_t * p_bitset = self._cache_hull_pairs216cdef int i217218for 0 <= i < ((self._n*(self._n-1))>>1):219bitset_free(p_bitset[0])220p_bitset = p_bitset + 1221222sage_free(self._cache_hull_pairs)223224cdef list _vertices_to_integers(self, vertices):225r"""226Converts a list of vertices to a list of integers with the cached data.227"""228cdef list answer = []229for v in v:230answer.append(self._dict_vertices_to_integers[v])231return answer232233cdef list _integers_to_vertices(self, integers):234r"""235Converts a list of integers to a list of vertices with the cached data.236"""237238cdef list answer = []239for v in integers:240answer.append(self._list_integers_to_vertices[v])241return answer242243cdef _bitset_convex_hull(self, bitset_t hull):244r"""245Computes the convex hull of a list of vertices given as a bitset.246247(this method returns nothing and modifies the input)248"""249cdef int count250cdef int tmp_count251cdef int i,j252253cdef bitset_t * p_bitset254255# Current size of the set256count = bitset_len(hull)257258while True:259260# Iterating over all the elements in the cache261p_bitset = self._cache_hull_pairs262263# For any vertex i264for 0 <= i < self._n-1:265266# If i is not in the current set, we skip it !267if not bitset_in(hull, i):268p_bitset = p_bitset + (self._n-1-i)269continue270271# If it is, we iterate over all the elements j272for i < j < self._n:273274# If both i and j are inside, we add all the (cached)275# vertices on a shortest ij-path276277if bitset_in(hull, j):278bitset_union(hull, hull, p_bitset[0])279280# Next bitset !281p_bitset = p_bitset + 1282283284tmp_count = bitset_len(hull)285286# If we added nothing new during the previous loop, our set is287# convex !288if tmp_count == count:289return290291# Otherwise, update and back to the loop292count = tmp_count293294cpdef hull(self, list vertices):295r"""296Returns the convex hull of a set of vertices.297298INPUT:299300* ``vertices`` -- A list of vertices.301302EXAMPLE::303304sage: from sage.graphs.convexity_properties import ConvexityProperties305sage: g = graphs.PetersenGraph()306sage: CP = ConvexityProperties(g)307sage: CP.hull([1,3])308[1, 2, 3]309"""310cdef bitset_t bs311bitset_init(bs, self._n)312bitset_set_first_n(bs, 0)313314for v in vertices:315bitset_add(bs, self._dict_vertices_to_integers[v])316317self._bitset_convex_hull(bs)318319#cdef list answer = bitset_list(bs)320cdef list answer = self._integers_to_vertices(bitset_list(bs))321322bitset_free(bs)323324return answer325326cdef _greedy_increase(self, bitset_t bs):327r"""328Given a bitset whose hull is not the whole set, greedily add vertices329and stop before its hull is the whole set.330331NOTE:332333* Counting the bits at each turn is not the best way...334"""335cdef bitset_t tmp336bitset_init(tmp, self._n)337338339for 0<= i < self._n:340if not bitset_in(bs, i):341bitset_copy(tmp, bs)342bitset_add(tmp, i)343self._bitset_convex_hull(tmp)344if bitset_len(tmp) < self._n:345bitset_add(bs, i)346347348cpdef hull_number(self, value_only = True, verbose = False):349r"""350Computes the hull number and a corresponding generating set.351352The hull number `hn(G)` of a graph `G` is the cardinality of a smallest353set of vertices `S` such that `h(S)=V(G)`.354355INPUT:356357* ``value_only`` (boolean) -- whether to return only the hull number358(default) or a minimum set whose convex hull is the whole graph.359360* ``verbose`` (boolean) -- whether to display information on the LP.361362**COMPLEXITY:**363364This problem is NP-Hard [CHZ02]_, but seems to be of the "nice"365kind. Update this comment if you fall on hard instances `:-)`366367**ALGORITHM:**368369This is solved by linear programming.370371As the function `h(S)` associating to each set `S` its convex hull is a372closure operator, it is clear that any set `S_G` of vertices such that373`h(S_G)=V(G)` must satisfy `S_G \not \subseteq C` for any *proper*374convex set `C \subsetneq V(G)`. The following formulation is hence375correct376377.. MATH::378379\text{Minimize :}& \sum_{v\in G}b_v\\380\text{Such that :}&\\381&\forall C\subsetneq V(G)\text{ a proper convex set }\\382&\sum_{v\in V(G)\backslash C} b_v \geq 1383384Of course, the number of convex sets -- and so the number of constraints385-- can be huge, and hard to enumerate, so at first an incomplete386formulation is solved (it is missing some constraints). If the answer387returned by the LP solver is a set `S` generating the whole graph, then388it is optimal and so is returned. Otherwise, the constraint389corresponding to the set `h(S)` can be added to the LP, which makes the390answer `S` infeasible, and another solution computed.391392This being said, simply adding the constraint corresponding to `h(S)` is393a bit slow, as these sets can be large (and the corresponding constrait394a bit weak). To improve it a bit, before being added, the set `h(S)` is395"greedily enriched" to a set `S'` with vertices for as long as396`h(S')\neq V(G)`. This way, we obtain a set `S'` with `h(S)\subseteq397h(S')\subsetneq V(G)`, and the constraint corresponding to `h(S')` --398which is stronger than the one corresponding to `h(S)` -- is added.399400This can actually be seen as a hitting set problem on the complement of401convex sets.402403EXAMPLE:404405The Hull number of Petersen's graph::406407sage: from sage.graphs.convexity_properties import ConvexityProperties408sage: g = graphs.PetersenGraph()409sage: CP = ConvexityProperties(g)410sage: CP.hull_number()4113412sage: generating_set = CP.hull_number(value_only = False)413sage: CP.hull(generating_set)414[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]415416REFERENCE:417418.. [CHZ02] F. Harary, E. Loukakis, C. Tsouros419The geodetic number of a graph420Mathematical and computer modelling421vol. 17 n11 pp.89--95, 1993422"""423424cdef int i425cdef list constraint # temporary variable to add constraints to the LP426427if self._n <= 2:428if value_only:429return self._n430else:431return self._list_integers_to_vertices432433cdef GenericBackend p = <GenericBackend> get_solver(constraint_generation = True)434435# Minimization436p.set_sense(False)437438# We have exactly n binary variables, all of them with a coefficient of439# 1 in the objective function440p.add_variables(self._n, 0, None, True, False, False, 1, None)441442# We know that at least 2 vertices are required to cover the whole graph443p.add_linear_constraint(zip(range(self._n), [1]*self._n), 2, None)444445# The set of vertices generated by the current LP solution446cdef bitset_t current_hull447bitset_init(current_hull, self._n)448449# Which is at first empty450bitset_set_first_n(current_hull,1)451452while True:453454# Greedily increase it to obtain a better constraint455self._greedy_increase(current_hull)456457if verbose:458print "Adding a constraint corresponding to convex set ",459print bitset_list(current_hull)460461# Building the corresponding constraint462constraint = []463for 0 <= i < self._n:464if not bitset_in(current_hull, i):465constraint.append((i,1))466467p.add_linear_constraint(constraint, 1, None)468469p.solve()470471# Computing the current solution's convex hull472bitset_set_first_n(current_hull,0)473474for 0 <= i < self._n:475if p.get_variable_value(i) > .5:476bitset_add(current_hull, i)477478self._bitset_convex_hull(current_hull)479480# Are we done ?481if bitset_len(current_hull) == self._n:482break483484bitset_free(current_hull)485486if value_only:487return <int> p.get_objective_value()488489constraint = []490for 0 <= i < self._n:491if p.get_variable_value(i) > .5:492constraint.append(i)493494return self._integers_to_vertices(constraint)495496497