Commit d1c285d0 authored by Tiago Peixoto's avatar Tiago Peixoto

Improve slightly the performance of shortest_distance() with multiple targets...

Improve slightly the performance of shortest_distance() with multiple targets and revert shortest_path() to single-target-only
parent 2131b6f0
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "graph_filtering.hh" #include "graph_filtering.hh"
#include "graph_properties.hh" #include "graph_properties.hh"
#include "graph_selectors.hh" #include "graph_selectors.hh"
#include "numpy_bind.hh"
#include <boost/graph/breadth_first_search.hpp> #include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp> #include <boost/graph/dijkstra_shortest_paths.hpp>
...@@ -80,7 +81,9 @@ class bfs_max_multiple_targets_visitor: ...@@ -80,7 +81,9 @@ class bfs_max_multiple_targets_visitor:
public boost::bfs_visitor<null_visitor> public boost::bfs_visitor<null_visitor>
{ {
public: public:
bfs_max_multiple_targets_visitor(DistMap dist_map, PredMap pred, size_t max_dist, std::unordered_set<std::size_t> target) bfs_max_multiple_targets_visitor(DistMap dist_map, PredMap pred,
size_t max_dist,
std::unordered_set<std::size_t> target)
: _dist_map(dist_map), _pred(pred), _max_dist(max_dist), _target(target), : _dist_map(dist_map), _pred(pred), _max_dist(max_dist), _target(target),
_dist(0) {} _dist(0) {}
...@@ -132,7 +135,8 @@ class djk_max_visitor: ...@@ -132,7 +135,8 @@ class djk_max_visitor:
{ {
public: public:
djk_max_visitor(DistMap dist_map, djk_max_visitor(DistMap dist_map,
typename property_traits<DistMap>::value_type max_dist, size_t target) typename property_traits<DistMap>::value_type max_dist,
size_t target)
: _dist_map(dist_map), _max_dist(max_dist), _target(target) {} : _dist_map(dist_map), _max_dist(max_dist), _target(target) {}
...@@ -172,7 +176,7 @@ public: ...@@ -172,7 +176,7 @@ public:
{ {
if (_dist_map[u] > _max_dist) if (_dist_map[u] > _max_dist)
throw stop_search(); throw stop_search();
auto search = _target.find(u); auto search = _target.find(u);
if (search != _target.end()) if (search != _target.end())
{ {
...@@ -193,19 +197,23 @@ private: ...@@ -193,19 +197,23 @@ private:
struct do_bfs_search struct do_bfs_search
{ {
template <class Graph, class VertexIndexMap, class DistMap, class PredMap> template <class Graph, class VertexIndexMap, class DistMap, class PredMap>
void operator()(const Graph& g, size_t source, boost::python::list target_list, void operator()(const Graph& g, size_t source,
boost::python::object otarget_list,
VertexIndexMap vertex_index, DistMap dist_map, VertexIndexMap vertex_index, DistMap dist_map,
PredMap pred_map, long double max_dist) const PredMap pred_map, long double max_dist) const
{ {
typedef typename property_traits<DistMap>::value_type dist_t; typedef typename property_traits<DistMap>::value_type dist_t;
boost::python::stl_input_iterator<size_t> begin(target_list), end; auto target_list = get_array<int64_t, 1>(otarget_list);
std::unordered_set<std::size_t> tgt(begin, end); std::unordered_set<std::size_t> tgt(target_list.begin(),
target_list.end());
dist_t max_d = (max_dist > 0) ? dist_t max_d = (max_dist > 0) ?
max_dist : numeric_limits<dist_t>::max(); max_dist : numeric_limits<dist_t>::max();
int i, N = num_vertices(g); int i, N = num_vertices(g);
#pragma omp parallel for default(shared) private(i) schedule(runtime) if (N > 100) #pragma omp parallel for default(shared) private(i) \
schedule(runtime) if (N > 100)
for (i = 0; i < N; ++i) for (i = 0; i < N; ++i)
dist_map[i] = numeric_limits<dist_t>::max(); dist_map[i] = numeric_limits<dist_t>::max();
dist_map[source] = 0; dist_map[source] = 0;
...@@ -215,10 +223,12 @@ struct do_bfs_search ...@@ -215,10 +223,12 @@ struct do_bfs_search
color_map(vertex_index, num_vertices(g)); color_map(vertex_index, num_vertices(g));
try try
{ {
if (tgt.size() <= 1) if (tgt.size() <= 1)
{ {
size_t target = tgt.empty() ? graph_traits<GraphInterface::multigraph_t>::null_vertex() : *tgt.begin(); size_t target = tgt.empty() ?
graph_traits<GraphInterface::multigraph_t>::null_vertex() :
*tgt.begin();
breadth_first_search(g, vertex(source, g), breadth_first_search(g, vertex(source, g),
visitor(bfs_max_visitor<DistMap, PredMap> visitor(bfs_max_visitor<DistMap, PredMap>
(dist_map, pred_map, max_d, target)). (dist_map, pred_map, max_d, target)).
...@@ -243,27 +253,33 @@ struct do_djk_search ...@@ -243,27 +253,33 @@ struct do_djk_search
{ {
template <class Graph, class VertexIndexMap, class DistMap, class PredMap, template <class Graph, class VertexIndexMap, class DistMap, class PredMap,
class WeightMap> class WeightMap>
void operator()(const Graph& g, size_t source, boost::python::list target_list, void operator()(const Graph& g, size_t source,
boost::python::object otarget_list,
VertexIndexMap vertex_index, DistMap dist_map, VertexIndexMap vertex_index, DistMap dist_map,
PredMap pred_map, WeightMap weight, long double max_dist) const PredMap pred_map, WeightMap weight, long double max_dist) const
{ {
auto target_list = get_array<int64_t, 1>(otarget_list);
typedef typename property_traits<DistMap>::value_type dist_t; typedef typename property_traits<DistMap>::value_type dist_t;
dist_t max_d = (max_dist > 0) ? dist_t max_d = (max_dist > 0) ?
max_dist : numeric_limits<dist_t>::max(); max_dist : numeric_limits<dist_t>::max();
boost::python::stl_input_iterator<size_t> begin(target_list), end;
std::unordered_set<std::size_t> tgt(begin, end); std::unordered_set<std::size_t> tgt(target_list.begin(),
target_list.end());
int i, N = num_vertices(g); int i, N = num_vertices(g);
#pragma omp parallel for default(shared) private(i) schedule(runtime) if (N > 100) #pragma omp parallel for default(shared) private(i) \
schedule(runtime) if (N > 100)
for (i = 0; i < N; ++i) for (i = 0; i < N; ++i)
dist_map[i] = numeric_limits<dist_t>::max(); dist_map[i] = numeric_limits<dist_t>::max();
dist_map[source] = 0; dist_map[source] = 0;
try try
{ {
if (tgt.size() <= 1) if (tgt.size() <= 1)
{ {
size_t target = tgt.empty() ? graph_traits<GraphInterface::multigraph_t>::null_vertex() : *tgt.begin(); size_t target = tgt.empty() ?
graph_traits<GraphInterface::multigraph_t>::null_vertex() :
*tgt.begin();
dijkstra_shortest_paths(g, vertex(source, g), dijkstra_shortest_paths(g, vertex(source, g),
weight_map(weight). weight_map(weight).
distance_map(dist_map). distance_map(dist_map).
...@@ -288,8 +304,9 @@ struct do_djk_search ...@@ -288,8 +304,9 @@ struct do_djk_search
} }
}; };
void get_dists(GraphInterface& gi, size_t source, boost::python::list tgt, void get_dists(GraphInterface& gi, size_t source, boost::python::object tgt,
boost::any dist_map, boost::any weight, boost::any pred_map, long double max_dist) boost::any dist_map, boost::any weight, boost::any pred_map,
long double max_dist)
{ {
typedef property_map_type typedef property_map_type
::apply<int64_t, GraphInterface::vertex_index_map_t>::type pred_map_t; ::apply<int64_t, GraphInterface::vertex_index_map_t>::type pred_map_t;
......
...@@ -1098,8 +1098,7 @@ def kcore_decomposition(g, deg="out", vprop=None): ...@@ -1098,8 +1098,7 @@ def kcore_decomposition(g, deg="out", vprop=None):
def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
directed=None, dense=False, dist_map=None, directed=None, dense=False, dist_map=None,
pred_map=False): pred_map=False):
""" """Calculate the distance from a source to a target vertex, or to of all
Calculate the distance from a source to a target vertex, or to of all
vertices from a given source, or the all pairs shortest paths, if the source vertices from a given source, or the all pairs shortest paths, if the source
is not specified. is not specified.
...@@ -1111,8 +1110,8 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, ...@@ -1111,8 +1110,8 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
Source vertex of the search. If unspecified, the all pairs shortest Source vertex of the search. If unspecified, the all pairs shortest
distances are computed. distances are computed.
target : :class:`~graph_tool.Vertex` or iterable of such objects (optional, default: None) target : :class:`~graph_tool.Vertex` or iterable of such objects (optional, default: None)
Target vertex/vertices of the search. If unspecified, the distance to all Target vertex (or vertices) of the search. If unspecified, the distance
vertices from the source will be computed. to all vertices from the source will be computed.
weights : :class:`~graph_tool.PropertyMap` (optional, default: None) weights : :class:`~graph_tool.PropertyMap` (optional, default: None)
The edge weights. If provided, the minimum spanning tree will minimize The edge weights. If provided, the minimum spanning tree will minimize
the edge weights. the edge weights.
...@@ -1202,12 +1201,17 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, ...@@ -1202,12 +1201,17 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
5 3 6 3 4 2147483647 5 3 6 3 4 2147483647
4 6 4 4 4 4 4 6 4 4 4 4
6 5 4 4] 6 5 4 4]
>>> dist = gt.shortest_distance(g, source=g.vertex(0), target=g.vertex(2))
>>> print (dist)
5
>>> dist = gt.shortest_distance(g, source=g.vertex(0), target=[g.vertex(2), g.vertex(6)])
>>> print (dist)
[5 9]
References References
---------- ----------
.. [bfs] Edward Moore, "The shortest path through a maze", International .. [bfs] Edward Moore, "The shortest path through a maze", International
Symposium on the Theory of Switching (1959), Harvard University Symposium on the Theory of Switching (1959), Harvard University Press.
Press;
.. [bfs-boost] http://www.boost.org/libs/graph/doc/breadth_first_search.html .. [bfs-boost] http://www.boost.org/libs/graph/doc/breadth_first_search.html
.. [dijkstra] E. Dijkstra, "A note on two problems in connexion with .. [dijkstra] E. Dijkstra, "A note on two problems in connexion with
graphs." Numerische Mathematik, 1:269-271, 1959. graphs." Numerische Mathematik, 1:269-271, 1959.
...@@ -1216,11 +1220,12 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, ...@@ -1216,11 +1220,12 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
.. [floyd-warshall-apsp] http://www.boost.org/libs/graph/doc/floyd_warshall_shortest.html .. [floyd-warshall-apsp] http://www.boost.org/libs/graph/doc/floyd_warshall_shortest.html
""" """
if not isinstance(target, list): if isinstance(target, collections.Iterable):
if isinstance(target, collections.Iterable): target = numpy.asarray(target, dtype="int64")
target = list(target) elif target is None:
else: target = numpy.array([], dtype="int64")
target = [target] else:
target = numpy.asarray([int(target)], dtype="int64")
if weights is None: if weights is None:
dist_type = 'int32_t' dist_type = 'int32_t'
...@@ -1251,7 +1256,7 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, ...@@ -1251,7 +1256,7 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
pmap = g.copy_property(u.vertex_index, value_type="int64_t") pmap = g.copy_property(u.vertex_index, value_type="int64_t")
libgraph_tool_topology.get_dists(g._Graph__graph, libgraph_tool_topology.get_dists(g._Graph__graph,
int(source), int(source),
list(target), target,
_prop("v", g, dist_map), _prop("v", g, dist_map),
_prop("e", g, weights), _prop("e", g, weights),
_prop("v", g, pmap), _prop("v", g, pmap),
...@@ -1261,19 +1266,17 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None, ...@@ -1261,19 +1266,17 @@ def shortest_distance(g, source=None, target=None, weights=None, max_dist=None,
_prop("v", g, dist_map), _prop("v", g, dist_map),
_prop("e", g, weights), dense) _prop("e", g, weights), dense)
if source is not None: if source is not None and len(target) > 0:
if len(target) > 1: if len(target) == 1:
dist_map = numpy.array([dist_map[target] for target in target]) dist_map = dist_map.a[target[0]]
else: else:
# Standard behaviour when there is one single target dist_map = numpy.array(dist_map.a[target])
dist_map = dist_map[target[0]]
if source is not None and pred_map: if source is not None and pred_map:
return dist_map, pmap return dist_map, pmap
else: else:
return dist_map return dist_map
def shortest_path(g, source, target, weights=None, pred_map=None): def shortest_path(g, source, target, weights=None, pred_map=None):
""" """
Return the shortest path from `source` to `target`. Return the shortest path from `source` to `target`.
...@@ -1284,8 +1287,8 @@ def shortest_path(g, source, target, weights=None, pred_map=None): ...@@ -1284,8 +1287,8 @@ def shortest_path(g, source, target, weights=None, pred_map=None):
Graph to be used. Graph to be used.
source : :class:`~graph_tool.Vertex` source : :class:`~graph_tool.Vertex`
Source vertex of the search. Source vertex of the search.
target : :class:`~graph_tool.Vertex` or iterable of such objects target : :class:`~graph_tool.Vertex`
Target vertex or vertices of the search. Target vertex of the search.
weights : :class:`~graph_tool.PropertyMap` (optional, default: None) weights : :class:`~graph_tool.PropertyMap` (optional, default: None)
The edge weights. The edge weights.
pred_map : :class:`~graph_tool.PropertyMap` (optional, default: None) pred_map : :class:`~graph_tool.PropertyMap` (optional, default: None)
...@@ -1336,67 +1339,43 @@ def shortest_path(g, source, target, weights=None, pred_map=None): ...@@ -1336,67 +1339,43 @@ def shortest_path(g, source, target, weights=None, pred_map=None):
graphs." Numerische Mathematik, 1:269-271, 1959. graphs." Numerische Mathematik, 1:269-271, 1959.
.. [dijkstra-boost] http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html .. [dijkstra-boost] http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html
""" """
if not isinstance(target, collections.Iterable):
target = [target]
vlists, elists = {}, {}
targets = set(target)
if pred_map is None: if pred_map is None:
_, pred_map = shortest_distance(g, source, target, pred_map = shortest_distance(g, source, target,
weights=weights, weights=weights,
pred_map=True) pred_map=True)[1]
if any(pred_map[tgt] != int(tgt) for tgt in target): if pred_map[target] == int(target): # no path to target
# there is a path to at least one of the targets return [], []
if weights is not None: vlist = [target]
max_w = weights.a.max() + 1 elist = []
else:
max_w = None if weights is not None:
max_w = weights.a.max() + 1
for tgt in target: else:
max_w = None
if tgt in vlists:
# the current target has already been found v = target
continue while v != source:
p = g.vertex(pred_map[v])
v = tgt min_w = max_w
vlist, elist = [tgt], [] pe = None
targets_indexes = {} s = None
for e in v.in_edges() if g.is_directed() else v.out_edges():
while v != source: s = e.source() if g.is_directed() else e.target()
p = g.vertex(pred_map[v]) if s == p:
min_w = max_w if weights is not None:
pe = None if weights[e] < min_w:
s = None min_w = weights[e]
for e in v.in_edges() if g.is_directed() else v.out_edges(): pe = e
s = e.source() if g.is_directed() else e.target() else:
if s == p: pe = e
if weights is not None: break
if weights[e] < min_w: elist.insert(0, pe)
min_w = weights[e] vlist.insert(0, p)
pe = e v = p
else: return vlist, elist
pe = e
break
elist.insert(0, pe)
vlist.insert(0, p)
if v in targets:
targets_indexes[v] = len(elist) - 1
v = p
# the paths are updated for every encountered target
path_len = len(elist)
vlists.update({tgt: vlist[:path_len-idx+1] for tgt, idx in targets_indexes.iteritems()})
elists.update({tgt: elist[:path_len-idx] for tgt, idx in targets_indexes.iteritems()})
if len(target) == 1:
return elists[target[0]], vlists[target[0]]
return [elists[tgt] for tgt in target], [vlists[tgt] for tgt in target]
def pseudo_diameter(g, source=None, weights=None): def pseudo_diameter(g, source=None, weights=None):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment