Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Vector for intermediate computations in Dijkstra #493

Merged
merged 37 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
615d473
Sort Dijkstra output at the end
IvanIsCoding Nov 24, 2021
6ea2edd
Handle dense cases at the end
IvanIsCoding Nov 24, 2021
6b6e547
Change condition for sorting output
IvanIsCoding Nov 24, 2021
c0fd854
Use Vector for intermediate calculations
IvanIsCoding Nov 24, 2021
566007e
Use vector in k_shortest_path
IvanIsCoding Nov 24, 2021
5fa8e91
Address clippy comments
IvanIsCoding Nov 24, 2021
d473c83
Fix bug
IvanIsCoding Nov 24, 2021
e8fe403
Incorporate feedback from PR
IvanIsCoding Nov 24, 2021
0e16201
Add quaternary heap
IvanIsCoding Nov 29, 2021
4a17a25
Fix steiner tree test
IvanIsCoding Nov 29, 2021
87a82aa
Avoid creating duplicated dictmaps
IvanIsCoding Nov 30, 2021
9d447c2
Revert "Fix steiner tree test"
IvanIsCoding Nov 30, 2021
71bb12e
Merge branch 'main' into dijkstra-regression
IvanIsCoding Nov 30, 2021
c1f4521
Add tests and docstring
IvanIsCoding Dec 1, 2021
4673692
Merge branch 'dijkstra-regression' of github.com:IvanIsCoding/retwork…
IvanIsCoding Dec 1, 2021
e0446f3
Use trait to reduce duplication
IvanIsCoding Dec 1, 2021
a8c2f41
Merge branch 'main' into dijkstra-regression
IvanIsCoding Dec 7, 2021
465fddb
Merge remote-tracking branch 'upstream/main' into dijkstra-regression
IvanIsCoding Dec 9, 2021
742a008
Merge branch 'main' into dijkstra-regression
IvanIsCoding Dec 17, 2021
2c25806
Move DistanceMap to its own file
IvanIsCoding Dec 19, 2021
67432c9
Use DistanceMap in k_shortest_path
IvanIsCoding Dec 19, 2021
df8afa9
Add test coverage
IvanIsCoding Dec 19, 2021
63f410b
Support HashMap in DistanceMap
IvanIsCoding Dec 19, 2021
6b6aa34
Merge branch 'main' into dijkstra-regression
IvanIsCoding Dec 19, 2021
68db1a8
Remove type casting
IvanIsCoding Dec 19, 2021
cf6dff7
Remove unnecessary IndexType
IvanIsCoding Dec 19, 2021
1f73c59
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 6, 2022
9beb87e
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 11, 2022
a4ec9e9
Use Vector in dijkstra_shortest_paths and all_pairs_dijkstra_path_len…
IvanIsCoding Jan 11, 2022
370dbc8
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 11, 2022
59affe7
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 11, 2022
51de10f
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 13, 2022
1dc199a
Update distancemap.rs
IvanIsCoding Jan 13, 2022
2102410
Update retworkx-core/src/distancemap.rs
IvanIsCoding Jan 14, 2022
db05bed
Add docstrings
IvanIsCoding Jan 14, 2022
4ceae0e
Merge branch 'main' into dijkstra-regression
IvanIsCoding Jan 14, 2022
c0288b9
Cargo fmt
IvanIsCoding Jan 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 57 additions & 15 deletions retworkx-core/src/shortest_path/dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,54 @@
use std::collections::BinaryHeap;
use std::hash::Hash;

use indexmap::map::Entry::{Occupied, Vacant};

use petgraph::algo::Measure;
use petgraph::visit::{EdgeRef, IntoEdges, VisitMap, Visitable};
use petgraph::graph::IndexType;
use petgraph::visit::{EdgeRef, IntoEdges, NodeIndexable, VisitMap, Visitable};

use crate::dictmap::*;
use crate::min_scored::MinScored;

// Trait for supporting storing
pub trait DistanceMap<K, V> {
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
fn new_distancemap(num_elements: usize) -> Self;
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
fn get_item(&self, pos: K) -> Option<&V>;
fn put_item(&mut self, pos: K, val: V);
}

impl<K: IndexType, V: Clone> DistanceMap<K, V> for Vec<Option<V>> {
#[inline]
fn new_distancemap(num_elements: usize) -> Self {
vec![None; num_elements]
}

#[inline]
fn get_item(&self, pos: K) -> Option<&V> {
self[pos.index()].as_ref()
}

#[inline]
fn put_item(&mut self, pos: K, val: V) {
self[pos.index()] = Some(val);
}
}

impl<K: Eq + Hash, V: Clone> DistanceMap<K, V> for DictMap<K, V> {
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
fn new_distancemap(_num_elements: usize) -> Self {
DictMap::<K, V>::default()
}

#[inline]
fn get_item(&self, pos: K) -> Option<&V> {
self.get(&pos)
}

#[inline]
fn put_item(&mut self, pos: K, val: V) {
self.insert(pos, val);
}
}

/// Dijkstra's shortest path algorithm.
///
/// Compute the length of the shortest path from `start` to every reachable
Expand Down Expand Up @@ -98,24 +138,25 @@ use crate::min_scored::MinScored;
/// assert_eq!(res.unwrap(), expected_res);
/// // z is not inside res because there is not path from b to z.
/// ```
pub fn dijkstra<G, F, K, E>(
pub fn dijkstra<G, F, K, E, S>(
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
graph: G,
start: G::NodeId,
goal: Option<G::NodeId>,
mut edge_cost: F,
mut path: Option<&mut DictMap<G::NodeId, Vec<G::NodeId>>>,
) -> Result<DictMap<G::NodeId, K>, E>
) -> Result<S, E>
where
G: IntoEdges + Visitable,
G::NodeId: Eq + Hash,
G: IntoEdges + Visitable + NodeIndexable,
G::NodeId: Eq + Hash + IndexType,
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
F: FnMut(G::EdgeRef) -> Result<K, E>,
K: Measure + Copy,
S: DistanceMap<G::NodeId, K>,
{
let mut visited = graph.visit_map();
let mut scores = DictMap::new();
let mut scores: S = S::new_distancemap(graph.node_bound());
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();
scores.insert(start, zero_score);
scores.put_item(start, zero_score);
visit_next.push(MinScored(zero_score, start));
if path.is_some() {
path.as_mut().unwrap().insert(start, vec![start]);
Expand All @@ -134,10 +175,10 @@ where
}
let cost = edge_cost(edge)?;
let next_score = node_score + cost;
match scores.entry(next) {
Occupied(ent) => {
if next_score < *ent.get() {
*ent.into_mut() = next_score;
match scores.get_item(next) {
Some(current_score) => {
if next_score < *current_score {
scores.put_item(next, next_score);
visit_next.push(MinScored(next_score, next));
if path.is_some() {
let mut node_path = path
Expand All @@ -155,8 +196,8 @@ where
}
}
}
Vacant(ent) => {
ent.insert(next_score);
None => {
scores.put_item(next, next_score);
visit_next.push(MinScored(next_score, next));
if path.is_some() {
let mut node_path =
Expand All @@ -169,5 +210,6 @@ where
}
visited.visit(node);
}

Ok(scores)
}
18 changes: 13 additions & 5 deletions retworkx-core/src/shortest_path/k_shortest_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use std::hash::Hash;

use petgraph::algo::Measure;
use petgraph::visit::{
EdgeRef, IntoEdges, NodeCount, NodeIndexable, Visitable,
EdgeRef, IntoEdges, IntoNodeIdentifiers, NodeCount, NodeIndexable,
Visitable,
};

use crate::dictmap::*;
Expand Down Expand Up @@ -83,13 +84,13 @@ pub fn k_shortest_path<G, F, E, K>(
mut edge_cost: F,
) -> Result<DictMap<G::NodeId, K>, E>
where
G: IntoEdges + Visitable + NodeCount + NodeIndexable,
G: IntoEdges + Visitable + NodeCount + NodeIndexable + IntoNodeIdentifiers,
G::NodeId: Eq + Hash,
F: FnMut(G::EdgeRef) -> Result<K, E>,
K: Measure + Copy,
{
let mut counter: Vec<usize> = vec![0; graph.node_bound()];
let mut scores = DictMap::with_capacity(graph.node_count());
let mut scores: Vec<Option<K>> = vec![None; graph.node_bound()];
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();

Expand All @@ -104,7 +105,7 @@ where
}

if current_counter == k {
scores.insert(node, node_score);
scores[graph.to_index(node)] = Some(node_score);
}

//Already reached goal k times
Expand All @@ -117,5 +118,12 @@ where
.push(MinScored(node_score + edge_cost(edge)?, edge.target()));
}
}
Ok(scores)

Ok(scores
.into_iter()
.enumerate()
.filter_map(|(node, score)| {
score.map(|val| (graph.from_index(node), val))
})
.collect())
}
23 changes: 12 additions & 11 deletions src/shortest_path/all_pairs_dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,24 @@ pub fn all_pairs_dijkstra_path_lengths<Ty: EdgeType + Sync>(
.into_par_iter()
.map(|x| {
let out_map = PathLengthMapping {
path_lengths: dijkstra(
path_lengths: (dijkstra(
graph,
x,
None,
|e| edge_cost(e.id()),
None,
)
.unwrap()
.iter()
.filter_map(|(index, cost)| {
if *index == x {
None
} else {
Some((index.index(), *cost))
}
})
.collect(),
as PyResult<DictMap<NodeIndex, f64>>)
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
.unwrap()
.iter()
.filter_map(|(index, cost)| {
if *index == x {
None
} else {
Some((index.index(), *cost))
}
})
.collect(),
};
(x.index(), out_map)
})
Expand Down
69 changes: 49 additions & 20 deletions src/shortest_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ pub fn graph_dijkstra_shortest_paths(
DictMap::with_capacity(graph.node_count());

let cost_fn = CostFn::try_from((weight_fn, default_weight))?;
dijkstra(

(dijkstra(
&graph.graph,
start,
goal_index,
|e| cost_fn.call(py, e.weight()),
Some(&mut paths),
)?;
) as PyResult<DictMap<NodeIndex, f64>>)?;

Ok(PathMapping {
paths: paths
Expand Down Expand Up @@ -144,7 +145,7 @@ pub fn digraph_dijkstra_shortest_paths(
let cost_fn = CostFn::try_from((weight_fn, default_weight))?;

if as_undirected {
dijkstra(
(dijkstra(
// TODO: Use petgraph undirected adapter after
// https://github.com/petgraph/petgraph/pull/318 is available in
// a petgraph release.
Expand All @@ -153,15 +154,15 @@ pub fn digraph_dijkstra_shortest_paths(
goal_index,
|e| cost_fn.call(py, e.weight()),
Some(&mut paths),
)?;
) as PyResult<DictMap<NodeIndex, f64>>)?;
} else {
dijkstra(
(dijkstra(
&graph.graph,
start,
goal_index,
|e| cost_fn.call(py, e.weight()),
Some(&mut paths),
)?;
) as PyResult<DictMap<NodeIndex, f64>>)?;
}
Ok(PathMapping {
paths: paths
Expand Down Expand Up @@ -216,22 +217,36 @@ pub fn graph_dijkstra_shortest_path_lengths(
let start = NodeIndex::new(node);
let goal_index: Option<NodeIndex> = goal.map(NodeIndex::new);

let res = dijkstra(
let res: Vec<Option<f64>> = dijkstra(
&graph.graph,
start,
goal_index,
|e| edge_cost_callable.call(py, e.weight()),
None,
)?;

if let Some(goal_usize) = goal {
return Ok(PathLengthMapping {
path_lengths: match res[goal_usize] {
Some(goal_length) => {
let mut ans = DictMap::new();
ans.insert(goal_usize, goal_length);
ans
}
None => DictMap::new(),
},
});
}

Ok(PathLengthMapping {
path_lengths: res
.iter()
.filter_map(|(k, v)| {
let k_int = k.index();
if k_int == node || goal.is_some() && goal.unwrap() != k_int {
None
.into_iter()
.enumerate()
.filter_map(|(k_int, opt_v)| {
if k_int != node {
opt_v.map(|v| (k_int, v))
} else {
Some((k_int, *v))
None
}
})
.collect(),
Expand Down Expand Up @@ -272,22 +287,36 @@ pub fn digraph_dijkstra_shortest_path_lengths(
let start = NodeIndex::new(node);
let goal_index: Option<NodeIndex> = goal.map(NodeIndex::new);

let res = dijkstra(
let res: Vec<Option<f64>> = dijkstra(
&graph.graph,
start,
goal_index,
|e| edge_cost_callable.call(py, e.weight()),
None,
)?;

if let Some(goal_usize) = goal {
return Ok(PathLengthMapping {
path_lengths: match res[goal_usize] {
Some(goal_length) => {
let mut ans = DictMap::new();
ans.insert(goal_usize, goal_length);
ans
}
None => DictMap::new(),
},
});
}

Ok(PathLengthMapping {
path_lengths: res
.iter()
.filter_map(|(k, v)| {
let k_int = k.index();
if k_int == node || goal.is_some() && goal.unwrap() != k_int {
None
.into_iter()
.enumerate()
.filter_map(|(k_int, opt_v)| {
if k_int != node {
opt_v.map(|v| (k_int, v))
} else {
Some((k_int, *v))
None
}
})
.collect(),
Expand Down
2 changes: 1 addition & 1 deletion src/steiner_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fn fast_metric_edges(
};

let mut paths = DictMap::with_capacity(graph.graph.node_count());
let mut distance =
let mut distance: DictMap<NodeIndex, f64> =
dijkstra(&graph.graph, dummy, None, cost_fn, Some(&mut paths))?;
paths.remove(&dummy);
distance.remove(&dummy);
Expand Down
10 changes: 10 additions & 0 deletions tests/digraph/test_dijkstra.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ def test_dijkstra(self):
expected = {4: 23.0}
self.assertEqual(expected, path)

def test_dijkstra_length_with_no_path(self):
g = retworkx.PyDiGraph()
a = g.add_node("A")
b = g.add_node("B")
path_lenghts = retworkx.digraph_dijkstra_shortest_path_lengths(
g, a, edge_cost_fn=float, goal=b
)
expected = {}
self.assertEqual(expected, path_lenghts)

def test_dijkstra_path(self):
paths = retworkx.digraph_dijkstra_shortest_paths(self.graph, self.a)
expected = {
Expand Down
10 changes: 10 additions & 0 deletions tests/graph/test_dijkstra.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ def test_dijkstra_with_no_goal_set(self):
expected = {1: 1.0, 2: 1.0, 3: 1.0, 4: 2.0, 5: 2.0}
self.assertEqual(expected, path)

def test_dijkstra_length_with_no_path(self):
g = retworkx.PyGraph()
a = g.add_node("A")
b = g.add_node("B")
path_lenghts = retworkx.graph_dijkstra_shortest_path_lengths(
g, a, edge_cost_fn=float, goal=b
)
expected = {}
self.assertEqual(expected, path_lenghts)

def test_dijkstra_path_with_no_goal_set(self):
path = retworkx.graph_dijkstra_shortest_paths(self.graph, self.a)
expected = {
Expand Down