Skip to content

Commit 4801702

Browse files
committed
closeness example
1 parent 462a101 commit 4801702

File tree

3 files changed

+128
-16
lines changed

3 files changed

+128
-16
lines changed

examples/centrality.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,52 @@ fn katz_centrality_impl_example() {
166166
println!("{:.5?}", centrality); // [0.23167, 0.71650, 0.65800]
167167
}
168168

169+
fn closeness_centrality_example() {
170+
use graphina::core::types::Graph;
171+
172+
use graphina::centrality::algorithms::closeness_centrality;
173+
174+
let mut graph = Graph::new();
175+
let ids = (0..5).map(|i| graph.add_node(i)).collect::<Vec<_>>();
176+
let edges = [(0, 1, 1.0), (0, 2, 1.0), (1, 3, 1.0)];
177+
for (s, d, w) in edges {
178+
graph.add_edge(ids[s], ids[d], w);
179+
}
180+
181+
let centrality = closeness_centrality(&graph, false).unwrap();
182+
println!("{:.5?}", centrality); // [0.75000, 0.75000, 0.50000, 0.50000, 0.00000]
183+
}
184+
185+
fn closeness_centrality_impl_example() {
186+
use graphina::core::types::Graph;
187+
188+
use graphina::centrality::algorithms::closeness_centrality_impl;
189+
190+
let mut graph: Graph<i32, (String, f64)> = Graph::new();
191+
192+
let ids = (0..5).map(|i| graph.add_node(i)).collect::<Vec<_>>();
193+
194+
let edges = [
195+
(0, 1, ("friend".to_string(), 0.9)),
196+
(0, 2, ("family".to_string(), 0.8)),
197+
(1, 3, ("friend".to_string(), 0.7)),
198+
(2, 4, ("enemy".to_string(), 0.1)),
199+
];
200+
for (s, d, w) in edges {
201+
graph.add_edge(ids[s], ids[d], w);
202+
}
203+
204+
let eval_cost = |(s, f): &(String, f64)| match s.as_str() {
205+
"friend" => Some(1.0 / *f / 2.0),
206+
"family" => Some(1.0 / *f / 4.0),
207+
"enemy" => None,
208+
_ => Some(1.0 / *f),
209+
};
210+
211+
let centrality = closeness_centrality_impl(&graph, eval_cost, true).unwrap();
212+
println!("{:.5?}", centrality); // [1.05244, 1.05244, 0.81436, 0.63088, 0.00000]
213+
}
214+
169215
macro_rules! run_examples {
170216
($($func:ident),* $(,)?) => {
171217
$(
@@ -189,6 +235,9 @@ fn main() {
189235
// katz centrality
190236
katz_centrality_example,
191237
katz_centrality_numpy_example,
192-
katz_centrality_impl_example
238+
katz_centrality_impl_example,
239+
// closeness centrality
240+
closeness_centrality_example,
241+
closeness_centrality_impl_example,
193242
);
194243
}

src/centrality/algorithms.rs

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -581,19 +581,54 @@ where
581581
///
582582
/// Closeness = (n - 1) / (sum of shortest-path distances).
583583
///
584+
/// where n is number of reachable nodes.
585+
///
584586
/// # Arguments
585587
///
586588
/// * `graph`: the targeted graph.
587589
/// * `eval_cost`: callback to evaluate the cost of edges in the graph, returning
588590
/// - `Some(f64)` for cost
589591
/// - `None` for impassable
592+
/// * `wf_improved`: whether or not to scale the result by reachability ratio.
590593
///
591594
/// # Returns
592595
///
593596
/// a vector of `f64` representing closeness centralities of each node in the graph.
597+
///
598+
/// # Example
599+
/// ```rust
600+
/// use graphina::core::types::Graph;
601+
///
602+
/// use graphina::centrality::algorithms::closeness_centrality_impl;
603+
///
604+
/// let mut graph: Graph<i32, (String, f64)> = Graph::new();
605+
///
606+
/// let ids = (0..5).map(|i| graph.add_node(i)).collect::<Vec<_>>();
607+
///
608+
/// let edges = [
609+
/// (0, 1, ("friend".to_string(), 0.9)),
610+
/// (0, 2, ("family".to_string(), 0.8)),
611+
/// (1, 3, ("friend".to_string(), 0.7)),
612+
/// (2, 4, ("enemy".to_string(), 0.1)),
613+
/// ];
614+
/// for (s, d, w) in edges {
615+
/// graph.add_edge(ids[s], ids[d], w);
616+
/// }
617+
///
618+
/// let eval_cost = |(s, f): &(String, f64)| match s.as_str() {
619+
/// "friend" => Some(1.0 / *f / 2.0),
620+
/// "family" => Some(1.0 / *f / 4.0),
621+
/// "enemy" => None,
622+
/// _ => Some(1.0 / *f),
623+
/// };
624+
///
625+
/// let centrality = closeness_centrality_impl(&graph, eval_cost, true).unwrap();
626+
/// println!("{:.5?}", centrality); // [1.05244, 1.05244, 0.81436, 0.63088, 0.00000]
627+
/// ```
594628
pub fn closeness_centrality_impl<A, W, Ty>(
595629
graph: &BaseGraph<A, W, Ty>,
596630
eval_cost: impl Fn(&W) -> Option<f64>,
631+
wf_improved: bool,
597632
) -> Result<Vec<f64>, GraphinaException>
598633
where
599634
A: Debug,
@@ -605,32 +640,60 @@ where
605640
let mut closeness = vec![0.0; n];
606641
for (node, _) in graph.nodes() {
607642
let (distances, _) = dijkstra_path_impl(graph, node, None, &eval_cost)?;
643+
let reachable = distances.iter().filter(|d| d.is_some()).count() as f64;
608644
let sum: f64 = distances.iter().filter_map(|d| d.to_owned()).sum();
609645
if sum > 0.0 {
610-
closeness[node.index()] = (n as f64 - 1.0) / sum;
646+
closeness[node.index()] = (reachable - 1.0) / sum;
647+
}
648+
if wf_improved {
649+
closeness[node.index()] *= (reachable - 1.0) / (n as f64 - 1.0);
611650
}
612651
}
613652
Ok(closeness)
614653
}
615654

616655
/// Compute closeness centrality using Dijkstra’s algorithm.
656+
///
617657
/// Closeness = (n - 1) / (sum of shortest-path distances).
658+
///
659+
/// where n is number of reachable nodes.
660+
///
661+
/// # Arguments
662+
///
663+
/// * `graph`: the targeted graph.
664+
/// * `wf_improved`: whether or not to scale the result by reachability ratio.
665+
///
666+
/// # Returns
667+
///
668+
/// a vector of `f64` representing closeness centralities of each node in the graph.
669+
///
670+
/// # Example
671+
/// ```rust
672+
/// use graphina::core::types::Graph;
673+
///
674+
/// use graphina::centrality::algorithms::closeness_centrality;
675+
///
676+
/// let mut graph = Graph::new();
677+
/// let ids = (0..5).map(|i| graph.add_node(i)).collect::<Vec<_>>();
678+
/// let edges = [(0, 1, 1.0), (0, 2, 1.0), (1, 3, 1.0)];
679+
/// for (s, d, w) in edges {
680+
/// graph.add_edge(ids[s], ids[d], w);
681+
/// }
682+
///
683+
/// let centrality = closeness_centrality(&graph, false).unwrap();
684+
/// println!("{:.5?}", centrality); // [0.75000, 0.75000, 0.50000, 0.50000, 0.00000]
685+
/// ```
618686
pub fn closeness_centrality<A, Ty>(
619-
graph: &BaseGraph<A, ordered_float::OrderedFloat<f64>, Ty>,
687+
graph: &BaseGraph<A, f64, Ty>,
688+
wf_improved: bool,
620689
) -> Result<Vec<f64>, GraphinaException>
621690
where
622-
Ty: GraphConstructor<A, ordered_float::OrderedFloat<f64>>,
691+
A: Debug,
692+
Ty: GraphConstructor<A, f64>,
693+
BaseGraph<A, f64, Ty>: GraphinaGraph<A, f64>,
623694
{
624-
let n = graph.node_count();
625-
let mut closeness = vec![0.0; n];
626-
for (node, _) in graph.nodes() {
627-
let distances = dijkstra(graph, node)?;
628-
let sum: f64 = distances.iter().filter_map(|d| d.map(|od| od.0)).sum();
629-
if sum > 0.0 {
630-
closeness[node.index()] = (n as f64 - 1.0) / sum;
631-
}
632-
}
633-
Ok(closeness)
695+
let eval_cost = |f: &f64| Some(*f);
696+
closeness_centrality_impl(graph, eval_cost, wf_improved)
634697
}
635698

636699
//

tests/test_centrality_algorithms.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ fn test_degree_centrality() {
5959

6060
#[test]
6161
fn test_closeness_centrality() {
62-
let graph = build_test_graph_ordered();
63-
let closeness = closeness_centrality(&graph).unwrap();
62+
let graph = build_test_graph_f64();
63+
let closeness = closeness_centrality(&graph, false).unwrap();
6464
// In our strongly connected graph with all edges = 1.0,
6565
// each node's distances: two neighbors at 1 and one at 2 -> sum = 4.
6666
// Closeness = (n-1)/sum = 3/4 = 0.75.

0 commit comments

Comments
 (0)