Skip to content

Commit a907d93

Browse files
dizzyihabedi
authored andcommitted
Weighted Eigen Vector Centrality (#17)
1 parent 8b2dd1b commit a907d93

File tree

2 files changed

+125
-16
lines changed

2 files changed

+125
-16
lines changed

src/centrality/algorithms.rs

Lines changed: 124 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,61 @@ where
8181
// -----------------------------
8282
//
8383

84-
/// Full implementation of eigenvector centrality with convergence tolerance.
85-
pub fn eigenvector_centrality_impl<A, Ty>(
86-
graph: &BaseGraph<A, f64, Ty>,
84+
/// Full implementation of eigenvector centrality with convergence tolerance,
85+
/// calculates eigenvector centrality for nodes in a graph iteratively.
86+
///
87+
/// this function designed for generic edge type,
88+
/// see [`eigenvector_centrality`] and [`eigenvector_centrality_numpy`]
89+
/// for cleaner interaction with `f64` edge graph.
90+
///
91+
/// # Arguments:
92+
///
93+
/// * `graph`: the targeted graph.
94+
/// * `max_iter`: The maximum number of iterations that the algorithm will run for.
95+
/// * `tol`: the average tolerance for convergence.
96+
/// * `eval_weight`: callback to evaluate the weight of edges in the graph.
97+
///
98+
/// # Returns :
99+
///
100+
/// a vector of `f64` representing eigenvector centralities of each node in the graph.
101+
///
102+
/// # Example
103+
/// ```rust
104+
/// use graphina::core::types::Graph;
105+
/// use graphina::centrality::algorithms::eigenvector_centrality_impl;
106+
///
107+
/// let mut g: Graph<i32, (f64, f64)> = Graph::new();
108+
/// // ^^^^^^^^^^
109+
/// // L arbitrary type as edge
110+
/// let nodes = [g.add_node(1), g.add_node(2), g.add_node(3)];
111+
/// g.add_edge(nodes[0], nodes[1], (0.0, 1.0));
112+
/// g.add_edge(nodes[0], nodes[2], (1.0, 0.0));
113+
/// let centrality = eigenvector_centrality_impl(
114+
/// &g,
115+
/// 1000,
116+
/// 1e-6_f64,
117+
/// |w| w.0 * 10.0 + w.1 // <-- custom evaluation for edge weight
118+
/// );
119+
/// println!("{:.5?}", centrality); // [0.70711, 0.07036, 0.70360]
120+
/// ```
121+
pub fn eigenvector_centrality_impl<A, W, Ty>(
122+
graph: &BaseGraph<A, W, Ty>,
87123
max_iter: usize,
88124
tol: f64,
125+
eval_weight: impl Fn(&W) -> f64,
89126
) -> Vec<f64>
90127
where
91-
Ty: GraphConstructor<A, f64>,
128+
Ty: GraphConstructor<A, W>,
92129
{
93130
let n = graph.node_count();
94131
let mut centrality = vec![1.0; n];
132+
let mut next = centrality.clone();
95133
for _ in 0..max_iter {
96-
let mut next = vec![0.0; n];
97-
for (node, _) in graph.nodes() {
98-
for neighbor in graph.neighbors(node) {
99-
next[neighbor.index()] += centrality[node.index()];
134+
for (src, dst, w) in graph.edges() {
135+
let w = eval_weight(w);
136+
next[dst.index()] += w * centrality[src.index()];
137+
if !<Ty as GraphConstructor<A, W>>::is_directed() {
138+
next[src.index()] += w * centrality[dst.index()];
100139
}
101140
}
102141
let norm = next.iter().map(|x| x * x).sum::<f64>().sqrt();
@@ -106,11 +145,14 @@ where
106145
}
107146
}
108147
let diff: f64 = centrality
109-
.iter()
148+
.iter_mut()
110149
.zip(next.iter())
111-
.map(|(a, b)| (a - b).abs())
150+
.map(|(a, b)| {
151+
let d = (*a - b).abs();
152+
*a = *b;
153+
d
154+
})
112155
.sum();
113-
centrality = next;
114156
if diff < tol * n as f64 {
115157
break;
116158
}
@@ -119,23 +161,90 @@ where
119161
}
120162

121163
/// Wrapper for eigenvector centrality with default tolerance (1e-6).
122-
pub fn eigenvector_centrality<A, Ty>(graph: &BaseGraph<A, f64, Ty>, max_iter: usize) -> Vec<f64>
164+
/// calculates eigenvector centrality for nodes in a graph iteratively.
165+
///
166+
/// # Arguments:
167+
///
168+
/// * `graph`: the targeted graph.
169+
/// * `max_iter`: The maximum number of iterations that the algorithm will run for.
170+
/// * `weighted`: whether or not the calculated centrality will be weighed.
171+
///
172+
/// # Returns :
173+
///
174+
/// a vector of `f64` representing eigenvector centralities of each node in the graph.
175+
///
176+
/// # Examples :
177+
/// ```rust
178+
/// use graphina::centrality::algorithms::eigenvector_centrality;
179+
/// use graphina::core::types::Graph;
180+
/// let mut g = Graph::new();
181+
///
182+
/// let nodes = [g.add_node(1), g.add_node(2), g.add_node(3)];
183+
/// g.add_edge(nodes[0], nodes[1], 1.0);
184+
/// g.add_edge(nodes[0], nodes[2], 2.0);
185+
/// let centrality = eigenvector_centrality(&g, 1000, false);
186+
/// println!("{:.5?}", centrality); // [0.70711, 0.50000, 0.50000]
187+
/// let centrality = eigenvector_centrality(&g, 1000, true);
188+
/// println!("{:.5?}", centrality); // [0.70711, 0.31623, 0.63246]
189+
/// ```
190+
pub fn eigenvector_centrality<A, Ty>(
191+
graph: &BaseGraph<A, f64, Ty>,
192+
max_iter: usize,
193+
weighted: bool,
194+
) -> Vec<f64>
123195
where
124196
Ty: GraphConstructor<A, f64>,
125197
{
126-
eigenvector_centrality_impl(graph, max_iter, 1e-6_f64)
198+
let eval_weight = if weighted {
199+
|f: &f64| *f
200+
} else {
201+
|_f: &f64| 1.0
202+
};
203+
eigenvector_centrality_impl(graph, max_iter, 1e-6_f64, eval_weight)
127204
}
128205

129-
/// NumPy–style eigenvector centrality (alias to the above).
206+
/// NumPy–style eigenvector centrality (alias to [`eigenvector_centrality`]).
207+
/// calculates eigenvector centrality for nodes in a graph iteratively.
208+
///
209+
/// # Arguments:
210+
///
211+
/// * `graph`: the targeted graph.
212+
/// * `max_iter`: The maximum number of iterations that the algorithm will run for.
213+
/// * `weighted`: whether or not the calculated centrality will be weighed.
214+
///
215+
/// # Returns :
216+
///
217+
/// a vector of `f64` representing eigenvector centralities of each node in the graph.
218+
///
219+
/// # Examples :
220+
/// ```rust
221+
/// use graphina::centrality::algorithms::eigenvector_centrality_numpy;
222+
/// use graphina::core::types::Graph;
223+
/// let mut g = Graph::new();
224+
///
225+
/// let nodes = [g.add_node(1), g.add_node(2), g.add_node(3)];
226+
/// g.add_edge(nodes[0], nodes[1], 1.0);
227+
/// g.add_edge(nodes[0], nodes[2], 2.0);
228+
/// let centrality = eigenvector_centrality_numpy(&g, 1000, false);
229+
/// println!("{:.5?}", centrality); // [0.70711, 0.50000, 0.50000]
230+
/// let centrality = eigenvector_centrality_numpy(&g, 1000, true);
231+
/// println!("{:.5?}", centrality); // [0.70711, 0.31623, 0.63246]
232+
/// ```
130233
pub fn eigenvector_centrality_numpy<A, Ty>(
131234
graph: &BaseGraph<A, f64, Ty>,
132235
max_iter: usize,
133236
tol: f64,
237+
weighted: bool,
134238
) -> Vec<f64>
135239
where
136240
Ty: GraphConstructor<A, f64>,
137241
{
138-
eigenvector_centrality_impl(graph, max_iter, tol)
242+
let eval_weight = if weighted {
243+
|f: &f64| *f
244+
} else {
245+
|_f: &f64| 1.0
246+
};
247+
eigenvector_centrality_impl(graph, max_iter, tol, eval_weight)
139248
}
140249

141250
//

tests/test_centrality_algorithms.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn test_betweenness_centrality() {
8282
#[test]
8383
fn test_eigenvector_centrality() {
8484
let graph = build_test_graph_f64();
85-
let ev = eigenvector_centrality(&graph, 20);
85+
let ev = eigenvector_centrality(&graph, 20, false);
8686
// Check that we have 4 scores and all are positive.
8787
assert_eq!(ev.len(), 4);
8888
for &score in &ev {

0 commit comments

Comments
 (0)