Skip to content

Commit

Permalink
Allow integer conversions and sentinel value for all id types
Browse files Browse the repository at this point in the history
This will allow more flexibility of code that uses an id type and can
handle both integer-like and not-like-integer types with significant
performance benefit if the id type is an integer. After this change,
this can be detected at runtime, choosing the appropriate implementation
depending on the actual instance of the graph passed.

And example will be CompactIdMap. For integer indices it can use the
current implementation which has minimal overhead. After this change, it
can support also non-like-integer ids, keeping the mapping from the id
to usize in a hashmap.
  • Loading branch information
pnevyk committed Apr 1, 2024
1 parent 94bfd7e commit 1efd3a2
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 185 deletions.
18 changes: 17 additions & 1 deletion gryf/examples/complex_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,23 @@ use gryf::{
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct ChessSquare(pub usize, pub usize);

impl IdType for ChessSquare {}
impl IdType for ChessSquare {
fn sentinel() -> Self {
Self(usize::MAX, usize::MAX)
}

fn is_integer() -> bool {
false
}

fn as_bits(&self) -> u64 {
panic!("unsupported")
}

fn from_bits(_: u64) -> Self {
panic!("unsupported")
}
}

struct Chessboard;

Expand Down
8 changes: 4 additions & 4 deletions gryf/examples/implicit_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn to_vertex(n: u64) -> VertexId {
}

fn to_num(v: VertexId) -> u64 {
v.to_bits()
v.as_bits()
}

struct Neighbor {
Expand All @@ -27,13 +27,13 @@ struct Neighbor {

impl NeighborRef<VertexId, EdgeId> for Neighbor {
fn id(&self) -> WeakRef<'_, VertexId> {
let n = self.src.to_bits();
let n = self.src.as_bits();
let c = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
WeakRef::Owned(to_vertex(c))
}

fn edge(&self) -> WeakRef<'_, EdgeId> {
WeakRef::Owned(EdgeId::from_bits(self.src.to_bits()))
WeakRef::Owned(EdgeId::from_bits(self.src.as_bits()))
}

fn src(&self) -> WeakRef<'_, VertexId> {
Expand Down Expand Up @@ -80,7 +80,7 @@ impl VerticesBaseWeak for Collatz {

impl VerticesWeak<u64> for Collatz {
fn vertex_weak(&self, id: &VertexId) -> Option<WeakRef<'_, u64>> {
Some(id.to_bits().into())
Some(id.as_bits().into())
}
}

Expand Down
4 changes: 2 additions & 2 deletions gryf/src/adapt/complement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rustc_hash::FxHashSet;

use crate::core::{
facts,
id::IntegerIdType,
id::{IdType, IntegerIdType},
marker::{Direction, Undirected},
EdgesBase, EdgesMut, Neighbors, Vertices, VerticesBase, VerticesMut, WeakRef,
};
Expand Down Expand Up @@ -127,7 +127,7 @@ where
let v = self.vertices.next()?;

if v != self.src && !self.neighbors.contains(&v) {
return Some((v, G::EdgeId::null(), self.src.clone(), self.dir));
return Some((v, IdType::sentinel(), self.src.clone(), self.dir));
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions gryf/src/adapt/subgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ where
mod tests {
use crate::{
core::{
id::{DefaultId, EdgeId, IntegerIdType, VertexId},
id::{DefaultId, EdgeId, IdType, VertexId},
marker::Directed,
EdgesMut, VerticesMut,
},
Expand Down Expand Up @@ -358,10 +358,10 @@ mod tests {
graph.add_edge(&v4, &v5, 10);

Subgraph::new(graph)
.filter_vertex(|v: &VertexId, _, _| v.to_usize() < 4)
.filter_vertex(|v: &VertexId, _, _| v.as_usize() < 4)
.filter_edge(|e, g, _| {
let (src, dst): (VertexId, VertexId) = g.endpoints(e).unwrap();
src.to_usize() + dst.to_usize() < 4 || dst.to_usize() == 5
src.as_usize() + dst.as_usize() < 4 || dst.as_usize() == 5
})
}

Expand Down
20 changes: 10 additions & 10 deletions gryf/src/algo/shortest_paths/bellman_ford.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ where
let vertex_map = graph.vertex_id_map();

let mut dist = vec![W::inf(); vertex_map.len()];
let mut pred = vec![Virtual::null(); vertex_map.len()];
let mut pred = vec![Virtual::sentinel(); vertex_map.len()];

dist[vertex_map.virt(start).unwrap().to_usize()] = W::zero();
dist[vertex_map.virt(start).unwrap().as_usize()] = W::zero();

let mut terminated_early = false;

Expand All @@ -39,8 +39,8 @@ where
for edge in graph.edges() {
if let Some((next_dist, u, v)) = process_edge(&edge, &edge_weight, &vertex_map, &dist) {
// Relax if better.
dist[v.to_usize()] = next_dist;
pred[v.to_usize()] = u;
dist[v.as_usize()] = next_dist;
pred[v.as_usize()] = u;
relaxed = true;
}

Expand All @@ -50,8 +50,8 @@ where
if let Some((next_dist, u, v)) =
process_edge(&TransposeRef::new(edge), &edge_weight, &vertex_map, &dist)
{
dist[v.to_usize()] = next_dist;
pred[v.to_usize()] = u;
dist[v.as_usize()] = next_dist;
pred[v.as_usize()] = u;
relaxed = true;
}
}
Expand Down Expand Up @@ -84,7 +84,7 @@ where
}

if let Some(goal) = goal {
if dist[vertex_map.virt(goal).unwrap().to_usize()] == W::inf() {
if dist[vertex_map.virt(goal).unwrap().as_usize()] == W::inf() {
return Err(Error::GoalNotReached);
}
}
Expand All @@ -105,7 +105,7 @@ where
.into_iter()
.enumerate()
.filter_map(|(i, p)| {
if !p.is_null() {
if !p.is_sentinel() {
Some((
vertex_map.real(Virtual::from(i)).unwrap(),
vertex_map.real(p).unwrap(),
Expand Down Expand Up @@ -135,7 +135,7 @@ where
{
let u = vertex_map.virt(*edge.src()).unwrap();

let short_dist = &dist[u.to_usize()];
let short_dist = &dist[u.as_usize()];
if short_dist == &W::inf() {
return None;
}
Expand All @@ -145,7 +145,7 @@ where
let edge_dist = edge_weight.get(edge.data());
let next_dist = short_dist.clone() + edge_dist;

if next_dist < dist[v.to_usize()] {
if next_dist < dist[v.as_usize()] {
Some((next_dist, u, v))
} else {
None
Expand Down
6 changes: 3 additions & 3 deletions gryf/src/algo/toposort/kahn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
algo::Cycle,
common::CompactIdMap,
core::{
id::{IntegerIdType, Virtual},
id::{IdType, IntegerIdType, Virtual},
marker::Direction,
GraphBase, NeighborRef, Neighbors, VerticesBase,
},
Expand Down Expand Up @@ -64,7 +64,7 @@ where
self.visited += 1;

for n in self.graph.neighbors_directed(&vertex, Direction::Outgoing) {
let i = self.map.virt(*n.id()).unwrap().to_usize();
let i = self.map.virt(*n.id()).unwrap().as_usize();
let deg = &mut self.in_deg[i];
*deg -= 1;

Expand Down Expand Up @@ -101,7 +101,7 @@ where
.graph
.neighbors_directed(&v, Direction::Incoming)
.find_map(|n| {
let i = self.map.virt(*n.id()).unwrap().to_usize();
let i = self.map.virt(*n.id()).unwrap().as_usize();
if self.in_deg[i] > 0 {
Some(n.edge().into_owned())
} else {
Expand Down
14 changes: 7 additions & 7 deletions gryf/src/common/compact_id_map.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::cmp::min;

use crate::core::id::{IntegerIdType, Virtual};
use crate::core::id::{IdType, IntegerIdType, Virtual};

// For compact storages, the space and time for both directions is constant (use
// `isomorphic`). For storages with holes, the space is O(|V|), virtual to real
Expand All @@ -18,7 +18,7 @@ impl<I: IntegerIdType> CompactIdMap<I> {
A: Iterator<Item = I>,
{
let mut map = iter.collect::<Vec<_>>();
map.sort_unstable_by_key(|id| id.to_bits());
map.sort_unstable_by_key(|id| id.as_bits());
let len = map.len();

Self { map, len }
Expand Down Expand Up @@ -50,19 +50,19 @@ impl<I: IntegerIdType> CompactIdMap<I> {
let id: Virtual<I> = id.into();

if self.is_isomorphic() {
(id.to_usize() < self.len()).then(|| I::from_bits(id.to_bits()))
(id.as_usize() < self.len()).then(|| I::from_bits(id.as_bits()))
} else {
self.map.get(id.to_usize()).copied()
self.map.get(id.as_usize()).cloned()
}
}

pub fn virt(&self, id: I) -> Option<Virtual<I>> {
if self.is_isomorphic() {
(id.to_usize() < self.len()).then(|| Virtual::new(id.to_bits()))
(id.as_usize() < self.len()).then(|| Virtual::new(id.as_bits()))
} else {
// Using `wrapping_sub` not to panic on overflow.
let direct = min(id.to_usize(), self.len().wrapping_sub(1));
let direct_elem = self.map.get(direct).copied()?;
let direct = min(id.as_usize(), self.len().wrapping_sub(1));
let direct_elem = self.map.get(direct).cloned()?;

// This will always be true for storages without holes, and sometimes
// for storages with holes.
Expand Down
8 changes: 4 additions & 4 deletions gryf/src/common/visit_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ impl<I: IdType, S: BuildHasher> VisitSet<I> for HashSet<I, S> {

impl<I: IntegerIdType> VisitSet<I> for FixedBitSet {
fn visit(&mut self, id: I) -> bool {
if self.len() < id.to_usize() {
self.grow(id.to_usize() - self.len());
if self.len() < id.as_usize() {
self.grow(id.as_usize() - self.len());
}
!self.put(id.to_usize())
!self.put(id.as_usize())
}

fn is_visited(&self, id: &I) -> bool {
self.contains(id.to_usize())
self.contains(id.as_usize())
}

fn visited_count(&self) -> usize {
Expand Down
2 changes: 1 addition & 1 deletion gryf/src/core/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ where
{
for edge in iter {
let (src, dst, edge) = edge.unpack();
let vertex_bound = max(src, dst).to_usize();
let vertex_bound = max(&src, &dst).as_usize();

while self.vertex_count() <= vertex_bound {
self.add_vertex(V::default());
Expand Down
Loading

0 comments on commit 1efd3a2

Please sign in to comment.