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 all 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
88 changes: 88 additions & 0 deletions retworkx-core/src/distancemap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

//! This module contains the [`DistanceMap`] trait which is used in
//! [`shortest_path`](crate::shortest_path).
//!
//! The trait allows the shortest path functions to support multiple
//! return types.

use std::hash::Hash;

use petgraph::graph::IndexType;

use crate::dictmap::*;
use hashbrown::HashMap;

/// A mapping for storing the distances of nodes for shortest path algorithms.
pub trait DistanceMap<K, V> {
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
/// Create mapping with support for items between 0 and `num_elements - 1`.
fn build(num_elements: usize) -> Self;

/// Get the distance to the item at `pos`. If the distance does not exist,
/// the function returns `None`.
fn get_item(&self, pos: K) -> Option<&V>;

/// Insert item at position `pos` with distance `V`.
fn put_item(&mut self, pos: K, val: V);
}

impl<K: IndexType, V: Clone> DistanceMap<K, V> for Vec<Option<V>> {
#[inline]
fn build(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> {
#[inline]
fn build(_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);
}
}

impl<K: Eq + Hash, V: Clone> DistanceMap<K, V> for HashMap<K, V> {
#[inline]
fn build(_num_elements: usize) -> Self {
HashMap::<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);
}
}
1 change: 1 addition & 0 deletions retworkx-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub mod shortest_path;
pub mod traversal;
// These modules define additional data structures
pub mod dictmap;
pub mod distancemap;
mod min_scored;

// re-export petgraph so there is a consistent version available to users and
Expand Down
31 changes: 16 additions & 15 deletions retworkx-core/src/shortest_path/dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@
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::visit::{EdgeRef, IntoEdges, NodeIndexable, VisitMap, Visitable};

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

/// Dijkstra's shortest path algorithm.
Expand All @@ -45,7 +44,7 @@ use crate::min_scored::MinScored;
/// the value is a Vec of node indices of the path starting with `start` and
/// ending at the index.
///
/// Returns a [`DictMap`] that maps `NodeId` to path cost.
/// Returns a [`DistanceMap`] that maps `NodeId` to path cost.
/// # Example
/// ```rust
/// use retworkx_core::petgraph::Graph;
Expand Down Expand Up @@ -98,24 +97,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: IntoEdges + Visitable + NodeIndexable,
G::NodeId: Eq + Hash,
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::build(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 +134,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 +155,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 +169,6 @@ where
}
visited.visit(node);
}

Ok(scores)
}
25 changes: 14 additions & 11 deletions retworkx-core/src/shortest_path/k_shortest_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ 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::*;
use crate::distancemap::DistanceMap;
use crate::min_scored::MinScored;

/// k'th shortest path algorithm.
Expand All @@ -42,28 +43,28 @@ use crate::min_scored::MinScored;
///
/// Computes in **O(k * (|E| + |V|*log(|V|)))** time (average).
///
/// Returns a [`DictMap`] that maps `NodeId` to path cost as the value.
/// Returns a [`DistanceMap`] that maps `NodeId` to path cost as the value.
///
/// # Example:
/// ```rust
///
/// use retworkx_core::petgraph;
/// use retworkx_core::petgraph::graph::NodeIndex;
/// use retworkx_core::shortest_path::k_shortest_path;
/// use retworkx_core::dictmap::DictMap;
/// use hashbrown::HashMap;
/// use retworkx_core::Result;
///
/// let g = petgraph::graph::UnGraph::<i32, _>::from_edges(&[
/// (0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (1, 4), (5, 6), (6, 7), (7, 5)
/// ]);
///
/// let res: Result<DictMap<NodeIndex, f64>> = k_shortest_path(
/// let res: Result<HashMap<NodeIndex, f64>> = k_shortest_path(
/// &g, NodeIndex::new(1), None, 2,
/// |e: retworkx_core::petgraph::graph::EdgeReference<&'static str>| Ok(1.0),
/// );
///
/// let output = res.unwrap();
/// let expected: DictMap<NodeIndex, f64> = [
/// let expected: HashMap<NodeIndex, f64> = [
/// (NodeIndex::new(0), 3.0),
/// (NodeIndex::new(1), 2.0),
/// (NodeIndex::new(2), 3.0),
Expand All @@ -75,21 +76,22 @@ use crate::min_scored::MinScored;
/// ].iter().cloned().collect();
/// assert_eq!(expected, output);
/// ```
pub fn k_shortest_path<G, F, E, K>(
pub fn k_shortest_path<G, F, E, K, S>(
graph: G,
start: G::NodeId,
goal: Option<G::NodeId>,
k: usize,
mut edge_cost: F,
) -> Result<DictMap<G::NodeId, K>, E>
) -> Result<S, 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,
S: DistanceMap<G::NodeId, K>,
{
let mut counter: Vec<usize> = vec![0; graph.node_bound()];
let mut scores = DictMap::with_capacity(graph.node_count());
let mut scores: S = S::build(graph.node_bound());
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();

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

if current_counter == k {
scores.insert(node, node_score);
scores.put_item(node, node_score);
}

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

Ok(scores)
}
31 changes: 14 additions & 17 deletions src/shortest_path/all_pairs_dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,24 +80,21 @@ pub fn all_pairs_dijkstra_path_lengths<Ty: EdgeType + Sync>(
let out_map: DictMap<usize, PathLengthMapping> = node_indices
.into_par_iter()
.map(|x| {
let path_lenghts: PyResult<Vec<Option<f64>>> =
dijkstra(graph, x, None, |e| edge_cost(e.id()), None);
let out_map = PathLengthMapping {
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(),
path_lengths: path_lenghts
.unwrap()
.into_iter()
.enumerate()
.filter_map(|(index, opt_cost)| {
if index != x.index() {
opt_cost.map(|cost| (index, cost))
} else {
None
}
})
.collect(),
};
(x.index(), out_map)
})
Expand Down
Loading