Skip to content

Commit 8ef2805

Browse files
committed
WIP
1 parent cbdee08 commit 8ef2805

File tree

3 files changed

+148
-21
lines changed

3 files changed

+148
-21
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ tracing = "0.1.41"
4646
tracing-subscriber = "0.3.19"
4747
petgraph = { version = "0.7.1", features = ["graphmap", "stable_graph", "matrix_graph", "serde-1", "rayon"] }
4848
rand = "0.9.0"
49+
sprs = "0.11.3"
4950

5051
[dev-dependencies]
5152
criterion = { version = "0.5", features = ["html_reports"] }

src/graph/types.rs

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use petgraph::graph::{EdgeIndex, Graph as PetGraph, NodeIndex};
33
use petgraph::prelude::EdgeRef;
44
use petgraph::visit::IntoNodeReferences;
55
use petgraph::{Directed, Undirected};
6+
use sprs::{CsMat, TriMat};
67

78
/// Trait for constructing graphs with specific edge types.
89
pub trait GraphConstructor<A, W>: petgraph::EdgeType + Sized {
@@ -67,7 +68,7 @@ impl EdgeId {
6768
}
6869
}
6970

70-
/// Base graph structure that wraps around `PetGraph`.
71+
/// Base graph structure that wraps around a petgraph instance.
7172
#[derive(Debug, Clone)]
7273
pub struct BaseGraph<A, W, Ty: GraphConstructor<A, W>> {
7374
inner: PetGraph<A, W, Ty>,
@@ -117,6 +118,7 @@ impl<A, W, Ty: GraphConstructor<A, W>> BaseGraph<A, W, Ty> {
117118
self.inner.neighbors(index).map(NodeId::new)
118119
}
119120

121+
/// Returns a reference to the attribute of a node.
120122
pub fn node_attr(&self, node: NodeId) -> Option<&A> {
121123
let index = NodeIndex::new(node.index());
122124
self.inner.node_weight(index)
@@ -146,17 +148,124 @@ impl<A, W, Ty: GraphConstructor<A, W>> BaseGraph<A, W, Ty> {
146148
})
147149
}
148150

149-
/// Returns a reference to the inner `PetGraph`.
150-
pub fn inner(&self) -> &PetGraph<A, W, Ty> {
151+
/// Returns a reference to the inner petgraph instance.
152+
/// (Not exposed to the user as part of the public API.)
153+
fn inner(&self) -> &PetGraph<A, W, Ty> {
151154
&self.inner
152155
}
153156

154-
/// Returns a mutable reference to the inner `PetGraph`.
155-
pub fn inner_mut(&mut self) -> &mut PetGraph<A, W, Ty> {
157+
/// Returns a mutable reference to the inner petgraph instance.
158+
/// (Not exposed to the user as part of the public API.)
159+
fn inner_mut(&mut self) -> &mut PetGraph<A, W, Ty> {
156160
&mut self.inner
157161
}
158162
}
159163

164+
/// Dense matrix API using owned values.
165+
impl<A, W, Ty: GraphConstructor<A, W>> BaseGraph<A, W, Ty>
166+
where
167+
W: Clone,
168+
{
169+
/// Returns the adjacency matrix of the graph as a 2D vector.
170+
///
171+
/// Each entry at `[i][j]` is an `Option<W>` which is `Some(w)` if an edge exists
172+
/// from node `i` to node `j`, or `None` otherwise.
173+
/// For undirected graphs, the matrix is symmetric.
174+
pub fn to_adjacency_matrix(&self) -> Vec<Vec<Option<W>>> {
175+
let n = self.node_count();
176+
let mut matrix = vec![vec![None; n]; n];
177+
for edge in self.inner().edge_references() {
178+
let i = edge.source().index();
179+
let j = edge.target().index();
180+
matrix[i][j] = Some(edge.weight().clone());
181+
if !<Ty as GraphConstructor<A, W>>::is_directed() {
182+
matrix[j][i] = Some(edge.weight().clone());
183+
}
184+
}
185+
matrix
186+
}
187+
188+
/// Constructs a new graph from an adjacency matrix.
189+
///
190+
/// The input is a slice of vectors, where each inner vector represents the outgoing edges
191+
/// from a node. A value of `Some(w)` at position `[i][j]` indicates an edge from node `i` to node `j`
192+
/// with weight `w`. For undirected graphs, only the upper triangle (including the diagonal)
193+
/// of the matrix is considered.
194+
///
195+
/// Node attributes are initialized using `A::default()`, so `A` must implement `Default`.
196+
pub fn from_adjacency_matrix(matrix: &[Vec<Option<W>>]) -> Self
197+
where
198+
A: Default,
199+
{
200+
let n = matrix.len();
201+
let mut graph = Self::new();
202+
// Add n nodes with default attributes.
203+
let nodes: Vec<NodeId> = (0..n).map(|_| graph.add_node(A::default())).collect();
204+
// Insert edges based on the matrix.
205+
for i in 0..n {
206+
for j in 0..matrix[i].len() {
207+
if let Some(weight) = &matrix[i][j] {
208+
if <Ty as GraphConstructor<A, W>>::is_directed() || i <= j {
209+
graph.add_edge(nodes[i], nodes[j], weight.clone());
210+
}
211+
}
212+
}
213+
}
214+
graph
215+
}
216+
}
217+
218+
/// Sparse matrix API using sprs for efficiency on large graphs.
219+
/// The trait bound now includes Add so that duplicate entries can be combined.
220+
impl<A, W, Ty: GraphConstructor<A, W>> BaseGraph<A, W, Ty>
221+
where
222+
W: Clone + std::ops::Add<Output = W>,
223+
{
224+
/// Returns the sparse adjacency matrix of the graph as a CsMat in CSR format.
225+
///
226+
/// Only existing edges are stored. For undirected graphs, both (i,j) and (j,i) are inserted,
227+
/// except for self-loops.
228+
pub fn to_sparse_adjacency_matrix(&self) -> CsMat<W> {
229+
let n = self.node_count();
230+
let mut triplet = TriMat::new((n, n));
231+
for edge in self.inner().edge_references() {
232+
let i = edge.source().index();
233+
let j = edge.target().index();
234+
triplet.add_triplet(i, j, edge.weight().clone());
235+
if !<Ty as GraphConstructor<A, W>>::is_directed() && i != j {
236+
triplet.add_triplet(j, i, edge.weight().clone());
237+
}
238+
}
239+
// Convert the triplet matrix into CSR format.
240+
triplet.to_csr()
241+
}
242+
243+
/// Constructs a new graph from a sparse adjacency matrix.
244+
///
245+
/// The input is a CsMat (typically in CSR format) where nonzero entries represent edges with their weights.
246+
/// For undirected graphs, only one of the symmetric entries is used (edges with i <= j).
247+
///
248+
/// Node attributes are initialized using `A::default()`, so `A` must implement `Default`.
249+
pub fn from_sparse_adjacency_matrix(sparse: &CsMat<W>) -> Self
250+
where
251+
A: Default,
252+
{
253+
let n = sparse.rows();
254+
let mut graph = Self::new();
255+
let nodes: Vec<NodeId> = (0..n).map(|_| graph.add_node(A::default())).collect();
256+
// Iterate over the outer (row) indices.
257+
for (i, row) in sparse.outer_iterator().enumerate() {
258+
// row.indices() gives column indices and row.data() gives the corresponding weights.
259+
for (&j, weight) in row.indices().iter().zip(row.data().iter()) {
260+
if <Ty as GraphConstructor<A, W>>::is_directed() || i <= j {
261+
graph.add_edge(nodes[i], nodes[j], weight.clone());
262+
}
263+
}
264+
}
265+
graph
266+
}
267+
}
268+
160269
/// Type alias for a directed graph.
161270
pub type Digraph<A, W> = BaseGraph<A, W, Directed>;
162271
/// Type alias for an undirected graph.

tests/graph/types_tests.rs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
use network::graph::{Digraph, Graph, NodeId};
2-
31
#[test]
4-
fn integration_test_digraph() {
2+
fn test_digraph() {
53
// Create a directed graph with integer node attributes and f32 edge weights.
64
let mut dgraph = Digraph::<i32, f32>::new();
75
let n1 = dgraph.add_node(1);
86
let n2 = dgraph.add_node(2);
97
let n3 = dgraph.add_node(3);
108

11-
let e1 = dgraph.add_edge(n1, n2, 1.0);
12-
let e2 = dgraph.add_edge(n2, n3, 2.0);
13-
let e3 = dgraph.add_edge(n3, n1, 3.0);
9+
let _e1 = dgraph.add_edge(n1, n2, 1.0);
10+
let _e2 = dgraph.add_edge(n2, n3, 2.0);
11+
let _e3 = dgraph.add_edge(n3, n1, 3.0);
1412

1513
// Verify counts.
1614
assert_eq!(dgraph.node_count(), 3);
@@ -27,29 +25,40 @@ fn integration_test_digraph() {
2725
assert_eq!(*dgraph.node_attr(n3).unwrap(), 3);
2826

2927
// Verify edge attributes.
30-
assert_eq!(*dgraph.edge_attr(e1).unwrap(), 1.0);
31-
assert_eq!(*dgraph.edge_attr(e2).unwrap(), 2.0);
32-
assert_eq!(*dgraph.edge_attr(e3).unwrap(), 3.0);
28+
// (Using the dense API here.)
29+
let matrix = dgraph.to_adjacency_matrix();
30+
assert_eq!(matrix[0][1], Some(1.0));
31+
assert_eq!(matrix[1][2], Some(2.0));
32+
assert_eq!(matrix[2][0], Some(3.0));
33+
34+
// Test sparse conversion.
35+
let sparse = dgraph.to_sparse_adjacency_matrix();
36+
assert_eq!(sparse.rows(), 3);
37+
// Check a few nonzero values.
38+
assert_eq!(sparse.get(0, 1), Some(&1.0));
39+
assert_eq!(sparse.get(1, 2), Some(&2.0));
40+
assert_eq!(sparse.get(2, 0), Some(&3.0));
3341

3442
// Update node attribute and verify.
3543
dgraph.update_node(n1, 10);
3644
assert_eq!(*dgraph.node_attr(n1).unwrap(), 10);
3745
}
3846

3947
#[test]
40-
fn integration_test_graph() {
48+
fn test_graph() {
4149
// Create an undirected graph with string node attributes and unweighted edges.
4250
let mut graph = Graph::<&str, ()>::new();
4351
let a = graph.add_node("A");
4452
let b = graph.add_node("B");
4553
let c = graph.add_node("C");
4654

47-
let e1 = graph.add_edge(a, b, ());
48-
let e2 = graph.add_edge(b, c, ());
49-
let e3 = graph.add_edge(c, a, ());
55+
let _e1 = graph.add_edge(a, b, ());
56+
let _e2 = graph.add_edge(b, c, ());
57+
let _e3 = graph.add_edge(c, a, ());
5058

5159
// Verify counts.
5260
assert_eq!(graph.node_count(), 3);
61+
// For undirected graphs, our API adds one edge per pair.
5362
assert_eq!(graph.edge_count(), 3);
5463

5564
// In an undirected graph, neighbors should include all connected nodes.
@@ -63,9 +72,17 @@ fn integration_test_graph() {
6372
assert_eq!(graph.node_attr(c).unwrap(), &"C");
6473

6574
// For unweighted edges, edge_attr returns the unit type `()`.
66-
assert_eq!(graph.edge_attr(e1).unwrap(), &());
67-
assert_eq!(graph.edge_attr(e2).unwrap(), &());
68-
assert_eq!(graph.edge_attr(e3).unwrap(), &());
75+
let matrix = graph.to_adjacency_matrix();
76+
// Each edge appears twice in the dense matrix for an undirected graph.
77+
assert_eq!(matrix[a.index()][b.index()], Some(()));
78+
assert_eq!(matrix[b.index()][c.index()], Some(()));
79+
assert_eq!(matrix[c.index()][a.index()], Some(()));
80+
81+
// Test sparse conversion.
82+
let sparse = graph.to_sparse_adjacency_matrix();
83+
assert_eq!(sparse.rows(), 3);
84+
// Check one of the symmetric entries.
85+
assert_eq!(sparse.get(a.index(), b.index()), Some(&()));
6986

7087
// Update node attribute and verify.
7188
graph.update_node(a, "Alpha");

0 commit comments

Comments
 (0)