Skip to content

Commit bef2730

Browse files
committed
WIP
1 parent fb51cb0 commit bef2730

23 files changed

+1548
-242
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name: Tests
33
on:
44
workflow_dispatch: { } # Allow manual execution
55
workflow_call: # Make this workflow callable from other workflows
6+
push:
7+
tags:
8+
- 'v*' # Trigger on version tags
69

710
jobs:
811
build:

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ tracing-subscriber = "0.3.19"
4444
petgraph = { version = "0.7.1", features = ["graphmap", "stable_graph", "matrix_graph", "serde-1", "rayon"] }
4545
rand = "0.9.0"
4646
sprs = "0.11.3"
47+
ordered-float = "5.0.0"
48+
rayon = "1.10.0"
4749

4850
[dev-dependencies]
4951
criterion = { version = "0.5", features = ["html_reports"] }

README.md

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Graphina
22

33
[![Tests](https://img.shields.io/github/actions/workflow/status/habedi/graphina/tests.yml?label=tests&style=popout-square&logo=github)](https://github.com/habedi/graphina/actions/workflows/tests.yml)
4+
[![Lint](https://img.shields.io/github/actions/workflow/status/habedi/graphina/lint.yml?label=lint&style=popout-square&logo=github)](https://github.com/habedi/graphina/actions/workflows/lint.yml)
45
[![Code Coverage](https://img.shields.io/codecov/c/github/habedi/graphina?style=popout-square&logo=codecov)](https://codecov.io/gh/habedi/graphina)
56
[![CodeFactor](https://img.shields.io/codefactor/grade/github/habedi/graphina?style=popout-square&logo=codefactor)](https://www.codefactor.io/repository/github/habedi/graphina)
67
[![Crates.io](https://img.shields.io/crates/v/graphina.svg?style=popout-square&color=fc8d62&logo=rust)](https://crates.io/crates/graphina)
@@ -23,35 +24,16 @@ set of ready-to-use algorithms for graph analysis and mining.
2324
2425
## Features
2526

26-
* **Graphs**:
27-
- [x] Directed and undirected graphs
28-
- [x] Weighted and unweighted graphs
29-
30-
* **IO**:
31-
- [x] Edge list
32-
- [x] Adjacency list
33-
- [ ] GraphML
34-
- [ ] GML
35-
- [ ] JSON
36-
37-
* **Generators**:
38-
- [x] Erdős–Rényi Graph
39-
- [x] Complete Graph
40-
- [x] Bipartite Graph
41-
- [x] Star Graph
42-
- [x] Cycle Graph
43-
- [x] Watts–Strogatz Graph
44-
- [x] Barabási–Albert Graph
45-
46-
* **Algorithms**:
47-
- [ ] Graph traversal
48-
- [ ] Shortest paths
49-
- [ ] Minimum spanning tree
50-
- [ ] Connected components
51-
- [ ] Clustering, partitioning, and community detection
52-
- [ ] Centrality
53-
- [ ] Graph matching
54-
- [ ] Graph visualization
27+
| Module | Feature | Status | Notes |
28+
|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|
29+
| [**Types**](src/graph/types.rs) | - ✓ Directed and undirected graphs<br>- ✓ Weighted and unweighted graphs | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | Core graph types |
30+
| [**IO**](src/graph/io.rs) | - ✓ Edge list<br>- ✓ Adjacency list<br>- ☐ GraphML<br>- ☐ GML<br>- ☐ JSON | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | I/O routines for reading/writing graph data |
31+
| [**Generators**](src/graph/generators.rs) | - ✓ Erdős–Rényi graph<br>- ✓ Watts–Strogatz graph<br>- ✓ Barabási–Albert graph<br>- ✓ Complete graph<br>- ✓ Bipartite graph<br>- ✓ Star graph<br>- ✓ Cycle graph | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | Generators for random and structured graph models |
32+
| [**Graph Traversal**](src/traversal/) | - ✓ Breadth-first search<br>- ✓ Depth-first search<br>- ✓ Iterative deepening DFS<br>- ✓ Bidirectional search | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | Graph traversal algorithms |
33+
| [**Shortest Paths**](src/paths/) | - ✓ Dijkstra's algorithm<br>- ✓ Bellman–Ford algorithm<br>- ✓ Floyd–Warshall algorithm<br>- ✓ Johnson's algorithm<br>- ✓ A* search algorithm<br>- ✓ Iterative deepening A* search algorithm | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | Shortest path algorithms |
34+
| [**Centrality**](src/centrality/algorithms.rs) | - ✓ Degree centrality<br>- ✓ Closeness centrality<br>- ✓ Betweenness centrality<br>- ✓ Eigenvector centrality<br>- ✓ PageRank<br>- ✓ Katz centrality<br>- ✓ Harmonic centrality | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | Centrality measures for graph analysis |
35+
| [**Minimum Spanning Tree**](src/mst/algorithms.rs) | - ✓ Borůvka’s algorithm<br>- ✓ Kruskal’s algorithm<br>- ✓ Prim’s algorithm | <small>- ☐ Tested<br>- ☐ Benchmarked</small> | MST algorithms for undirected graphs |
36+
| **Remaining Modules** | - ☐ Connected components<br>- ☐ Clustering, partitioning, and community detection<br>- ☐ Graph matching<br>- ☐ Graph visualization | Planned | Future work: additional graph algorithms |
5537

5638
## Installation
5739

src/centrality/algorithms.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// File: src/centrality/algorithms.rs
2+
3+
use crate::graph::types::{BaseGraph, NodeId};
4+
use crate::paths::algorithms::dijkstra; // Our dijkstra implementation
5+
use ordered_float::OrderedFloat;
6+
use std::collections::VecDeque;
7+
8+
/// ### Degree Centrality
9+
/// For each node, degree centrality is defined as the sum of its out-degree and in-degree.
10+
pub fn degree_centrality<A, W, Ty>(graph: &BaseGraph<A, W, Ty>) -> Vec<f64>
11+
where
12+
W: Copy,
13+
Ty: crate::graph::types::GraphConstructor<A, W>,
14+
{
15+
let n = graph.node_count();
16+
let mut degree = vec![0usize; n];
17+
// Out-degree: count neighbors for each node.
18+
for (node, _) in graph.nodes() {
19+
let outdeg = graph.neighbors(node).count();
20+
degree[node.index()] += outdeg;
21+
}
22+
// In-degree: count each appearance as target in edges.
23+
for (_u, v, _w) in graph.edges() {
24+
degree[v.index()] += 1;
25+
}
26+
degree.into_iter().map(|d| d as f64).collect()
27+
}
28+
29+
/// ### Closeness Centrality
30+
/// For each node, closeness centrality is defined as (n-1) divided by the sum of shortest-path
31+
/// distances from that node to all other nodes. (If a node cannot reach all others, its centrality is 0.)
32+
/// This implementation uses our dijkstra algorithm, so it requires the graph’s weight type to be
33+
/// `OrderedFloat<f64>`.
34+
pub fn closeness_centrality<A, Ty>(graph: &BaseGraph<A, OrderedFloat<f64>, Ty>) -> Vec<f64>
35+
where
36+
Ty: crate::graph::types::GraphConstructor<A, OrderedFloat<f64>>,
37+
{
38+
let n = graph.node_count();
39+
let mut closeness = vec![0.0; n];
40+
for (node, _) in graph.nodes() {
41+
let distances = dijkstra(graph, node);
42+
let sum: f64 = distances.iter().filter_map(|&d| d.map(|od| od.0)).sum();
43+
if sum > 0.0 {
44+
closeness[node.index()] = (n as f64 - 1.0) / sum;
45+
} else {
46+
closeness[node.index()] = 0.0;
47+
}
48+
}
49+
closeness
50+
}
51+
52+
/// ### Betweenness Centrality (Unweighted)
53+
/// Using Brandes’ algorithm (based on BFS).
54+
/// Note: This is an unweighted implementation.
55+
pub fn betweenness_centrality<A, Ty>(graph: &BaseGraph<A, f64, Ty>) -> Vec<f64>
56+
where
57+
Ty: crate::graph::types::GraphConstructor<A, f64>,
58+
{
59+
let n = graph.node_count();
60+
let mut bc = vec![0.0; n];
61+
62+
// For each source node s:
63+
for (s, _) in graph.nodes() {
64+
let mut stack = Vec::new();
65+
let mut pred: Vec<Vec<NodeId>> = vec![Vec::new(); n];
66+
let mut sigma = vec![0.0; n]; // Number of shortest paths from s.
67+
let mut dist = vec![-1.0; n]; // Distance from s (-1 means infinity).
68+
sigma[s.index()] = 1.0;
69+
dist[s.index()] = 0.0;
70+
let mut queue = VecDeque::new();
71+
queue.push_back(s);
72+
73+
while let Some(v) = queue.pop_front() {
74+
stack.push(v);
75+
for w in graph.neighbors(v) {
76+
if dist[w.index()] < 0.0 {
77+
dist[w.index()] = dist[v.index()] + 1.0;
78+
queue.push_back(w);
79+
}
80+
if dist[w.index()] == dist[v.index()] + 1.0 {
81+
sigma[w.index()] += sigma[v.index()];
82+
pred[w.index()].push(v);
83+
}
84+
}
85+
}
86+
87+
let mut delta = vec![0.0; n];
88+
while let Some(w) = stack.pop() {
89+
for &v in &pred[w.index()] {
90+
delta[v.index()] +=
91+
(sigma[v.index()] / sigma[w.index()]) * (1.0 + delta[w.index()]);
92+
}
93+
if w != s {
94+
bc[w.index()] += delta[w.index()];
95+
}
96+
}
97+
}
98+
bc
99+
}
100+
101+
/// ### Eigenvector Centrality
102+
/// Computes centrality via power iteration over the adjacency matrix.
103+
/// This implementation uses plain f64.
104+
pub fn eigenvector_centrality<A, Ty>(graph: &BaseGraph<A, f64, Ty>, iterations: usize) -> Vec<f64>
105+
where
106+
Ty: crate::graph::types::GraphConstructor<A, f64>,
107+
{
108+
let n = graph.node_count();
109+
let mut centrality = vec![1.0; n];
110+
for _ in 0..iterations {
111+
let mut next = vec![0.0; n];
112+
for (node, _) in graph.nodes() {
113+
for neighbor in graph.neighbors(node) {
114+
next[neighbor.index()] += centrality[node.index()];
115+
}
116+
}
117+
let norm = next.iter().map(|x| x * x).sum::<f64>().sqrt();
118+
if norm > 0.0 {
119+
for x in next.iter_mut() {
120+
*x /= norm;
121+
}
122+
}
123+
centrality = next;
124+
}
125+
centrality
126+
}
127+
128+
/// ### PageRank
129+
/// Computes PageRank scores using iterative updates with damping.
130+
pub fn pagerank<A, Ty>(graph: &BaseGraph<A, f64, Ty>, damping: f64, iterations: usize) -> Vec<f64>
131+
where
132+
Ty: crate::graph::types::GraphConstructor<A, f64>,
133+
{
134+
let n = graph.node_count();
135+
let mut rank = vec![1.0 / n as f64; n];
136+
let teleport = (1.0 - damping) / n as f64;
137+
138+
// Precompute out-degree for each node.
139+
let mut out_deg = vec![0usize; n];
140+
for (node, _) in graph.nodes() {
141+
out_deg[node.index()] = graph.neighbors(node).count();
142+
}
143+
144+
for _ in 0..iterations {
145+
let mut new_rank = vec![teleport; n];
146+
for (u, _) in graph.nodes() {
147+
let r = rank[u.index()];
148+
if out_deg[u.index()] > 0 {
149+
let share = damping * r / out_deg[u.index()] as f64;
150+
for v in graph.neighbors(u) {
151+
new_rank[v.index()] += share;
152+
}
153+
} else {
154+
// Distribute uniformly if no out-edges.
155+
for x in new_rank.iter_mut() {
156+
*x += damping * r / n as f64;
157+
}
158+
}
159+
}
160+
rank = new_rank;
161+
}
162+
rank
163+
}
164+
165+
/// ### Katz Centrality
166+
/// Computes centrality using the formula: x = alpha * A * x + beta.
167+
/// beta is a constant offset for each node.
168+
pub fn katz_centrality<A, Ty>(
169+
graph: &BaseGraph<A, f64, Ty>,
170+
alpha: f64,
171+
beta: f64,
172+
iterations: usize,
173+
) -> Vec<f64>
174+
where
175+
Ty: crate::graph::types::GraphConstructor<A, f64>,
176+
{
177+
let n = graph.node_count();
178+
let mut centrality = vec![beta; n];
179+
for _ in 0..iterations {
180+
let mut next = vec![beta; n];
181+
for (node, _) in graph.nodes() {
182+
for neighbor in graph.neighbors(node) {
183+
next[neighbor.index()] += alpha * centrality[node.index()];
184+
}
185+
}
186+
let norm: f64 = next.iter().map(|&x| x * x).sum::<f64>().sqrt();
187+
if norm > 0.0 {
188+
for x in next.iter_mut() {
189+
*x /= norm;
190+
}
191+
}
192+
centrality = next;
193+
}
194+
centrality
195+
}
196+
197+
/// ### Harmonic Centrality
198+
/// For each node, harmonic centrality is defined as the sum of the reciprocals of the shortest
199+
/// path distances to all other nodes (ignoring unreachable nodes). Uses dijkstra,
200+
/// so requires weights to be OrderedFloat<f64>.
201+
pub fn harmonic_centrality<A, Ty>(graph: &BaseGraph<A, OrderedFloat<f64>, Ty>) -> Vec<f64>
202+
where
203+
Ty: crate::graph::types::GraphConstructor<A, OrderedFloat<f64>>,
204+
{
205+
let n = graph.node_count();
206+
let mut centrality = vec![0.0; n];
207+
for (node, _) in graph.nodes() {
208+
let distances = dijkstra(graph, node);
209+
let sum: f64 = distances
210+
.iter()
211+
.filter_map(|&d| d.map(|od| od.0))
212+
.filter(|&d| d > 0.0)
213+
.map(|d| 1.0 / d)
214+
.sum();
215+
centrality[node.index()] = sum;
216+
}
217+
centrality
218+
}

src/centrality/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1+
pub mod algorithms;

0 commit comments

Comments
 (0)