Commit 012787ec authored by Tiago Peixoto's avatar Tiago Peixoto

Add `dag` parameter to shortest_distance()/shortest_path()/all_shortest_paths()

This enables linear-time computation of shortest distances on DAGs that
also allows for negative weights.
parent 7342a3ae
......@@ -28,6 +28,7 @@
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/dijkstra_shortest_paths_no_color_map.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>
#include <boost/graph/dag_shortest_paths.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python.hpp>
......@@ -335,7 +336,7 @@ struct do_djk_search
boost::python::object otarget_list,
VertexIndexMap vertex_index, DistMap dist_map,
PredMap pred_map, WeightMap weight, long double max_dist,
std::vector<size_t>& reached) const
std::vector<size_t>& reached, bool dag) const
{
auto target_list = get_array<int64_t, 1>(otarget_list);
......@@ -359,22 +360,55 @@ struct do_djk_search
size_t target = tgt.empty() ?
graph_traits<GraphInterface::multigraph_t>::null_vertex() :
*tgt.begin();
dijkstra_shortest_paths_no_color_map_no_init
(g, vertex(source, g), pred_map, dist_map, weight,
vertex_index, std::less<dist_t>(),
boost::closed_plus<dist_t>(), inf, dist_t(),
djk_max_visitor<DistMap>(dist_map, max_d, inf, target,
reached));
if (!dag)
{
dijkstra_shortest_paths_no_color_map_no_init
(g, vertex(source, g), pred_map, dist_map, weight,
vertex_index, std::less<dist_t>(),
boost::closed_plus<dist_t>(), inf, dist_t(),
djk_max_visitor<DistMap>(dist_map, max_d, inf, target,
reached));
}
else
{
unchecked_vector_property_map<boost::default_color_type, VertexIndexMap>
color_map(vertex_index, num_vertices(g));
dag_shortest_paths
(g, vertex(source, g), dist_map, weight,
color_map, pred_map,
djk_max_visitor<DistMap>(dist_map, max_d, inf, target,
reached),
std::less<dist_t>(),
boost::closed_plus<dist_t>(), inf, dist_t());
}
}
else
{
dijkstra_shortest_paths_no_color_map_no_init
(g, vertex(source, g), pred_map, dist_map, weight,
vertex_index, std::less<dist_t>(),
boost::closed_plus<dist_t>(), inf, dist_t(),
djk_max_multiple_targets_visitor<DistMap>(dist_map, max_d,
inf, tgt,
reached));
if (!dag)
{
dijkstra_shortest_paths_no_color_map_no_init
(g, vertex(source, g), pred_map, dist_map, weight,
vertex_index, std::less<dist_t>(),
boost::closed_plus<dist_t>(), inf, dist_t(),
djk_max_multiple_targets_visitor<DistMap>(dist_map,
max_d, inf,
tgt,
reached));
}
else
{
unchecked_vector_property_map<boost::default_color_type, VertexIndexMap>
color_map(vertex_index, num_vertices(g));
dag_shortest_paths
(g, vertex(source, g), dist_map, weight, color_map,
pred_map,
djk_max_multiple_targets_visitor<DistMap>(dist_map,
max_d, inf,
tgt,
reached),
std::less<dist_t>(), boost::closed_plus<dist_t>(), inf,
dist_t());
}
}
}
......@@ -411,7 +445,8 @@ struct do_bf_search
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, bool bf, std::vector<size_t>& reached)
long double max_dist, bool bf, std::vector<size_t>& reached,
bool dag)
{
typedef property_map_type
::apply<int64_t, GraphInterface::vertex_index_map_t>::type pred_map_t;
......@@ -444,7 +479,7 @@ void get_dists(GraphInterface& gi, size_t source, boost::python::object tgt,
run_action<>()
(gi, std::bind(do_djk_search(), std::placeholders::_1, source, tgt, gi.get_vertex_index(),
std::placeholders::_2, pmap.get_unchecked(num_vertices(gi.get_graph())),
std::placeholders::_3, max_dist, std::ref(reached)),
std::placeholders::_3, max_dist, std::ref(reached), dag),
writable_vertex_scalar_properties(),
edge_scalar_properties())
(dist_map, weight);
......
......@@ -1540,7 +1540,7 @@ def kcore_decomposition(g, vprop=None):
def shortest_distance(g, source=None, target=None, weights=None,
negative_weights=False, max_dist=None, directed=None,
dense=False, dist_map=None, pred_map=False,
return_reached=False):
return_reached=False, dag=False):
"""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
is not specified.
......@@ -1604,6 +1604,11 @@ def shortest_distance(g, source=None, target=None, weights=None,
return_reached : ``bool`` (optional, default: ``False``)
If ``True``, return an array of visited vertices.
dag : ``bool`` (optional, default:``False``)
If ``True``, assume that the graph is a Directed Acyclic Graph (DAG),
which will be faster if ``weights`` are given, in which case they are
also allowed to contain negative values (irrespective of the parameter
``negative_weights``).
Returns
-------
......@@ -1635,9 +1640,10 @@ def shortest_distance(g, source=None, target=None, weights=None,
or ``inf`` in case of floating point types.
If source is specified, the algorithm runs in :math:`O(V + E)` time, or
:math:`O(V \log V)` if weights are given. If ``negative_weights == True``,
the complexity is :math:`O(VE)`. If source is not specified, it runs in
:math:`O(VE\log V)` time, or :math:`O(V^3)` if dense == True.
:math:`O(V \log V)` if weights are given (if ``dag == True`` this improves
to :math:`O(V+E)`). If ``negative_weights == True``, the complexity is
:math:`O(VE)`. If source is not specified, it runs in :math:`O(VE\log V)`
time, or :math:`O(V^3)` if dense == True.
Examples
--------
......@@ -1763,7 +1769,7 @@ def shortest_distance(g, source=None, target=None, weights=None,
_prop("e", u, weights),
_prop("v", u, pmap),
float(max_dist),
negative_weights, reached)
negative_weights, reached, dag)
else:
libgraph_tool_topology.get_all_dists(u._Graph__graph,
_prop("v", u, dist_map),
......@@ -1790,7 +1796,7 @@ def shortest_distance(g, source=None, target=None, weights=None,
return dist_map
def shortest_path(g, source, target, weights=None, negative_weights=False,
pred_map=None):
pred_map=None, dag=False):
"""Return the shortest path from ``source`` to ``target``.
Parameters
......@@ -1809,6 +1815,11 @@ def shortest_path(g, source, target, weights=None, negative_weights=False,
Vertex property map with the predecessors in the search tree. If this is
provided, the shortest paths are not computed, and are obtained directly
from this map.
dag : ``bool`` (optional, default:``False``)
If ``True``, assume that the graph is a Directed Acyclic Graph (DAG),
which will be faster if ``weights`` are given, in which case they are
also allowed to contain negative values (irrespective of the parameter
``negative_weights``).
Returns
-------
......@@ -1826,7 +1837,7 @@ def shortest_path(g, source, target, weights=None, negative_weights=False,
negative weights, as long as there are no negative loops.
The algorithm runs in :math:`O(V + E)` time, or :math:`O(V \log V)` if
weights are given.
weights are given (if ``dag == True`` this improves to :math:`O(V+E)`).
Examples
--------
......@@ -1855,12 +1866,13 @@ def shortest_path(g, source, target, weights=None, negative_weights=False,
graphs." Numerische Mathematik, 1:269-271, 1959.
.. [dijkstra-boost] http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html
.. [bellman-ford] http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html
"""
if pred_map is None:
pred_map = shortest_distance(g, source, target, weights=weights,
negative_weights=negative_weights,
pred_map=True)[1]
pred_map=True, dag=dag)[1]
if pred_map[target] == int(target): # no path to target
return [], []
......@@ -1932,7 +1944,7 @@ def all_predecessors(g, dist_map, pred_map, weights=None, epsilon=1e-8):
def all_shortest_paths(g, source, target, weights=None, negative_weights=False,
dist_map=None, pred_map=None, all_preds_map=None,
epsilon=1e-8):
epsilon=1e-8, dag=False):
"""Return an iterator over all shortest paths from `source` to `target`.
Parameters
......@@ -1961,6 +1973,11 @@ def all_shortest_paths(g, source, target, weights=None, negative_weights=False,
epsilon : `float` (optional, default: `1e-8`)
Maximum relative difference between distances to be considered "equal",
in case floating-point weights are used.
dag : ``bool`` (optional, default:``False``)
If ``True``, assume that the graph is a Directed Acyclic Graph (DAG),
which will be faster if ``weights`` are given, in which case they are
also allowed to contain negative values (irrespective of the parameter
``negative_weights``).
Returns
-------
......@@ -2006,7 +2023,7 @@ def all_shortest_paths(g, source, target, weights=None, negative_weights=False,
if dist_map is None or pred_map is None:
dist_map, pred_map = shortest_distance(g, source, weights=weights,
negative_weights=negative_weights,
pred_map=True)
pred_map=True, dag=dag)
if all_preds_map is None:
all_preds_map = all_predecessors(g, dist_map, pred_map, weights, epsilon)
......
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