Merge remote-tracking branch 'origin/bm/algorithms-shortest-path' into bm/dino-closure
# Conflicts:
# .gitignore
# crates/algorithms/src/shortest_paths/astar/impl.rs
# crates/algorithms/src/shortest_paths/common/path.rs
# crates/algorithms/src/shortest_paths/common/transit.rs
# crates/algorithms/src/shortest_paths/dijkstra/iter.rs
# crates/algorithms/src/shortest_paths/dijkstra/mod.rs
# crates/algorithms/src/shortest_paths/mod.rs
diff --git a/.gitignore b/.gitignore
index f2694a9..f4602b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
Cargo.lock
/crates/algorithms/tests/cases/problems
venv
+crates/algorithms/benches/input
diff --git a/crates/algorithms/Cargo.toml b/crates/algorithms/Cargo.toml
index 21bf91d..8e9dec9 100644
--- a/crates/algorithms/Cargo.toml
+++ b/crates/algorithms/Cargo.toml
@@ -19,6 +19,7 @@
error-stack.workspace = true
hashbrown = { workspace = true, features = ['ahash'] }
either = "1.9.0"
+dary_heap = "0.3.6"
[build-dependencies]
rustc_version = "0.4.0"
@@ -51,6 +52,9 @@
remove-me-only-intended-for-move-graph = []
remove-me-only-intended-for-move-adjacency-matrix = []
+[profile.release]
+lto = "fat"
+
[[bench]]
name = "shortest_paths"
harness = false
diff --git a/crates/algorithms/benches/shortest_paths/large.rs b/crates/algorithms/benches/shortest_paths/large.rs
index d21a814..5f11794 100644
--- a/crates/algorithms/benches/shortest_paths/large.rs
+++ b/crates/algorithms/benches/shortest_paths/large.rs
@@ -133,7 +133,7 @@
output.push((source, target, weight));
if let Some(amount_of_lines) = &mut amount_of_lines {
- *amount_of_lines = amount_of_lines.checked_sub(1).expect("too many lines")
+ *amount_of_lines = amount_of_lines.checked_sub(1).expect("too many lines");
}
}
b'a' => panic!("edge lines before problem statement"),
@@ -179,21 +179,16 @@
}
fn dijkstra(criterion: &mut Criterion) {
- criterion.bench_with_input(
- // BenchmarkId::from_parameter("dimacs9/florida"),
- BenchmarkId::new("dimacs9/florida", "dijkstra"),
- &"USA-road-d.FLA",
- |bench, &filename| {
- let (source, graph) = build_graph(filename);
- let dijkstra = Dijkstra::directed();
+ let (source, graph) = build_graph("USA-road-d.FLA");
+ let dijkstra = Dijkstra::directed();
- bench.iter(|| {
- for distances in dijkstra.distance_from(&graph, &source).unwrap() {
- black_box(distances);
- }
- });
- },
- );
+ criterion.bench_function("dimacs9/florida/dijkstra", |bench| {
+ bench.iter_with_large_drop(|| {
+ let scores: Vec<_> = dijkstra.distance_from(&graph, &source).unwrap().collect();
+
+ scores
+ });
+ });
}
criterion_group!(benches, dijkstra);
diff --git a/crates/algorithms/src/shortest_paths/astar/impl.rs b/crates/algorithms/src/shortest_paths/astar/impl.rs
index 33c5e13..263d1a8 100644
--- a/crates/algorithms/src/shortest_paths/astar/impl.rs
+++ b/crates/algorithms/src/shortest_paths/astar/impl.rs
@@ -23,6 +23,7 @@
pub(super) struct AStarImpl<'graph: 'parent, 'parent, S, E, H, C>
where
S: GraphStorage,
+ S::NodeId: FlaggableGraphId<S>,
E: GraphCost<S>,
E::Value: Ord,
{
@@ -44,7 +45,7 @@
impl<'graph: 'parent, 'parent, S, E, H, C> AStarImpl<'graph, 'parent, S, E, H, C>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -102,7 +103,7 @@
}
pub(super) fn find(mut self) -> Option<Route<'graph, S, E::Value>> {
- while let Some(node) = self.queue.pop_min() {
+ while let Some(QueueItem { node, .. }) = self.queue.pop_min() {
if node.id() == self.target.id() {
let transit = if self.predecessor_mode == PredecessorMode::Record {
reconstruct_path_to(&self.predecessors, node.id())
diff --git a/crates/algorithms/src/shortest_paths/astar/mod.rs b/crates/algorithms/src/shortest_paths/astar/mod.rs
index a15e300..fa9935d 100644
--- a/crates/algorithms/src/shortest_paths/astar/mod.rs
+++ b/crates/algorithms/src/shortest_paths/astar/mod.rs
@@ -12,6 +12,9 @@
use error_stack::Result;
use petgraph_core::{
edge::marker::{Directed, Undirected},
+ Direction,
+ },
+ id::FlaggableGraphId,
DirectedGraphStorage, Graph, GraphDirectionality, GraphStorage, Node,
};
@@ -149,7 +152,7 @@
) -> Result<AStarImpl<'graph, 'this, S, E, H, impl Connections<'graph, S> + 'this>, AStarError>
where
S: DirectedGraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -176,7 +179,7 @@
) -> Result<AStarImpl<'graph, 'this, S, E, H, impl Connections<'graph, S> + 'this>, AStarError>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -199,7 +202,7 @@
impl<S, E, H> ShortestPath<S> for AStar<Undirected, E, H>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -268,7 +271,7 @@
impl<S, E, H> ShortestDistance<S> for AStar<Undirected, E, H>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -338,7 +341,7 @@
impl<S, E, H> ShortestPath<S> for AStar<Directed, E, H>
where
S: DirectedGraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
@@ -407,7 +410,7 @@
impl<S, E, H> ShortestDistance<S> for AStar<Directed, E, H>
where
S: DirectedGraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + Eq + Hash,
E: GraphCost<S>,
E::Value: AStarMeasure,
H: GraphHeuristic<S, Value = E::Value>,
diff --git a/crates/algorithms/src/shortest_paths/common/queue/priority.rs b/crates/algorithms/src/shortest_paths/common/queue/priority.rs
index bcbc6d9..494738b 100644
--- a/crates/algorithms/src/shortest_paths/common/queue/priority.rs
+++ b/crates/algorithms/src/shortest_paths/common/queue/priority.rs
@@ -4,17 +4,18 @@
cmp::{Ordering, Reverse},
};
-use petgraph_core::{GraphStorage, Node};
+use petgraph_core::{
+ id::{FlagStorage, FlaggableGraphId},
+ GraphStorage, Node,
+};
struct PriorityQueueItem<'a, S, T>
where
S: GraphStorage,
{
- node: Node<'a, S>,
+ pub(in crate::shortest_paths) node: Node<'a, S>,
- priority: T,
-
- skip: Cell<bool>,
+ pub(in crate::shortest_paths) priority: T,
}
impl<S, T> PartialEq for PriorityQueueItem<'_, S, T>
@@ -23,7 +24,7 @@
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
- self.priority.eq(&other.priority)
+ other.priority.eq(&self.priority)
}
}
@@ -40,71 +41,78 @@
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- self.priority.partial_cmp(&other.priority)
+ other.priority.partial_cmp(&self.priority)
}
}
-impl<S, T> Ord for PriorityQueueItem<'_, S, T>
+impl<S, T> Ord for QueueItem<'_, S, T>
where
S: GraphStorage,
T: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
- self.priority.cmp(&other.priority)
+ other.priority.cmp(&self.priority)
}
}
pub(in crate::shortest_paths) struct PriorityQueue<'a, S, T>
where
S: GraphStorage,
+ S::NodeId: FlaggableGraphId<S>,
T: Ord,
{
- heap: BinaryHeap<Reverse<PriorityQueueItem<'a, S, T>>>,
+ heap: BinaryHeap<PriorityQueueItem<'a, S, T>>,
+
+ flags: <S::NodeId as FlaggableGraphId<S>>::Store<'a>,
}
impl<'a, S, T> PriorityQueue<'a, S, T>
where
S: GraphStorage,
+ S::NodeId: FlaggableGraphId<S>,
T: Ord,
{
- pub(in crate::shortest_paths) fn new() -> Self {
+ #[inline]
+ pub(in crate::shortest_paths) fn new(storage: &'a S) -> Self {
Self {
heap: BinaryHeap::new(),
+ flags: <S::NodeId as FlaggableGraphId<S>>::flag_store(storage),
}
}
pub(in crate::shortest_paths) fn push(&mut self, node: Node<'a, S>, priority: T) {
- self.heap.push(Reverse(PriorityQueueItem {
- node,
- priority,
-
- skip: Cell::new(false),
- }));
+ self.heap.push(PriorityQueueItem { node, priority });
}
+ pub(in crate::shortest_paths) fn visit(&mut self, id: &'a S::NodeId) {
+ self.flags.set(id, true);
+ }
+
+ #[inline]
+ pub(in crate::shortest_paths) fn has_been_visited(&self, id: &'a S::NodeId) -> bool {
+ self.flags.index(id)
+ }
+
+ #[inline]
pub(in crate::shortest_paths) fn decrease_priority(&mut self, node: Node<'a, S>, priority: T) {
- for Reverse(item) in &self.heap {
- if item.node.id() == node.id() {
- item.skip.set(true);
- break;
- }
+ if self.has_been_visited(node.id()) {
+ return;
}
- self.heap.push(Reverse(PriorityQueueItem {
- node,
- priority,
-
- skip: Cell::new(false),
- }));
+ self.heap.push(PriorityQueueItem { node, priority });
}
- pub(in crate::shortest_paths) fn pop_min(&mut self) -> Option<Node<'a, S>> {
- while let Some(Reverse(item)) = self.heap.pop() {
- if !item.skip.get() {
- return Some(item.node);
- }
- }
+ #[inline]
+ pub(in crate::shortest_paths) fn pop_min(&mut self) -> Option<QueueItem<'a, S, T>> {
+ loop {
+ let item = self.heap.pop()?;
- None
+ if self.has_been_visited(item.node.id()) {
+ continue;
+ }
+
+ self.visit(item.node.id());
+ return Some(item);
+ }
}
}
diff --git a/crates/algorithms/src/shortest_paths/common/transit.rs b/crates/algorithms/src/shortest_paths/common/transit.rs
index 2e05130..8921358 100644
--- a/crates/algorithms/src/shortest_paths/common/transit.rs
+++ b/crates/algorithms/src/shortest_paths/common/transit.rs
@@ -1,8 +1,4 @@
-use alloc::{vec, vec::Vec};
-use core::{
- hash::{BuildHasher, Hash},
- iter,
-};
+
use hashbrown::{HashMap, HashSet};
use petgraph_core::{GraphStorage, Node};
@@ -13,33 +9,32 @@
Record,
}
-pub(in crate::shortest_paths) fn reconstruct_path_to<'a, S, H>(
- predecessors: &HashMap<&'a S::NodeId, Option<Node<'a, S>>, H>,
+pub(in crate::shortest_paths) fn reconstruct_path_to<'a, S>(
+ predecessors: &<S::NodeId as AttributeGraphId<S>>::Store<'a, Option<Node<'a, S>>>,
target: &'a S::NodeId,
) -> Vec<Node<'a, S>>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
- H: BuildHasher,
+ S::NodeId: AttributeGraphId<S>,
{
let mut current = target;
let mut path = Vec::new();
loop {
- let Some(node) = predecessors[current] else {
+ let Some(node) = predecessors.index(current) else {
// this case should in theory _never_ happen, as the next statement
// terminates if the next node is `None` (we're at a source node)
// we do it this way, so that we don't need to push and then pop immediately.
break;
};
- if predecessors[node.id()].is_none() {
+ if predecessors.index(node.id()).is_none() {
// we have reached the source node
break;
}
- path.push(node);
+ path.push(*node);
current = node.id();
}
diff --git a/crates/algorithms/src/shortest_paths/dijkstra/iter.rs b/crates/algorithms/src/shortest_paths/dijkstra/iter.rs
index 471ef22..c998f95 100644
--- a/crates/algorithms/src/shortest_paths/dijkstra/iter.rs
+++ b/crates/algorithms/src/shortest_paths/dijkstra/iter.rs
@@ -1,11 +1,15 @@
use alloc::vec::Vec;
use core::hash::Hash;
+use std::mem;
use error_stack::{Report, Result};
use fxhash::FxBuildHasher;
use hashbrown::HashMap;
use numi::num::{identity::Zero, ops::AddRef};
-use petgraph_core::{Graph, GraphStorage, Node};
+use petgraph_core::{
+ id::{AttributeGraphId, AttributeStorage, FlaggableGraphId},
+ Graph, GraphStorage, Node,
+};
use crate::shortest_paths::{
common::{
@@ -22,9 +26,11 @@
pub(super) struct DijkstraIter<'graph: 'parent, 'parent, S, E, G>
where
S: GraphStorage,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
{
+ graph: &'graph Graph<S>,
queue: PriorityQueue<'graph, S, E::Value>,
edge_cost: &'parent E,
@@ -35,18 +41,18 @@
num_nodes: usize,
init: bool,
- next: Option<Node<'graph, S>>,
+ next: Option<QueueItem<'graph, S, E::Value>>,
predecessor_mode: PredecessorMode,
- distances: HashMap<&'graph S::NodeId, E::Value, FxBuildHasher>,
- predecessors: HashMap<&'graph S::NodeId, Option<Node<'graph, S>>, FxBuildHasher>,
+ distances: <S::NodeId as AttributeGraphId<S>>::Store<'graph, E::Value>,
+ predecessors: <S::NodeId as AttributeGraphId<S>>::Store<'graph, Option<Node<'graph, S>>>,
}
impl<'graph: 'parent, 'parent, S, E, G> DijkstraIter<'graph, 'parent, S, E, G>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
G: Connections<'graph, S>,
@@ -59,7 +65,7 @@
source: &'graph S::NodeId,
- intermediates: PredecessorMode,
+ predecessor_mode: PredecessorMode,
) -> Result<Self, DijkstraError> {
let source_node = graph
.node(source)
@@ -67,15 +73,16 @@
let queue = PriorityQueue::new();
- let mut distances = HashMap::with_hasher(FxBuildHasher::default());
- distances.insert(source, E::Value::zero());
+ let mut distances = <S::NodeId as AttributeGraphId<S>>::attribute_store(graph.storage());
+ distances.set(source, E::Value::zero());
- let mut predecessors = HashMap::with_hasher(FxBuildHasher::default());
- if intermediates == PredecessorMode::Record {
- predecessors.insert(source, None);
+ let mut predecessors = <S::NodeId as AttributeGraphId<S>>::attribute_store(graph.storage());
+ if predecessor_mode == PredecessorMode::Record {
+ predecessors.set(source, None);
}
Ok(Self {
+ graph,
queue,
edge_cost,
connections,
@@ -83,7 +90,7 @@
num_nodes: graph.num_nodes(),
init: true,
next: None,
- predecessor_mode: intermediates,
+ predecessor_mode,
distances,
predecessors,
})
@@ -93,7 +100,7 @@
impl<'graph: 'parent, 'parent, S, E, G> Iterator for DijkstraIter<'graph, 'parent, S, E, G>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
G: Connections<'graph, S>,
@@ -105,7 +112,11 @@
// and then begin with the actual iteration loop.
if self.init {
self.init = false;
- self.next = Some(self.source);
+ self.next = Some(QueueItem {
+ node: self.source,
+ priority: E::Value::zero(),
+ });
+ self.queue.visit(self.source.id());
return Some(Route::new(
Path::new(self.source, Vec::new(), self.source),
@@ -115,30 +126,43 @@
// Process the neighbours from the node we determined in the last iteration.
// Reasoning behind this see below.
- let node = self.next?;
+ let QueueItem {
+ node,
+ priority: cost,
+ } = mem::take(&mut self.next)?;
+
let connections = self.connections.connections(&node);
-
for edge in connections {
- let (u, v) = edge.endpoints();
- let target = if v.id() == node.id() { u } else { v };
+ let (u, v) = edge.endpoint_ids();
+ let target = if u == node.id() { v } else { u };
- let alternative = self.distances[node.id()].add_ref(self.edge_cost.cost(edge).as_ref());
+ // do not pursue edges that have already been processed.
+ if self.queue.has_been_visited(target) {
+ continue;
+ }
- if let Some(distance) = self.distances.get(target.id()) {
- // do not insert the updated distance if it is not strictly better than the current
- // one
- if alternative >= *distance {
+ let alternative = &cost + self.edge_cost.cost(edge).as_ref();
+
+ // TODO: Entry API
+ if let Some(distance) = self.distances.get_mut(target) {
+ // do not insert the updated distance if it is not strictly better than the
+ // current one
+ if *distance <= alternative {
continue;
}
- }
- self.distances.insert(target.id(), alternative.clone());
+ *distance = alternative.clone();
+ } else {
+ self.distances.set(target, alternative.clone());
+ }
if self.predecessor_mode == PredecessorMode::Record {
- self.predecessors.insert(target.id(), Some(node));
+ self.predecessors.set(target, Some(node));
}
- self.queue.decrease_priority(target, alternative);
+ if let Some(target) = self.graph.node(target) {
+ self.queue.decrease_priority(target, alternative);
+ }
}
// this is what makes this special: instead of getting the next node as the start of next
@@ -156,25 +180,28 @@
// for neighbour in get_neighbours() { ... }
// ```
// Only difference is that we do not have generators in stable Rust (yet).
- let Some(node) = self.queue.pop_min() else {
- self.next = None;
+ let Some(item) = self.queue.pop_min() else {
+ // next is already `None`
return None;
};
- self.next = Some(node);
+ let node = item.node;
+ let cost = item.priority.clone();
+ self.next = Some(item);
// we're currently visiting the node that has the shortest distance, therefore we know
// that the distance is the shortest possible
- let distance = self.distances[node.id()].clone();
+ let distance = cost;
let transit = if self.predecessor_mode == PredecessorMode::Discard {
Vec::new()
} else {
reconstruct_path_to(&self.predecessors, node.id())
};
- let path = Path::new(self.source, transit, node);
-
- Some(Route::new(path, Cost::new(distance)))
+ Some(Route::new(
+ Path::new(self.source, transit, node),
+ Cost::new(distance),
+ ))
}
fn size_hint(&self) -> (usize, Option<usize>) {
diff --git a/crates/algorithms/src/shortest_paths/dijkstra/mod.rs b/crates/algorithms/src/shortest_paths/dijkstra/mod.rs
index b8e5c77..69732e4 100644
--- a/crates/algorithms/src/shortest_paths/dijkstra/mod.rs
+++ b/crates/algorithms/src/shortest_paths/dijkstra/mod.rs
@@ -11,6 +11,9 @@
use error_stack::Result;
use petgraph_core::{
edge::marker::{Directed, Undirected},
+ Direction,
+ },
+ id::FlaggableGraphId,
DirectedGraphStorage, Graph, GraphDirectionality, GraphStorage, Node,
};
@@ -174,7 +177,7 @@
impl<S, E> ShortestPath<S> for Dijkstra<Undirected, E>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
{
@@ -258,7 +261,7 @@
impl<S, E> ShortestDistance<S> for Dijkstra<Undirected, E>
where
S: GraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
{
@@ -307,7 +310,7 @@
impl<S, E> ShortestDistance<S> for Dijkstra<Directed, E>
where
S: DirectedGraphStorage,
- S::NodeId: Eq + Hash,
+ S::NodeId: FlaggableGraphId<S> + AttributeGraphId<S>,
E: GraphCost<S>,
E::Value: DijkstraMeasure,
{
diff --git a/crates/algorithms/src/shortest_paths/mod.rs b/crates/algorithms/src/shortest_paths/mod.rs
index 8e7f26d..fb51d08 100644
--- a/crates/algorithms/src/shortest_paths/mod.rs
+++ b/crates/algorithms/src/shortest_paths/mod.rs
@@ -60,7 +60,7 @@
use petgraph_core::{Graph, GraphStorage};
pub use self::{
- astar::AStar,
+ // astar::AStar,
bellman_ford::BellmanFord,
common::{
cost::{Cost, GraphCost},
diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs
index 5cd81db..ec86092 100644
--- a/crates/core/src/error.rs
+++ b/crates/core/src/error.rs
@@ -1,5 +1,7 @@
use core::fmt::{Display, Formatter};
+use error_stack::Context;
+
/// General error type for `petgraph-core`.
///
/// This error is used in [`Graph`] as a context from the returned result of fallible
diff --git a/crates/core/src/id/attributes.rs b/crates/core/src/id/attributes.rs
new file mode 100644
index 0000000..adb91be
--- /dev/null
+++ b/crates/core/src/id/attributes.rs
@@ -0,0 +1,36 @@
+use crate::{base::MaybeOwned, GraphStorage};
+
+// TODO: Entry API
+
+pub trait AttributeStorage<K, V> {
+ fn get(&self, id: &K) -> Option<&V>;
+ fn get_mut(&mut self, id: &K) -> Option<&mut V>;
+ fn index(&self, id: &K) -> &V {
+ self.get(id).unwrap()
+ }
+ fn index_mut(&mut self, id: &K) -> &mut V {
+ self.get_mut(id).unwrap()
+ }
+
+ fn set(&mut self, id: &K, value: V) -> Option<V>;
+ fn remove(&mut self, id: &K) -> Option<V>;
+
+ type Iter<'a>: Iterator<Item = (MaybeOwned<'a, K>, &'a V)>
+ where
+ K: 'a,
+ V: 'a,
+ Self: 'a;
+
+ fn iter(&self) -> Self::Iter<'_>;
+}
+
+pub trait AttributeGraphId<S>: Sized
+where
+ S: GraphStorage,
+{
+ type Store<'a, V>: AttributeStorage<Self, V>
+ where
+ S: 'a;
+
+ fn attribute_store<V>(storage: &S) -> Self::Store<'_, V>;
+}
diff --git a/crates/core/src/id/flag.rs b/crates/core/src/id/flag.rs
new file mode 100644
index 0000000..4cad073
--- /dev/null
+++ b/crates/core/src/id/flag.rs
@@ -0,0 +1,23 @@
+use crate::GraphStorage;
+// TODO: naming
+
+pub trait FlagStorage<Id> {
+ fn get(&self, id: &Id) -> Option<bool>;
+ #[inline]
+ fn index(&self, id: &Id) -> bool {
+ self.get(id).unwrap_or(false)
+ }
+
+ fn set(&mut self, id: &Id, flag: bool) -> Option<bool>;
+}
+
+pub trait FlaggableGraphId<S>: Sized
+where
+ S: GraphStorage,
+{
+ type Store<'a>: FlagStorage<Self>
+ where
+ S: 'a;
+
+ fn flag_store(storage: &S) -> Self::Store<'_>;
+}
diff --git a/crates/core/src/id/mod.rs b/crates/core/src/id/mod.rs
index f5eb4be..f298d14 100644
--- a/crates/core/src/id/mod.rs
+++ b/crates/core/src/id/mod.rs
@@ -7,9 +7,15 @@
//! exclusive and define if a node or edge id will be automatically assigned by the graph (are
//! managed) and a user has no control over their value or are arbitrary, allowing the user to use
//! _any_ value.
+mod attributes;
+mod flag;
mod linear;
-pub use self::linear::{IndexMapper, LinearGraphId};
+pub use self::{
+ attributes::{AttributeGraphId, AttributeStorage},
+ flag::{FlagStorage, FlaggableGraphId},
+ linear::{IndexMapper, LinearGraphId},
+};
use crate::attributes::NoValue;
// The `PartialEq` bound is required for the default implementation, we could in theory remove it,
diff --git a/crates/dino/Cargo.toml b/crates/dino/Cargo.toml
index 0c0e715..758ab55 100644
--- a/crates/dino/Cargo.toml
+++ b/crates/dino/Cargo.toml
@@ -11,8 +11,10 @@
error-stack.workspace = true
either = "1.9.0" # TODO: potentially remove
+bitvec = "1.0.1"
# Closures
roaring = "0.10.2"
+croaring = "1.0.1"
fnv = { version = "1.0.7", default-features = false }
hashbrown = { workspace = true, features = ['ahash'] }
diff --git a/crates/dino/src/closure/mod.rs b/crates/dino/src/closure/mod.rs
index 5495d96..6ab397d 100644
--- a/crates/dino/src/closure/mod.rs
+++ b/crates/dino/src/closure/mod.rs
@@ -1,29 +1,20 @@
mod union;
+use croaring::Bitmap as RoaringBitmap;
use either::Either;
use fnv::FnvBuildHasher;
-// The closure tables have quite a bit of allocations (due to the nested nature of the data
-// structure). Question is can we avoid them?
use hashbrown::HashMap;
-use roaring::RoaringBitmap;
-use self::union::UnionIterator;
+pub(crate) use self::union::UnionIterator;
use crate::{
- closure::union::UnionIntoIterator,
- edge::Edge,
- node::Node,
+ edge::{Edge, EdgeSlab},
+ node::{Node, NodeSlab},
slab::{EntryId, Key as _, Slab},
EdgeId, NodeId,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Key {
- SourceToTargets(NodeId),
- TargetToSources(NodeId),
-
- SourceToEdges(NodeId),
- TargetsToEdges(NodeId),
-
EndpointsToEdges(NodeId, NodeId),
}
@@ -41,138 +32,118 @@
}
}
- fn create_edge<T>(&mut self, edge: &Edge<T>) {
+ fn create_edge<T, U>(&mut self, edge: &Edge<T>, nodes: &mut NodeSlab<U>) {
let raw_index = edge.id.into_id().raw();
let source = edge.source;
let target = edge.target;
- self.inner
- .entry(Key::SourceToTargets(source))
- .or_default()
- .insert(target.into_id().raw());
+ if let Some(source) = nodes.get_mut(source) {
+ source.closures.outgoing_nodes.push(target);
+ source.closures.outgoing_edges.push(edge.id);
+ }
- self.inner
- .entry(Key::TargetToSources(target))
- .or_default()
- .insert(source.into_id().raw());
-
- self.inner
- .entry(Key::SourceToEdges(source))
- .or_default()
- .insert(raw_index);
-
- self.inner
- .entry(Key::TargetsToEdges(target))
- .or_default()
- .insert(raw_index);
+ if let Some(target) = nodes.get_mut(target) {
+ target.closures.incoming_nodes.push(source);
+ target.closures.incoming_edges.push(edge.id);
+ }
self.inner
.entry(Key::EndpointsToEdges(source, target))
.or_default()
- .insert(raw_index);
+ .add(raw_index);
}
- fn remove_edge<T>(&mut self, edge: &Edge<T>) {
- let raw_index = edge.id.into_id().raw();
-
- let source = edge.source;
- let target = edge.target;
-
- let is_multi = self
- .inner
- .get(&Key::EndpointsToEdges(edge.source, edge.target))
- .map_or(false, |bitmap| bitmap.len() > 1);
-
- if !is_multi {
- if let Some(targets) = self.inner.get_mut(&Key::SourceToTargets(source)) {
- targets.remove(edge.target.into_id().raw());
- }
-
- if let Some(sources) = self.inner.get_mut(&Key::TargetToSources(target)) {
- sources.remove(edge.source.into_id().raw());
- }
- }
-
- if let Some(edges) = self.inner.get_mut(&Key::SourceToEdges(source)) {
- edges.remove(raw_index);
- }
-
- if let Some(edges) = self.inner.get_mut(&Key::TargetsToEdges(target)) {
- edges.remove(raw_index);
- }
-
- if let Some(edges) = self.inner.get_mut(&Key::EndpointsToEdges(source, target)) {
- edges.remove(raw_index);
-
- if edges.is_empty() {
- self.inner.remove(&Key::EndpointsToEdges(source, target));
- }
- }
+ fn remove_edge<T, U>(&mut self, edge: &Edge<T>, nodes: &mut NodeSlab<U>) {
+ todo!()
+ // let raw_index = edge.id.into_id().raw();
+ //
+ // let source = edge.source;
+ // let target = edge.target;
+ //
+ // let is_multi = self
+ // .inner
+ // .get(&Key::EndpointsToEdges(edge.source, edge.target))
+ // .map_or(false, |bitmap| bitmap.cardinality() > 1);
+ //
+ // if let Some(source) = nodes.get_mut(source) {
+ // if !is_multi {
+ // source
+ // .closures
+ // .outgoing_nodes
+ // .remove(target.into_id().raw());
+ // }
+ //
+ // source.closures.outgoing_edges.remove(raw_index);
+ // }
+ //
+ // if let Some(target) = nodes.get_mut(target) {
+ // if !is_multi {
+ // target
+ // .closures
+ // .incoming_nodes
+ // .remove(source.into_id().raw());
+ // }
+ //
+ // target.closures.incoming_edges.remove(raw_index);
+ // }
+ //
+ // if let Some(edges) = self.inner.get_mut(&Key::EndpointsToEdges(source, target)) {
+ // edges.remove(raw_index);
+ //
+ // if edges.is_empty() {
+ // self.inner.remove(&Key::EndpointsToEdges(source, target));
+ // }
+ // }
}
- fn create_node<T>(&mut self, node: &Node<T>) {
- self.inner
- .insert(Key::SourceToTargets(node.id), RoaringBitmap::new());
- self.inner
- .insert(Key::TargetToSources(node.id), RoaringBitmap::new());
-
- self.inner
- .insert(Key::SourceToEdges(node.id), RoaringBitmap::new());
- self.inner
- .insert(Key::TargetsToEdges(node.id), RoaringBitmap::new());
-
- self.nodes.insert(node.id.into_id().raw());
+ fn remove_node<T>(&mut self, node: Node<T>, nodes: &mut NodeSlab<T>) -> (NodeId, T) {
+ todo!()
+ // let raw_index = node.id.into_id().raw();
+ //
+ // let targets = node.closures.outgoing_nodes;
+ // for target in targets.iter() {
+ // let target_id = NodeId::from_id(EntryId::new_unchecked(target));
+ //
+ // let Some(target) = nodes.get_mut(target_id) else {
+ // continue;
+ // };
+ //
+ // target.closures.incoming_nodes.remove(raw_index);
+ //
+ // self.inner
+ // .remove(&Key::EndpointsToEdges(node.id, target_id));
+ // }
+ //
+ // let sources = node.closures.incoming_nodes;
+ // for source in sources.iter() {
+ // let source_id = NodeId::from_id(EntryId::new_unchecked(source));
+ //
+ // let Some(source) = nodes.get_mut(source_id) else {
+ // continue;
+ // };
+ //
+ // source.closures.outgoing_nodes.remove(raw_index);
+ // self.inner
+ // .remove(&Key::EndpointsToEdges(source_id, node.id));
+ // }
+ //
+ // (node.id, node.weight)
}
- fn remove_node<T>(&mut self, node: &Node<T>) {
- let raw_index = node.id.into_id().raw();
-
- let targets = self.inner.remove(&Key::SourceToTargets(node.id));
-
- if let Some(targets) = targets {
- for target in targets {
- let target = NodeId::from_id(EntryId::new_unchecked(target));
- if let Some(sources) = self.inner.get_mut(&Key::TargetToSources(target)) {
- sources.remove(raw_index);
- }
-
- self.inner.remove(&Key::EndpointsToEdges(node.id, target));
- }
- }
-
- let sources = self.inner.remove(&Key::TargetToSources(node.id));
-
- if let Some(sources) = sources {
- for source in sources {
- let source = NodeId::from_id(EntryId::new_unchecked(source));
- if let Some(targets) = self.inner.get_mut(&Key::SourceToTargets(source)) {
- targets.remove(raw_index);
- }
-
- self.inner.remove(&Key::EndpointsToEdges(source, node.id));
- }
- }
-
- self.inner.remove(&Key::SourceToEdges(node.id));
- self.inner.remove(&Key::TargetsToEdges(node.id));
-
- self.nodes.remove(raw_index);
- }
-
- fn clear(&mut self) {
+ fn clear<N>(&mut self, nodes: &mut NodeSlab<N>) {
self.inner.clear();
+
+ for node in nodes.iter_mut() {
+ node.closures.clear();
+ }
}
- fn refresh<N, E>(&mut self, nodes: &Slab<NodeId, Node<N>>, edges: &Slab<EdgeId, Edge<E>>) {
- self.clear();
-
- for node in nodes.iter() {
- self.create_node(node);
- }
+ fn refresh<N, E>(&mut self, nodes: &mut NodeSlab<N>, edges: &EdgeSlab<E>) {
+ self.clear(nodes);
for edge in edges.iter() {
- self.create_edge(edge);
+ self.create_edge(edge, nodes);
}
}
@@ -185,122 +156,6 @@
}
}
-pub(crate) struct NodeClosure<'a> {
- storage: &'a ClosureStorage,
-}
-
-impl<'a> NodeClosure<'a> {
- const fn new(storage: &'a ClosureStorage) -> Self {
- Self { storage }
- }
-
- pub(crate) fn outgoing_neighbours(&self, id: NodeId) -> impl Iterator<Item = NodeId> + 'a {
- let Some(bitmap) = self.storage.inner.get(&Key::SourceToTargets(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- Either::Right(
- bitmap
- .iter()
- .map(|value| NodeId::from_id(EntryId::new_unchecked(value))),
- )
- }
-
- pub(crate) fn incoming_neighbours(&self, id: NodeId) -> impl Iterator<Item = NodeId> + 'a {
- let Some(bitmap) = self.storage.inner.get(&Key::TargetToSources(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- Either::Right(
- bitmap
- .iter()
- .map(|value| NodeId::from_id(EntryId::new_unchecked(value))),
- )
- }
-
- pub(crate) fn neighbours(&self, id: NodeId) -> impl Iterator<Item = NodeId> + 'a {
- let Some(left) = self.storage.inner.get(&Key::SourceToTargets(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- let Some(right) = self.storage.inner.get(&Key::TargetToSources(id)) else {
- return Either::Right(Either::Left(
- left.iter()
- .map(|value| NodeId::from_id(EntryId::new_unchecked(value))),
- ));
- };
-
- Either::Right(Either::Right(
- UnionIterator::new(left, right)
- .map(|value| NodeId::from_id(EntryId::new_unchecked(value))),
- ))
- }
-
- pub(crate) fn outgoing_edges(&self, id: NodeId) -> impl Iterator<Item = EdgeId> + 'a {
- let Some(bitmap) = self.storage.inner.get(&Key::SourceToEdges(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- Either::Right(
- bitmap
- .iter()
- .map(|value| EdgeId::from_id(EntryId::new_unchecked(value))),
- )
- }
-
- pub(crate) fn incoming_edges(&self, id: NodeId) -> impl Iterator<Item = EdgeId> + 'a {
- let Some(bitmap) = self.storage.inner.get(&Key::TargetsToEdges(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- Either::Right(
- bitmap
- .iter()
- .map(|value| EdgeId::from_id(EntryId::new_unchecked(value))),
- )
- }
-
- pub(crate) fn edges(&self, id: NodeId) -> impl Iterator<Item = EdgeId> + 'a {
- let Some(left) = self.storage.inner.get(&Key::SourceToEdges(id)) else {
- return Either::Left(core::iter::empty());
- };
-
- let Some(right) = self.storage.inner.get(&Key::TargetsToEdges(id)) else {
- return Either::Right(Either::Left(
- left.iter()
- .map(|value| EdgeId::from_id(EntryId::new_unchecked(value))),
- ));
- };
-
- Either::Right(Either::Right(
- UnionIterator::new(left, right)
- .map(|value| EdgeId::from_id(EntryId::new_unchecked(value))),
- ))
- }
-
- pub(crate) fn externals(&self) -> impl Iterator<Item = NodeId> + 'a {
- self.storage.nodes.iter().filter_map(|index| {
- let id = NodeId::from_id(EntryId::new_unchecked(index));
-
- let has_source_to_targets = self
- .storage
- .inner
- .get(&Key::SourceToTargets(id))
- .map_or(false, |bitmap| !bitmap.is_empty());
-
- let has_target_to_sources = self
- .storage
- .inner
- .get(&Key::TargetToSources(id))
- .map_or(false, |bitmap| !bitmap.is_empty());
-
- let is_external = !has_source_to_targets && !has_target_to_sources;
-
- is_external.then_some(id)
- })
- }
-}
-
pub(crate) struct EdgeClosure<'a> {
storage: &'a ClosureStorage,
}
@@ -374,54 +229,20 @@
}
}
- pub(crate) const fn nodes(&self) -> NodeClosure<'_> {
- NodeClosure::new(&self.storage)
- }
-
- pub(crate) fn create_node<T>(&mut self, node: &Node<T>) {
- self.storage.create_node(node);
- }
-
- pub(crate) fn remove_node<T>(&mut self, node: &Node<T>) {
- self.storage.remove_node(node);
+ pub(crate) fn remove_node<T>(&mut self, node: Node<T>, nodes: &mut NodeSlab<T>) -> (NodeId, T) {
+ self.storage.remove_node(node, nodes)
}
pub(crate) const fn edges(&self) -> EdgeClosure<'_> {
EdgeClosure::new(&self.storage)
}
- pub(crate) fn create_edge<T>(&mut self, edge: &Edge<T>) {
- self.storage.create_edge(edge);
+ pub(crate) fn create_edge<T, U>(&mut self, edge: &Edge<T>, nodes: &mut NodeSlab<U>) {
+ self.storage.create_edge(edge, nodes);
}
- pub(crate) fn remove_edge<T>(&mut self, edge: &Edge<T>) {
- self.storage.remove_edge(edge);
- }
-
- /// Drain the `SourceToTargets` and `TargetToSources` entries for the given node
- ///
- /// Returns an iterator of all edges and ensures that there are no duplicates.
- ///
- /// # Note
- ///
- /// To completely remove the edge you also need to call `remove_edge`.
- /// This leaves the closure table in an incomplete (although recoverable) state, use
- /// `remove_node` to completely remove a node, or call `refresh` to rebuild the closure
- /// table.
- pub(crate) fn drain_edges(&mut self, node: NodeId) -> impl Iterator<Item = EdgeId> {
- let left = self.storage.inner.remove(&Key::SourceToEdges(node));
- let right = self.storage.inner.remove(&Key::TargetsToEdges(node));
-
- let iter = match (left, right) {
- (None, None) => Either::Left(core::iter::empty()),
- (Some(left), None) => Either::Right(Either::Left(left.into_iter())),
- (None, Some(right)) => Either::Right(Either::Left(right.into_iter())),
- (Some(left), Some(right)) => {
- Either::Right(Either::Right(UnionIntoIterator::new(left, right)))
- }
- };
-
- iter.map(|id| EdgeId::from_id(EntryId::new_unchecked(id)))
+ pub(crate) fn remove_edge<T, U>(&mut self, edge: &Edge<T>, nodes: &mut NodeSlab<U>) {
+ self.storage.remove_edge(edge, nodes);
}
pub(crate) fn reserve(&mut self, additional: usize) {
@@ -434,28 +255,30 @@
pub(crate) fn refresh<N, E>(
&mut self,
- nodes: &Slab<NodeId, Node<N>>,
+ nodes: &mut Slab<NodeId, Node<N>>,
edges: &Slab<EdgeId, Edge<E>>,
) {
self.storage.refresh(nodes, edges);
}
- pub(crate) fn clear(&mut self) {
- self.storage.clear();
+ pub(crate) fn clear<N>(&mut self, nodes: &mut NodeSlab<N>) {
+ self.storage.clear(nodes);
}
}
#[cfg(test)]
mod tests {
+ use alloc::vec::Vec;
use core::iter::once;
use hashbrown::{HashMap, HashSet};
- use petgraph_core::{attributes::Attributes, edge::marker::Directed};
+ use petgraph_core::{attributes::Attributes, edge::marker::Directed, GraphDirectionality};
+ use roaring::RoaringBitmap;
use crate::{
- closure::{Closures, Key},
+ closure::Key,
slab::{EntryId, Key as _},
- DinoGraph, EdgeId, NodeId,
+ DinoGraph, DinoStorage, EdgeId, NodeId,
};
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -472,19 +295,19 @@
}
impl EvaluatedNodeClosure {
- fn new(closures: &Closures, id: NodeId) -> Self {
- let lookup = closures.nodes();
+ fn new<N, E>(storage: &DinoStorage<N, E>, id: NodeId) -> Self {
+ let node = storage.nodes.get(id).expect("node not found");
Self {
- outgoing_neighbours: lookup.outgoing_neighbours(id).collect(),
- incoming_neighbours: lookup.incoming_neighbours(id).collect(),
+ outgoing_neighbours: node.outgoing_neighbours().collect(),
+ incoming_neighbours: node.incoming_neighbours().collect(),
- neighbours: lookup.neighbours(id).collect(),
+ neighbours: node.neighbours().collect(),
- outgoing_edges: lookup.outgoing_edges(id).collect(),
- incoming_edges: lookup.incoming_edges(id).collect(),
+ outgoing_edges: node.outgoing_edges().collect(),
+ incoming_edges: node.incoming_edges().collect(),
- edges: lookup.edges(id).collect(),
+ edges: node.edges().collect(),
}
}
}
@@ -501,71 +324,40 @@
}
impl EvaluatedEdgeClosures {
- fn new(closures: &Closures) -> Self {
+ fn new<N, E>(storage: &DinoStorage<N, E>) -> Self {
+ fn node_id_set(bitmap: &[NodeId]) -> HashSet<NodeId> {
+ bitmap.iter().copied().collect()
+ }
+
+ fn edge_id_set(bitmap: &[EdgeId]) -> HashSet<EdgeId> {
+ bitmap.iter().copied().collect()
+ }
+
Self {
- source_to_targets: closures
- .storage
- .inner
- .iter()
- .filter_map(|(key, bitmap)| match key {
- Key::SourceToTargets(id) => Some((
- *id,
- bitmap
- .iter()
- .map(|id| NodeId::from_id(EntryId::new_unchecked(id)))
- .collect::<HashSet<_>>(),
- )),
- _ => None,
- })
+ source_to_targets: storage
+ .nodes
+ .entries()
+ .map(|(id, node)| (id, node_id_set(&node.closures.outgoing_nodes)))
.collect(),
- target_to_sources: closures
- .storage
- .inner
- .iter()
- .filter_map(|(key, bitmap)| match key {
- Key::TargetToSources(id) => Some((
- *id,
- bitmap
- .iter()
- .map(|id| NodeId::from_id(EntryId::new_unchecked(id)))
- .collect::<HashSet<_>>(),
- )),
- _ => None,
- })
+ target_to_sources: storage
+ .nodes
+ .entries()
+ .map(|(id, node)| (id, node_id_set(&node.closures.incoming_nodes)))
.collect(),
- source_to_edges: closures
- .storage
- .inner
- .iter()
- .filter_map(|(key, bitmap)| match key {
- Key::SourceToEdges(id) => Some((
- *id,
- bitmap
- .iter()
- .map(|id| EdgeId::from_id(EntryId::new_unchecked(id)))
- .collect::<HashSet<_>>(),
- )),
- _ => None,
- })
+ source_to_edges: storage
+ .nodes
+ .entries()
+ .map(|(id, node)| (id, edge_id_set(&node.closures.outgoing_edges)))
.collect(),
- targets_to_edges: closures
- .storage
- .inner
- .iter()
- .filter_map(|(key, bitmap)| match key {
- Key::TargetsToEdges(id) => Some((
- *id,
- bitmap
- .iter()
- .map(|id| EdgeId::from_id(EntryId::new_unchecked(id)))
- .collect::<HashSet<_>>(),
- )),
- _ => None,
- })
+ targets_to_edges: storage
+ .nodes
+ .entries()
+ .map(|(id, node)| (id, edge_id_set(&node.closures.incoming_edges)))
.collect(),
- endpoints_to_edges: closures
+ endpoints_to_edges: storage
+ .closures
.storage
.inner
.iter()
@@ -599,16 +391,6 @@
}};
}
- fn assert_storage_size(closure: &Closures, expected_nodes: usize, expected_endpoints: usize) {
- // expected_nodes * (SourceToTargets + TargetToSources + SourceToEdges + TargetToEdges)
- // + expected_endpoints * EndpointsToEdges
-
- assert_eq!(
- closure.storage.inner.len(),
- 4 * expected_nodes + expected_endpoints
- );
- }
-
#[test]
fn single_node() {
let mut graph = DinoGraph::<u8, u8, Directed>::new();
@@ -618,14 +400,10 @@
let closures = &graph.storage().closures;
- assert_storage_size(closures, 1, 0);
- assert_eq!(
- closures.nodes().externals().collect::<HashSet<_>>(),
- once(id).collect()
- );
+ assert_eq!(externals(&graph), once(id).collect());
assert_eq!(
- EvaluatedNodeClosure::new(closures, id),
+ EvaluatedNodeClosure::new(graph.storage(), id),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: HashSet::new(),
@@ -637,7 +415,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
id => HashSet::new(),
@@ -656,6 +434,18 @@
);
}
+ fn externals<N, E, D>(graph: &DinoGraph<N, E, D>) -> HashSet<NodeId>
+ where
+ D: GraphDirectionality,
+ {
+ graph
+ .storage()
+ .nodes
+ .entries()
+ .filter_map(|(id, node)| node.is_isolated().then_some(id))
+ .collect()
+ }
+
#[test]
fn multiple_nodes() {
let mut graph = DinoGraph::<u8, u8, Directed>::new();
@@ -666,16 +456,10 @@
let b = graph.try_insert_node(Attributes::new(2)).unwrap();
let b = *b.id();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 2, 0);
- assert_eq!(
- closures.nodes().externals().collect::<HashSet<_>>(),
- [a, b].into_iter().collect()
- );
+ assert_eq!(externals(&graph), [a, b].into_iter().collect());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: HashSet::new(),
@@ -687,7 +471,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, b),
+ EvaluatedNodeClosure::new(graph.storage(), b),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: HashSet::new(),
@@ -699,7 +483,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => HashSet::new(),
@@ -735,13 +519,10 @@
let edge = graph.try_insert_edge(1u8, &a, &b).unwrap();
let edge = *edge.id();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 2, 1);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: once(b).collect(),
incoming_neighbours: HashSet::new(),
@@ -753,7 +534,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, b),
+ EvaluatedNodeClosure::new(graph.storage(), b),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: once(a).collect(),
@@ -765,7 +546,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => once(b).collect(),
@@ -800,13 +581,10 @@
let edge = graph.try_insert_edge(1u8, &a, &a).unwrap();
let edge = *edge.id();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 1, 1);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: once(a).collect(),
incoming_neighbours: once(a).collect(),
@@ -818,7 +596,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => once(a).collect(),
@@ -897,13 +675,10 @@
let (a, b, c, ab, bc, ca) = (*a, *b, *c, *ab, *bc, *ca);
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 3, 3);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: once(b).collect(),
incoming_neighbours: once(c).collect(),
@@ -915,7 +690,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, b),
+ EvaluatedNodeClosure::new(graph.storage(), b),
EvaluatedNodeClosure {
outgoing_neighbours: once(c).collect(),
incoming_neighbours: once(a).collect(),
@@ -927,7 +702,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, c),
+ EvaluatedNodeClosure::new(graph.storage(), c),
EvaluatedNodeClosure {
outgoing_neighbours: once(a).collect(),
incoming_neighbours: once(b).collect(),
@@ -939,7 +714,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => once(b).collect(),
@@ -993,13 +768,10 @@
let ab2 = graph.try_insert_edge(1u8, &a, &b).unwrap();
let ab2 = *ab2.id();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 2, 1);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: once(b).collect(),
incoming_neighbours: HashSet::new(),
@@ -1011,7 +783,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, b),
+ EvaluatedNodeClosure::new(graph.storage(), b),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: once(a).collect(),
@@ -1023,7 +795,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => once(b).collect(),
@@ -1064,13 +836,10 @@
graph.remove_node(&b).unwrap();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 2, 1);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: once(c).collect(),
@@ -1082,7 +851,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, c),
+ EvaluatedNodeClosure::new(graph.storage(), c),
EvaluatedNodeClosure {
outgoing_neighbours: once(a).collect(),
incoming_neighbours: HashSet::new(),
@@ -1094,7 +863,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => HashSet::new(),
@@ -1136,13 +905,10 @@
graph.remove_edge(&bc).unwrap();
- let closures = &graph.storage().closures;
-
- assert_storage_size(closures, 3, 2);
- assert_eq!(closures.nodes().externals().count(), 0);
+ assert!(externals(&graph).is_empty());
assert_eq!(
- EvaluatedNodeClosure::new(closures, a),
+ EvaluatedNodeClosure::new(graph.storage(), a),
EvaluatedNodeClosure {
outgoing_neighbours: once(b).collect(),
incoming_neighbours: once(c).collect(),
@@ -1154,7 +920,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, b),
+ EvaluatedNodeClosure::new(graph.storage(), b),
EvaluatedNodeClosure {
outgoing_neighbours: HashSet::new(),
incoming_neighbours: once(a).collect(),
@@ -1166,7 +932,7 @@
);
assert_eq!(
- EvaluatedNodeClosure::new(closures, c),
+ EvaluatedNodeClosure::new(graph.storage(), c),
EvaluatedNodeClosure {
outgoing_neighbours: once(a).collect(),
incoming_neighbours: HashSet::new(),
@@ -1178,7 +944,7 @@
);
assert_eq!(
- EvaluatedEdgeClosures::new(closures),
+ EvaluatedEdgeClosures::new(graph.storage()),
EvaluatedEdgeClosures {
source_to_targets: map! {
a => once(b).collect(),
diff --git a/crates/dino/src/closure/union.rs b/crates/dino/src/closure/union.rs
index 24c6a0f..60e74de 100644
--- a/crates/dino/src/closure/union.rs
+++ b/crates/dino/src/closure/union.rs
@@ -1,4 +1,4 @@
-use roaring::RoaringBitmap;
+use croaring::Bitmap as RoaringBitmap;
#[derive(Default)]
struct IteratorState {
@@ -83,35 +83,9 @@
}
}
-pub(crate) struct UnionIntoIterator {
- left: roaring::bitmap::IntoIter,
- right: roaring::bitmap::IntoIter,
-
- state: IteratorState,
-}
-
-impl UnionIntoIterator {
- pub(crate) fn new(left: RoaringBitmap, right: RoaringBitmap) -> Self {
- Self {
- left: left.into_iter(),
- right: right.into_iter(),
-
- state: IteratorState::default(),
- }
- }
-}
-
-impl Iterator for UnionIntoIterator {
- type Item = u32;
-
- fn next(&mut self) -> Option<Self::Item> {
- self.state.next(&mut self.left, &mut self.right)
- }
-}
-
pub(crate) struct UnionIterator<'a> {
- left: roaring::bitmap::Iter<'a>,
- right: roaring::bitmap::Iter<'a>,
+ left: croaring::bitmap::BitmapIterator<'a>,
+ right: croaring::bitmap::BitmapIterator<'a>,
state: IteratorState,
}
@@ -142,9 +116,9 @@
mod tests {
use alloc::vec::Vec;
- use roaring::RoaringBitmap;
+ use croaring::Bitmap as RoaringBitmap;
- use crate::closure::union::{UnionIntoIterator, UnionIterator};
+ use crate::closure::union::UnionIterator;
#[test]
fn empty() {
@@ -153,51 +127,39 @@
let iterator = UnionIterator::new(&a, &b);
assert_eq!(iterator.count(), 0);
-
- let iterator = UnionIntoIterator::new(a, b);
- assert_eq!(iterator.count(), 0);
}
#[test]
fn non_overlapping() {
- let a = RoaringBitmap::from_sorted_iter(0..10).unwrap();
- let b = RoaringBitmap::from_sorted_iter(10..20).unwrap();
+ let a = RoaringBitmap::from_iter(0..10);
+ let b = RoaringBitmap::from_iter(10..20);
let iterator = UnionIterator::new(&a, &b);
assert_eq!(iterator.collect::<Vec<_>>(), (0..20).collect::<Vec<_>>());
-
- let iterator = UnionIntoIterator::new(a, b);
- assert_eq!(iterator.collect::<Vec<_>>(), (0..20).collect::<Vec<_>>());
}
#[test]
fn overlapping() {
- let a = RoaringBitmap::from_sorted_iter(0..10).unwrap();
- let b = RoaringBitmap::from_sorted_iter(5..15).unwrap();
+ let a = RoaringBitmap::from_iter(0..10);
+ let b = RoaringBitmap::from_iter(5..15);
let iterator = UnionIterator::new(&a, &b);
assert_eq!(iterator.collect::<Vec<_>>(), (0..15).collect::<Vec<_>>());
-
- let iterator = UnionIntoIterator::new(a, b);
- assert_eq!(iterator.collect::<Vec<_>>(), (0..15).collect::<Vec<_>>());
}
#[test]
fn multiple_overlapping_regions() {
- let mut a = RoaringBitmap::from_sorted_iter(0..10).unwrap();
- let mut b = RoaringBitmap::from_sorted_iter(5..15).unwrap();
+ let mut a = RoaringBitmap::from_iter(0..10);
+ let mut b = RoaringBitmap::from_iter(5..15);
- a.insert_range(20..30);
- b.insert_range(25..35);
+ a.add_range(20..30);
+ b.add_range(25..35);
- a.insert_range(40..50);
- b.insert_range(15..21);
- b.insert_range(29..42);
+ a.add_range(40..50);
+ b.add_range(15..21);
+ b.add_range(29..42);
let iterator = UnionIterator::new(&a, &b);
assert_eq!(iterator.collect::<Vec<_>>(), (0..50).collect::<Vec<_>>());
-
- let iterator = UnionIntoIterator::new(a, b);
- assert_eq!(iterator.collect::<Vec<_>>(), (0..50).collect::<Vec<_>>());
}
}
diff --git a/crates/dino/src/directed.rs b/crates/dino/src/directed.rs
index 9e774aa..37ed7b6 100644
--- a/crates/dino/src/directed.rs
+++ b/crates/dino/src/directed.rs
@@ -5,7 +5,9 @@
storage::{DirectedGraphStorage, GraphStorage},
};
-use crate::{DinoStorage, Directed};
+use crate::{
+ iter::directed::NodeDirectedConnectionsIter, node::NodeClosures, DinoStorage, Directed,
+};
impl<N, E> DirectedGraphStorage for DinoStorage<N, E, Directed> {
fn directed_edges_between<'a: 'b, 'b>(
@@ -40,13 +42,13 @@
id: &'b Self::NodeId,
direction: Direction,
) -> impl Iterator<Item = Edge<'a, Self>> + 'b {
- let closure = self.closures.nodes();
-
- match direction {
- Direction::Incoming => Either::Left(closure.incoming_edges(*id)),
- Direction::Outgoing => Either::Right(closure.outgoing_edges(*id)),
+ NodeDirectedConnectionsIter {
+ storage: self,
+ iter: self.nodes.get(*id).map(|node| match direction {
+ Direction::Incoming => node.closures.incoming_edges(),
+ Direction::Outgoing => node.closures.outgoing_edges(),
+ }),
}
- .filter_map(move |id| self.edge(&id))
}
fn node_directed_connections_mut<'a: 'b, 'b>(
@@ -54,19 +56,18 @@
id: &'b Self::NodeId,
direction: Direction,
) -> impl Iterator<Item = EdgeMut<'a, Self>> + 'b {
- let Self {
- closures, edges, ..
- } = self;
+ let Self { nodes, edges, .. } = self;
- let closure = closures.nodes();
-
- let available = match direction {
- Direction::Incoming => Either::Left(closure.incoming_edges(*id)),
- Direction::Outgoing => Either::Right(closure.outgoing_edges(*id)),
- };
+ let allow = nodes
+ .get(*id)
+ .into_iter()
+ .flat_map(move |node| match direction {
+ Direction::Incoming => Either::Left(node.incoming_edges()),
+ Direction::Outgoing => Either::Right(node.outgoing_edges()),
+ });
edges
- .filter_mut(available)
+ .filter_mut(allow)
.map(|edge| EdgeMut::new(&edge.id, &mut edge.weight, &edge.source, &edge.target))
}
@@ -75,13 +76,14 @@
id: &'b Self::NodeId,
direction: Direction,
) -> impl Iterator<Item = Node<'a, Self>> + 'b {
- let closure = self.closures.nodes();
-
- match direction {
- Direction::Incoming => Either::Left(closure.incoming_neighbours(*id)),
- Direction::Outgoing => Either::Right(closure.outgoing_neighbours(*id)),
- }
- .filter_map(move |id| self.node(&id))
+ self.nodes
+ .get(*id)
+ .into_iter()
+ .flat_map(move |node| match direction {
+ Direction::Incoming => Either::Left(node.incoming_neighbours()),
+ Direction::Outgoing => Either::Right(node.outgoing_neighbours()),
+ })
+ .filter_map(move |id| self.node(&id))
}
fn node_directed_neighbours_mut<'a: 'b, 'b>(
@@ -89,19 +91,22 @@
id: &'b Self::NodeId,
direction: Direction,
) -> impl Iterator<Item = NodeMut<'a, Self>> + 'b {
- let Self {
- closures, nodes, ..
- } = self;
-
- let closure = closures.nodes();
-
- let available = match direction {
- Direction::Incoming => Either::Left(closure.incoming_neighbours(*id)),
- Direction::Outgoing => Either::Right(closure.outgoing_neighbours(*id)),
+ let Some(node) = self.nodes.get(*id) else {
+ return Either::Right(core::iter::empty());
};
- nodes
- .filter_mut(available)
- .map(|node| NodeMut::new(&node.id, &mut node.weight))
+ // SAFETY: we never access the closure argument mutably, only the weight.
+ // Therefore it is safe for us to access both at the same time.
+ let closure: &NodeClosures = unsafe { &*(&node.closures as *const _) };
+ let neighbours = match direction {
+ Direction::Incoming => Either::Left(closure.incoming_neighbours()),
+ Direction::Outgoing => Either::Right(closure.outgoing_neighbours()),
+ };
+
+ Either::Left(
+ self.nodes
+ .filter_mut(neighbours)
+ .map(move |node| NodeMut::new(&node.id, &mut node.weight)),
+ )
}
}
diff --git a/crates/dino/src/edge.rs b/crates/dino/src/edge.rs
index 022e305..595d101 100644
--- a/crates/dino/src/edge.rs
+++ b/crates/dino/src/edge.rs
@@ -71,6 +71,8 @@
impl ManagedGraphId for EdgeId {}
+pub(crate) type EdgeSlab<T> = crate::slab::Slab<EdgeId, Edge<T>>;
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Edge<T> {
pub(crate) id: EdgeId,
diff --git a/crates/dino/src/iter/closure.rs b/crates/dino/src/iter/closure.rs
new file mode 100644
index 0000000..d50c642
--- /dev/null
+++ b/crates/dino/src/iter/closure.rs
@@ -0,0 +1,4 @@
+use crate::{EdgeId, NodeId};
+
+pub type NodeIdClosureIter<'a> = core::iter::Copied<core::slice::Iter<'a, NodeId>>;
+pub type EdgeIdClosureIter<'a> = core::iter::Copied<core::slice::Iter<'a, EdgeId>>;
diff --git a/crates/dino/src/iter/directed.rs b/crates/dino/src/iter/directed.rs
new file mode 100644
index 0000000..4acccf7
--- /dev/null
+++ b/crates/dino/src/iter/directed.rs
@@ -0,0 +1,43 @@
+use petgraph_core::{edge::Direction, Edge, GraphDirectionality, GraphStorage};
+
+use crate::{iter::closure::NodeIdClosureIter, node::Node, slab::Key, DinoStorage, EdgeId, NodeId};
+
+pub struct NodeDirectedConnectionsIter<'storage, N, E, D, I>
+where
+ D: GraphDirectionality,
+{
+ pub(crate) storage: &'storage DinoStorage<N, E, D>,
+ pub(crate) iter: Option<I>,
+}
+
+impl<'storage, N, E, D, I> Iterator for NodeDirectedConnectionsIter<'storage, N, E, D, I>
+where
+ D: GraphDirectionality,
+ I: Iterator<Item = EdgeId>,
+{
+ type Item = Edge<'storage, DinoStorage<N, E, D>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let iter = self.iter.as_mut()?;
+
+ loop {
+ let id = iter.next()?;
+
+ let Some(edge) = self.storage.edges.get_unchecked(id) else {
+ continue;
+ };
+
+ return Some(Edge::new(
+ self.storage,
+ &edge.id,
+ &edge.weight,
+ &edge.source,
+ &edge.target,
+ ));
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.as_ref().map_or((0, Some(0)), Iterator::size_hint)
+ }
+}
diff --git a/crates/dino/src/iter/mod.rs b/crates/dino/src/iter/mod.rs
new file mode 100644
index 0000000..b329399
--- /dev/null
+++ b/crates/dino/src/iter/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod closure;
+pub(crate) mod directed;
diff --git a/crates/dino/src/lib.rs b/crates/dino/src/lib.rs
index a889383..0d12dfa 100644
--- a/crates/dino/src/lib.rs
+++ b/crates/dino/src/lib.rs
@@ -109,6 +109,7 @@
mod edge;
mod node;
+pub(crate) mod iter;
mod retain;
pub(crate) mod slab;
#[cfg(test)]
@@ -119,6 +120,7 @@
use core::fmt::{Debug, Display};
pub use edge::EdgeId;
+use either::Either;
use error_stack::{Context, Report, Result};
pub use node::NodeId;
use petgraph_core::{
@@ -132,7 +134,12 @@
Graph,
};
-use crate::{closure::Closures, edge::Edge, node::Node, slab::Slab};
+use crate::{
+ closure::Closures,
+ edge::Edge,
+ node::{Node, NodeClosures},
+ slab::Slab,
+};
/// Alias for a [`Graph`] that uses [`DinoStorage`] as its backing storage.
///
@@ -433,7 +440,7 @@
nodes: impl IntoIterator<Item = DetachedNode<Self::NodeId, Self::NodeWeight>>,
edges: impl IntoIterator<Item = DetachedEdge<Self::EdgeId, Self::NodeId, Self::EdgeWeight>>,
) -> Result<Self, Self::Error> {
- let nodes: Slab<_, _> = nodes
+ let mut nodes: Slab<_, _> = nodes
.into_iter()
.map(|node: DetachedNode<Self::NodeId, Self::NodeWeight>| {
(node.id, Node::new(node.id, node.weight))
@@ -456,7 +463,7 @@
// We don't know their ID yet (need a way to get those -> PartialNode/Edge)
let mut closures = Closures::new();
- closures.refresh(&nodes, &edges);
+ closures.refresh(&mut nodes, &edges);
Ok(Self {
nodes,
@@ -520,11 +527,9 @@
.nodes
.get_mut(id)
.ok_or_else(|| Report::new(Error::NodeNotFound))?;
+
// we do not need to set the node's id, since the assertion above guarantees that the id is
// correct
-
- self.closures.create_node(node);
-
Ok(NodeMut::new(&node.id, &mut node.weight))
}
@@ -572,7 +577,7 @@
// we do not need to set the node's id, since the assertion above guarantees that the id is
// correct
- self.closures.create_edge(edge);
+ self.closures.create_edge(edge, &mut self.nodes);
Ok(EdgeMut::new(
&edge.id,
@@ -586,16 +591,17 @@
&mut self,
id: &Self::NodeId,
) -> Option<DetachedNode<Self::NodeId, Self::NodeWeight>> {
- for edge in self.closures.drain_edges(*id) {
+ let node = self.nodes.remove(*id)?;
+
+ for edge in node.edges() {
if let Some(edge) = self.edges.remove(edge) {
- self.closures.remove_edge(&edge);
+ self.closures.remove_edge(&edge, &mut self.nodes);
}
}
- let node = self.nodes.remove(*id)?;
- self.closures.remove_node(&node);
+ let (id, weight) = self.closures.remove_node(node, &mut self.nodes);
- Some(DetachedNode::new(node.id, node.weight))
+ Some(DetachedNode::new(id, weight))
}
fn remove_edge(
@@ -603,7 +609,7 @@
id: &Self::EdgeId,
) -> Option<DetachedEdge<Self::EdgeId, Self::NodeId, Self::EdgeWeight>> {
let edge = self.edges.remove(*id)?;
- self.closures.remove_edge(&edge);
+ self.closures.remove_edge(&edge, &mut self.nodes);
Some(DetachedEdge::new(
edge.id,
@@ -616,7 +622,7 @@
fn clear(&mut self) {
self.nodes.clear();
self.edges.clear();
- self.closures.clear();
+ self.closures.clear(&mut self.nodes);
}
fn node(&self, id: &Self::NodeId) -> Option<petgraph_core::node::Node<Self>> {
@@ -683,22 +689,26 @@
&'a self,
id: &'b Self::NodeId,
) -> impl Iterator<Item = petgraph_core::edge::Edge<'a, Self>> + 'b {
- self.closures
- .nodes()
- .edges(*id)
- .filter_map(|edge| self.edge(&edge))
+ self.nodes
+ .get(*id)
+ .into_iter()
+ .flat_map(move |node| node.edges())
+ .filter_map(move |edge| self.edge(&edge))
}
fn node_connections_mut<'a: 'b, 'b>(
&'a mut self,
id: &'b Self::NodeId,
) -> impl Iterator<Item = EdgeMut<'a, Self>> + 'b {
- let Self {
- closures, edges, ..
- } = self;
+ let Self { nodes, edges, .. } = self;
+
+ let allow = nodes
+ .get(*id)
+ .into_iter()
+ .flat_map(move |node| node.edges());
edges
- .filter_mut(closures.nodes().edges(*id))
+ .filter_mut(allow)
.map(move |edge| EdgeMut::new(&edge.id, &mut edge.weight, &edge.source, &edge.target))
}
@@ -706,9 +716,10 @@
&'a self,
id: &'b Self::NodeId,
) -> impl Iterator<Item = petgraph_core::node::Node<'a, Self>> + 'b {
- self.closures
- .nodes()
- .neighbours(*id)
+ self.nodes
+ .get(*id)
+ .into_iter()
+ .flat_map(move |node| node.neighbours())
.filter_map(move |node| self.node(&node))
}
@@ -716,29 +727,33 @@
&'a mut self,
id: &'b Self::NodeId,
) -> impl Iterator<Item = NodeMut<'a, Self>> + 'b {
- let Self {
- closures, nodes, ..
- } = self;
+ let Some(node) = self.nodes.get(*id) else {
+ return Either::Right(core::iter::empty());
+ };
- nodes
- .filter_mut(closures.nodes().neighbours(*id))
- .map(move |node| NodeMut::new(&node.id, &mut node.weight))
+ // SAFETY: we never access the closure argument mutably, only the weight.
+ // Therefore it is safe for us to access both at the same time.
+ let closure: &NodeClosures = unsafe { &*(&node.closures as *const _) };
+ let neighbours = closure.neighbours();
+
+ Either::Left(
+ self.nodes
+ .filter_mut(neighbours)
+ .map(move |node| NodeMut::new(&node.id, &mut node.weight)),
+ )
}
fn isolated_nodes(&self) -> impl Iterator<Item = petgraph_core::node::Node<Self>> {
- self.closures
- .nodes()
- .externals()
- .filter_map(move |node| self.node(&node))
+ self.nodes
+ .iter()
+ .filter(|node| node.is_isolated())
+ .map(move |node| petgraph_core::node::Node::new(self, &node.id, &node.weight))
}
fn isolated_nodes_mut(&mut self) -> impl Iterator<Item = NodeMut<Self>> {
- let Self {
- nodes, closures, ..
- } = self;
-
- nodes
- .filter_mut(closures.nodes().externals())
+ self.nodes
+ .iter_mut()
+ .filter(move |node| node.is_isolated())
.map(move |node| NodeMut::new(&node.id, &mut node.weight))
}
diff --git a/crates/dino/src/node.rs b/crates/dino/src/node.rs
index c4c2e54..f3a9187 100644
--- a/crates/dino/src/node.rs
+++ b/crates/dino/src/node.rs
@@ -1,14 +1,25 @@
-use core::fmt::{Display, Formatter};
+use alloc::vec::Vec;
+use core::{
+ fmt::{Display, Formatter},
+ iter::Enumerate,
+};
+use bitvec::{boxed::BitBox, vec::BitVec};
use petgraph_core::{
attributes::NoValue,
+ base::MaybeOwned,
edge::marker::GraphDirectionality,
- id::{GraphId, LinearGraphId, ManagedGraphId},
+ id::{
+ AttributeGraphId, AttributeStorage, FlagStorage, FlaggableGraphId, GraphId, IndexMapper,
+ LinearGraphId, ManagedGraphId,
+ },
+ GraphStorage,
};
use crate::{
- slab::{EntryId, Key, SlabIndexMapper},
- DinoStorage,
+ iter::closure::{EdgeIdClosureIter, NodeIdClosureIter},
+ slab::{EntryId, Generation, Key, SlabIndexMapper},
+ DinoStorage, EdgeId,
};
/// Identifier for a node in [`DinoStorage`].
@@ -45,6 +56,7 @@
Self(id)
}
+ #[inline]
fn into_id(self) -> EntryId {
self.0
}
@@ -65,16 +77,278 @@
}
}
+// TODO: potential matter to reduce memory usage
+pub struct FlagStore {
+ vector: BitBox,
+}
+
+impl FlagStorage<NodeId> for FlagStore {
+ #[inline]
+ fn get(&self, id: &NodeId) -> Option<bool> {
+ self.vector.get(id.into_id().index()).map(|flag| *flag)
+ }
+
+ #[inline]
+
+ fn index(&self, id: &NodeId) -> bool {
+ self.vector[id.into_id().index()]
+ }
+
+ fn set(&mut self, id: &NodeId, flag: bool) -> Option<bool> {
+ let old = self.vector.replace(id.into_id().index(), flag);
+
+ Some(old)
+ }
+}
+
+impl<N, E, D> FlaggableGraphId<DinoStorage<N, E, D>> for NodeId
+where
+ D: GraphDirectionality,
+{
+ type Store<'a> = FlagStore where
+ DinoStorage<N, E, D>: 'a;
+
+ fn flag_store(storage: &DinoStorage<N, E, D>) -> Self::Store<'_> {
+ let vector = BitVec::repeat(false, storage.num_nodes()).into_boxed_bitslice();
+
+ Self::Store { vector }
+ }
+}
+
+pub struct AttributeStoreIter<'a, T> {
+ iter: Enumerate<core::slice::Iter<'a, Option<(Generation, T)>>>,
+}
+
+impl<'a, T> Iterator for AttributeStoreIter<'a, T> {
+ type Item = (MaybeOwned<'a, NodeId>, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ let (index, item) = self.iter.next()?;
+
+ if let Some((generation, item)) = item {
+ let id = EntryId::new(*generation, index)?;
+ let id = NodeId::from_id(id);
+
+ return Some((MaybeOwned::Owned(id), item));
+ }
+ }
+ }
+}
+
+pub struct AttributeStore<T> {
+ items: Vec<Option<(Generation, T)>>,
+}
+
+impl<T> AttributeStorage<NodeId, T> for AttributeStore<T> {
+ type Iter<'a> = AttributeStoreIter<'a, T> where NodeId: 'a, T: 'a, Self: 'a;
+
+ fn get(&self, id: &NodeId) -> Option<&T> {
+ if id.into_id().index() >= self.items.len() {
+ return None;
+ }
+
+ self.items[id.into_id().index()]
+ .as_ref()
+ .map(|(_, item)| item)
+ }
+
+ fn get_mut(&mut self, id: &NodeId) -> Option<&mut T> {
+ if id.into_id().index() >= self.items.len() {
+ return None;
+ }
+
+ self.items[id.into_id().index()]
+ .as_mut()
+ .map(|(_, item)| item)
+ }
+
+ fn index(&self, id: &NodeId) -> &T {
+ self.items[id.into_id().index()]
+ .as_ref()
+ .map(|(_, item)| item)
+ .expect("index called on empty node")
+ }
+
+ fn index_mut(&mut self, id: &NodeId) -> &mut T {
+ self.items[id.into_id().index()]
+ .as_mut()
+ .map(|(_, item)| item)
+ .expect("index_mut called on empty node")
+ }
+
+ fn set(&mut self, id: &NodeId, value: T) -> Option<T> {
+ let index = id.into_id().index();
+ let generation = id.into_id().generation();
+
+ if index >= self.items.len() {
+ return None;
+ }
+
+ core::mem::replace(&mut self.items[index], Some((generation, value))).map(|(_, item)| item)
+ }
+
+ fn remove(&mut self, id: &NodeId) -> Option<T> {
+ let index = id.into_id().index();
+
+ if index >= self.items.len() {
+ return None;
+ }
+
+ self.items[index].take().map(|(_, item)| item)
+ }
+
+ fn iter(&self) -> Self::Iter<'_> {
+ Self::Iter {
+ iter: self.items.iter().enumerate(),
+ }
+ }
+}
+
+impl<N, E, D> AttributeGraphId<DinoStorage<N, E, D>> for NodeId
+where
+ D: GraphDirectionality,
+{
+ type Store<'a, T> = AttributeStore<T> where DinoStorage<N, E, D>: 'a;
+
+ fn attribute_store<T>(storage: &DinoStorage<N, E, D>) -> Self::Store<'_, T> {
+ let mut items = Vec::with_capacity(storage.nodes.total_len());
+ items.resize_with(storage.nodes.total_len(), || None);
+
+ Self::Store { items }
+ }
+}
+
impl ManagedGraphId for NodeId {}
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) type NodeSlab<T> = crate::slab::Slab<NodeId, Node<T>>;
+
+#[derive(Debug, Clone)]
+pub(crate) struct NodeClosures {
+ pub(crate) outgoing_nodes: Vec<NodeId>,
+ pub(crate) incoming_nodes: Vec<NodeId>,
+
+ pub(crate) outgoing_edges: Vec<EdgeId>,
+ pub(crate) incoming_edges: Vec<EdgeId>,
+}
+
+impl NodeClosures {
+ fn new() -> Self {
+ Self {
+ outgoing_nodes: Vec::new(),
+ incoming_nodes: Vec::new(),
+
+ outgoing_edges: Vec::new(),
+ incoming_edges: Vec::new(),
+ }
+ }
+
+ pub(crate) fn outgoing_neighbours(&self) -> NodeIdClosureIter {
+ self.outgoing_nodes.iter().copied()
+ }
+
+ pub(crate) fn incoming_neighbours(&self) -> NodeIdClosureIter {
+ self.incoming_nodes.iter().copied()
+ }
+
+ pub(crate) fn neighbours(&self) -> impl Iterator<Item = NodeId> + '_ {
+ todo!();
+ core::iter::empty()
+ }
+
+ pub(crate) fn outgoing_edges(&self) -> EdgeIdClosureIter {
+ self.outgoing_edges.iter().copied()
+ }
+
+ pub(crate) fn incoming_edges(&self) -> EdgeIdClosureIter {
+ self.incoming_edges.iter().copied()
+ }
+
+ pub(crate) fn edges(&self) -> impl Iterator<Item = EdgeId> + '_ {
+ todo!();
+ core::iter::empty()
+ }
+
+ pub(crate) fn clear(&mut self) {
+ self.outgoing_nodes.clear();
+ self.incoming_nodes.clear();
+
+ self.outgoing_edges.clear();
+ self.incoming_edges.clear();
+ }
+}
+
+#[derive(Debug, Clone)]
pub(crate) struct Node<T> {
pub(crate) id: NodeId,
pub(crate) weight: T,
+
+ pub(crate) closures: NodeClosures,
}
impl<T> Node<T> {
- pub(crate) const fn new(id: NodeId, weight: T) -> Self {
- Self { id, weight }
+ pub(crate) fn new(id: NodeId, weight: T) -> Self {
+ Self {
+ id,
+ weight,
+ closures: NodeClosures::new(),
+ }
+ }
+
+ pub(crate) fn outgoing_neighbours(&self) -> impl Iterator<Item = NodeId> + '_ {
+ self.closures.outgoing_neighbours()
+ }
+
+ pub(crate) fn incoming_neighbours(&self) -> impl Iterator<Item = NodeId> + '_ {
+ self.closures.incoming_neighbours()
+ }
+
+ pub(crate) fn neighbours(&self) -> impl Iterator<Item = NodeId> + '_ {
+ self.closures.neighbours()
+ }
+
+ pub(crate) fn outgoing_edges(&self) -> impl Iterator<Item = EdgeId> + '_ {
+ self.closures.outgoing_edges()
+ }
+
+ pub(crate) fn incoming_edges(&self) -> impl Iterator<Item = EdgeId> + '_ {
+ self.closures.incoming_edges()
+ }
+
+ pub(crate) fn edges(&self) -> impl Iterator<Item = EdgeId> + '_ {
+ self.closures.edges()
+ }
+
+ pub(crate) fn is_isolated(&self) -> bool {
+ self.closures.outgoing_nodes.is_empty() && self.closures.incoming_nodes.is_empty()
+ }
+}
+
+impl<T> PartialEq for Node<T>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ (&self.id, &self.weight) == (&other.id, &other.weight)
+ }
+}
+
+impl<T> Eq for Node<T> where T: Eq {}
+
+impl<T> PartialOrd for Node<T>
+where
+ T: PartialOrd,
+{
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ (&self.id, &self.weight).partial_cmp(&(&other.id, &other.weight))
+ }
+}
+
+impl<T> Ord for Node<T>
+where
+ T: Ord,
+{
+ fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+ (&self.id, &self.weight).cmp(&(&other.id, &other.weight))
}
}
diff --git a/crates/dino/src/retain.rs b/crates/dino/src/retain.rs
index 6c9bc77..48661e9 100644
--- a/crates/dino/src/retain.rs
+++ b/crates/dino/src/retain.rs
@@ -42,7 +42,7 @@
edges(edge)
});
- self.closures.refresh(&self.nodes, &self.edges);
+ self.closures.refresh(&mut self.nodes, &self.edges);
}
fn retain_nodes(&mut self, mut f: impl FnMut(NodeMut<'_, Self>) -> bool) {
@@ -65,7 +65,7 @@
&& !removed.contains(value.target.into_id().raw())
});
- self.closures.refresh(&self.nodes, &self.edges);
+ self.closures.refresh(&mut self.nodes, &self.edges);
}
fn retain_edges(&mut self, mut f: impl FnMut(EdgeMut<'_, Self>) -> bool) {
@@ -75,6 +75,6 @@
f(edge)
});
- self.closures.refresh(&self.nodes, &self.edges);
+ self.closures.refresh(&mut self.nodes, &self.edges);
}
}
diff --git a/crates/dino/src/slab/mod.rs b/crates/dino/src/slab/mod.rs
index 8a8a811..6321d35 100644
--- a/crates/dino/src/slab/mod.rs
+++ b/crates/dino/src/slab/mod.rs
@@ -145,6 +145,16 @@
entry.get()
}
+ pub(crate) fn get_unchecked(&self, key: K) -> Option<&V> {
+ let id = key.into_id();
+
+ let index = id.index();
+
+ let entry = self.entries.get(index)?;
+
+ entry.get()
+ }
+
pub(crate) fn get_mut(&mut self, key: K) -> Option<&mut V> {
let id = key.into_id();
@@ -352,6 +362,10 @@
pub(crate) fn len(&self) -> usize {
self.entries.len() - self.free.len()
}
+
+ pub(crate) fn total_len(&self) -> usize {
+ self.entries.len()
+ }
}
impl<K, V> FromIterator<(K, V)> for Slab<K, V>