rustworkx_core/
centrality.rs

1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use std::collections::VecDeque;
14use std::hash::Hash;
15use std::sync::RwLock;
16
17use hashbrown::HashMap;
18use petgraph::algo::dijkstra;
19use petgraph::visit::{
20    Bfs, EdgeCount, EdgeIndexable, EdgeRef, GraphBase, GraphProp, IntoEdgeReferences, IntoEdges,
21    IntoEdgesDirected, IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount,
22    NodeIndexable, Reversed, ReversedEdgeReference, Visitable,
23};
24use rayon_cond::CondIterator;
25
26/// Compute the betweenness centrality of all nodes in a graph.
27///
28/// The algorithm used in this function is based on:
29///
30/// Ulrik Brandes, A Faster Algorithm for Betweenness Centrality.
31/// Journal of Mathematical Sociology 25(2):163-177, 2001.
32///
33/// This function is multithreaded and will run in parallel if the number
34/// of nodes in the graph is above the value of ``parallel_threshold``. If the
35/// function will be running in parallel the env var ``RAYON_NUM_THREADS`` can
36/// be used to adjust how many threads will be used.
37///
38/// Arguments:
39///
40/// * `graph` - The graph object to run the algorithm on
41/// * `include_endpoints` - Whether to include the endpoints of paths in the path
42///   lengths used to compute the betweenness
43/// * `normalized` - Whether to normalize the betweenness scores by the number
44///   of distinct paths between all pairs of nodes
45/// * `parallel_threshold` - The number of nodes to calculate the betweenness
46///   centrality in parallel at, if the number of nodes in `graph` is less
47///   than this value it will run in a single thread. A good default to use
48///   here if you're not sure is `50` as that was found to be roughly the
49///   number of nodes where parallelism improves performance
50///
51/// # Example
52/// ```rust
53/// use rustworkx_core::petgraph;
54/// use rustworkx_core::centrality::betweenness_centrality;
55///
56/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
57///     (0, 4), (1, 2), (2, 3), (3, 4), (1, 4)
58/// ]);
59/// // Calculate the betweenness centrality
60/// let output = betweenness_centrality(&g, true, true, 200);
61/// assert_eq!(
62///     vec![Some(0.4), Some(0.5), Some(0.45), Some(0.5), Some(0.75)],
63///     output
64/// );
65/// ```
66/// # See Also
67/// [`edge_betweenness_centrality`]
68pub fn betweenness_centrality<G>(
69    graph: G,
70    include_endpoints: bool,
71    normalized: bool,
72    parallel_threshold: usize,
73) -> Vec<Option<f64>>
74where
75    G: NodeIndexable
76        + IntoNodeIdentifiers
77        + IntoNeighborsDirected
78        + NodeCount
79        + GraphProp
80        + GraphBase
81        + std::marker::Sync,
82    <G as GraphBase>::NodeId: std::cmp::Eq + Hash + Send,
83    // rustfmt deletes the following comments if placed inline above
84    // + IntoNodeIdentifiers // for node_identifiers()
85    // + IntoNeighborsDirected // for neighbors()
86    // + NodeCount // for node_count
87    // + GraphProp // for is_directed
88{
89    // Correspondence of variable names to quantities in the paper is as follows:
90    //
91    // P -- predecessors
92    // S -- verts_sorted_by_distance,
93    //      vertices in order of non-decreasing distance from s
94    // Q -- Q
95    // sigma -- sigma
96    // delta -- delta
97    // d -- distance
98    let max_index = graph.node_bound();
99
100    let mut betweenness: Vec<Option<f64>> = vec![None; max_index];
101    for node_s in graph.node_identifiers() {
102        let is: usize = graph.to_index(node_s);
103        betweenness[is] = Some(0.0);
104    }
105    let locked_betweenness = RwLock::new(&mut betweenness);
106    let node_indices: Vec<G::NodeId> = graph.node_identifiers().collect();
107
108    CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
109        .map(|node_s| (shortest_path_for_centrality(&graph, &node_s), node_s))
110        .for_each(|(mut shortest_path_calc, node_s)| {
111            _accumulate_vertices(
112                &locked_betweenness,
113                max_index,
114                &mut shortest_path_calc,
115                node_s,
116                &graph,
117                include_endpoints,
118            );
119        });
120
121    _rescale(
122        &mut betweenness,
123        graph.node_count(),
124        normalized,
125        graph.is_directed(),
126        include_endpoints,
127    );
128
129    betweenness
130}
131
132/// Compute the edge betweenness centrality of all edges in a graph.
133///
134/// The algorithm used in this function is based on:
135///
136/// Ulrik Brandes: On Variants of Shortest-Path Betweenness
137/// Centrality and their Generic Computation.
138/// Social Networks 30(2):136-145, 2008.
139/// <https://doi.org/10.1016/j.socnet.2007.11.001>.
140///
141/// This function is multithreaded and will run in parallel if the number
142/// of nodes in the graph is above the value of ``parallel_threshold``. If the
143/// function will be running in parallel the env var ``RAYON_NUM_THREADS`` can
144/// be used to adjust how many threads will be used.
145///
146/// Arguments:
147///
148/// * `graph` - The graph object to run the algorithm on
149/// * `normalized` - Whether to normalize the betweenness scores by the number
150///   of distinct paths between all pairs of nodes
151/// * `parallel_threshold` - The number of nodes to calculate the betweenness
152///   centrality in parallel at, if the number of nodes in `graph` is less
153///   than this value it will run in a single thread. A good default to use
154///   here if you're not sure is `50` as that was found to be roughly the
155///   number of nodes where parallelism improves performance
156///
157/// # Example
158/// ```rust
159/// use rustworkx_core::petgraph;
160/// use rustworkx_core::centrality::edge_betweenness_centrality;
161///
162/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
163///     (0, 4), (1, 2), (1, 3), (2, 3), (3, 4), (1, 4)
164/// ]);
165///
166/// let output = edge_betweenness_centrality(&g, false, 200);
167/// let expected = vec![Some(4.0), Some(2.0), Some(1.0), Some(2.0), Some(3.0), Some(3.0)];
168/// assert_eq!(output, expected);
169/// ```
170/// # See Also
171/// [`betweenness_centrality`]
172pub fn edge_betweenness_centrality<G>(
173    graph: G,
174    normalized: bool,
175    parallel_threshold: usize,
176) -> Vec<Option<f64>>
177where
178    G: NodeIndexable
179        + EdgeIndexable
180        + IntoEdges
181        + IntoNodeIdentifiers
182        + IntoNeighborsDirected
183        + NodeCount
184        + EdgeCount
185        + GraphProp
186        + Sync,
187    G::NodeId: Eq + Hash + Send,
188    G::EdgeId: Eq + Hash + Send,
189{
190    let max_index = graph.node_bound();
191    let mut betweenness = vec![None; graph.edge_bound()];
192    for edge in graph.edge_references() {
193        let is: usize = EdgeIndexable::to_index(&graph, edge.id());
194        betweenness[is] = Some(0.0);
195    }
196    let locked_betweenness = RwLock::new(&mut betweenness);
197    let node_indices: Vec<G::NodeId> = graph.node_identifiers().collect();
198    CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
199        .map(|node_s| shortest_path_for_edge_centrality(&graph, &node_s))
200        .for_each(|mut shortest_path_calc| {
201            accumulate_edges(
202                &locked_betweenness,
203                max_index,
204                &mut shortest_path_calc,
205                &graph,
206            );
207        });
208
209    _rescale(
210        &mut betweenness,
211        graph.node_count(),
212        normalized,
213        graph.is_directed(),
214        true,
215    );
216    betweenness
217}
218
219fn _rescale(
220    betweenness: &mut [Option<f64>],
221    node_count: usize,
222    normalized: bool,
223    directed: bool,
224    include_endpoints: bool,
225) {
226    let mut do_scale = true;
227    let mut scale = 1.0;
228    if normalized {
229        if include_endpoints {
230            if node_count < 2 {
231                do_scale = false;
232            } else {
233                scale = 1.0 / (node_count * (node_count - 1)) as f64;
234            }
235        } else if node_count <= 2 {
236            do_scale = false;
237        } else {
238            scale = 1.0 / ((node_count - 1) * (node_count - 2)) as f64;
239        }
240    } else if !directed {
241        scale = 0.5;
242    } else {
243        do_scale = false;
244    }
245    if do_scale {
246        for x in betweenness.iter_mut() {
247            *x = x.map(|y| y * scale);
248        }
249    }
250}
251
252fn _accumulate_vertices<G>(
253    locked_betweenness: &RwLock<&mut Vec<Option<f64>>>,
254    max_index: usize,
255    path_calc: &mut ShortestPathData<G>,
256    node_s: <G as GraphBase>::NodeId,
257    graph: G,
258    include_endpoints: bool,
259) where
260    G: NodeIndexable
261        + IntoNodeIdentifiers
262        + IntoNeighborsDirected
263        + NodeCount
264        + GraphProp
265        + GraphBase
266        + std::marker::Sync,
267    <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
268{
269    let mut delta = vec![0.0; max_index];
270    for w in &path_calc.verts_sorted_by_distance {
271        let iw = graph.to_index(*w);
272        let coeff = (1.0 + delta[iw]) / path_calc.sigma[w];
273        let p_w = path_calc.predecessors.get(w).unwrap();
274        for v in p_w {
275            let iv = graph.to_index(*v);
276            delta[iv] += path_calc.sigma[v] * coeff;
277        }
278    }
279    let mut betweenness = locked_betweenness.write().unwrap();
280    if include_endpoints {
281        let i_node_s = graph.to_index(node_s);
282        betweenness[i_node_s] = betweenness[i_node_s]
283            .map(|x| x + ((path_calc.verts_sorted_by_distance.len() - 1) as f64));
284        for w in &path_calc.verts_sorted_by_distance {
285            if *w != node_s {
286                let iw = graph.to_index(*w);
287                betweenness[iw] = betweenness[iw].map(|x| x + delta[iw] + 1.0);
288            }
289        }
290    } else {
291        for w in &path_calc.verts_sorted_by_distance {
292            if *w != node_s {
293                let iw = graph.to_index(*w);
294                betweenness[iw] = betweenness[iw].map(|x| x + delta[iw]);
295            }
296        }
297    }
298}
299
300fn accumulate_edges<G>(
301    locked_betweenness: &RwLock<&mut Vec<Option<f64>>>,
302    max_index: usize,
303    path_calc: &mut ShortestPathDataWithEdges<G>,
304    graph: G,
305) where
306    G: NodeIndexable + EdgeIndexable + Sync,
307    G::NodeId: Eq + Hash,
308    G::EdgeId: Eq + Hash,
309{
310    let mut delta = vec![0.0; max_index];
311    for w in &path_calc.verts_sorted_by_distance {
312        let iw = NodeIndexable::to_index(&graph, *w);
313        let coeff = (1.0 + delta[iw]) / path_calc.sigma[w];
314        let p_w = path_calc.predecessors.get(w).unwrap();
315        let e_w = path_calc.predecessor_edges.get(w).unwrap();
316        let mut betweenness = locked_betweenness.write().unwrap();
317        for i in 0..p_w.len() {
318            let v = p_w[i];
319            let iv = NodeIndexable::to_index(&graph, v);
320            let ie = EdgeIndexable::to_index(&graph, e_w[i]);
321            let c = path_calc.sigma[&v] * coeff;
322            betweenness[ie] = betweenness[ie].map(|x| x + c);
323            delta[iv] += c;
324        }
325    }
326}
327/// Compute the degree centrality of all nodes in a graph.
328///
329/// For undirected graphs, this calculates the normalized degree for each node.
330/// For directed graphs, this calculates the normalized out-degree for each node.
331///
332/// Arguments:
333///
334/// * `graph` - The graph object to calculate degree centrality for
335///
336/// # Example
337/// ```rust
338/// use rustworkx_core::petgraph::graph::{UnGraph, DiGraph};
339/// use rustworkx_core::centrality::degree_centrality;
340///
341/// // Undirected graph example
342/// let graph = UnGraph::<i32, ()>::from_edges(&[
343///     (0, 1), (1, 2), (2, 3), (3, 0)
344/// ]);
345/// let centrality = degree_centrality(&graph, None);
346///
347/// // Directed graph example
348/// let digraph = DiGraph::<i32, ()>::from_edges(&[
349///     (0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3)
350/// ]);
351/// let centrality = degree_centrality(&digraph, None);
352/// ```
353pub fn degree_centrality<G>(graph: G, direction: Option<petgraph::Direction>) -> Vec<f64>
354where
355    G: NodeIndexable
356        + IntoNodeIdentifiers
357        + IntoNeighbors
358        + IntoNeighborsDirected
359        + NodeCount
360        + GraphProp,
361    G::NodeId: Eq + Hash,
362{
363    let node_count = graph.node_count() as f64;
364    let mut centrality = vec![0.0; graph.node_bound()];
365
366    for node in graph.node_identifiers() {
367        let (degree, normalization) = match (graph.is_directed(), direction) {
368            (true, None) => {
369                let out_degree = graph
370                    .neighbors_directed(node, petgraph::Direction::Outgoing)
371                    .count() as f64;
372                let in_degree = graph
373                    .neighbors_directed(node, petgraph::Direction::Incoming)
374                    .count() as f64;
375                let total = in_degree + out_degree;
376                // Use 2(n-1) normalization only if this is a complete graph
377                let norm = if total == 2.0 * (node_count - 1.0) {
378                    2.0 * (node_count - 1.0)
379                } else {
380                    node_count - 1.0
381                };
382                (total, norm)
383            }
384            (true, Some(dir)) => (
385                graph.neighbors_directed(node, dir).count() as f64,
386                node_count - 1.0,
387            ),
388            (false, _) => (graph.neighbors(node).count() as f64, node_count - 1.0),
389        };
390        centrality[graph.to_index(node)] = degree / normalization;
391    }
392
393    centrality
394}
395
396struct ShortestPathData<G>
397where
398    G: GraphBase,
399    <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
400{
401    verts_sorted_by_distance: Vec<G::NodeId>,
402    predecessors: HashMap<G::NodeId, Vec<G::NodeId>>,
403    sigma: HashMap<G::NodeId, f64>,
404}
405
406fn shortest_path_for_centrality<G>(graph: G, node_s: &G::NodeId) -> ShortestPathData<G>
407where
408    G: NodeIndexable + IntoNodeIdentifiers + IntoNeighborsDirected + NodeCount + GraphBase,
409    <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
410{
411    let mut verts_sorted_by_distance: Vec<G::NodeId> = Vec::new(); // a stack
412    let c = graph.node_count();
413    let mut predecessors = HashMap::<G::NodeId, Vec<G::NodeId>>::with_capacity(c);
414    let mut sigma = HashMap::<G::NodeId, f64>::with_capacity(c);
415    let mut distance = HashMap::<G::NodeId, i64>::with_capacity(c);
416    #[allow(non_snake_case)]
417    let mut Q: VecDeque<G::NodeId> = VecDeque::with_capacity(c);
418
419    for node in graph.node_identifiers() {
420        predecessors.insert(node, Vec::new());
421        sigma.insert(node, 0.0);
422        distance.insert(node, -1);
423    }
424    sigma.insert(*node_s, 1.0);
425    distance.insert(*node_s, 0);
426    Q.push_back(*node_s);
427    while let Some(v) = Q.pop_front() {
428        verts_sorted_by_distance.push(v);
429        let distance_v = distance[&v];
430        for w in graph.neighbors(v) {
431            if distance[&w] < 0 {
432                Q.push_back(w);
433                distance.insert(w, distance_v + 1);
434            }
435            if distance[&w] == distance_v + 1 {
436                sigma.insert(w, sigma[&w] + sigma[&v]);
437                let e_p = predecessors.get_mut(&w).unwrap();
438                e_p.push(v);
439            }
440        }
441    }
442    verts_sorted_by_distance.reverse(); // will be effectively popping from the stack
443    ShortestPathData {
444        verts_sorted_by_distance,
445        predecessors,
446        sigma,
447    }
448}
449
450struct ShortestPathDataWithEdges<G>
451where
452    G: GraphBase,
453    G::NodeId: Eq + Hash,
454    G::EdgeId: Eq + Hash,
455{
456    verts_sorted_by_distance: Vec<G::NodeId>,
457    predecessors: HashMap<G::NodeId, Vec<G::NodeId>>,
458    predecessor_edges: HashMap<G::NodeId, Vec<G::EdgeId>>,
459    sigma: HashMap<G::NodeId, f64>,
460}
461
462fn shortest_path_for_edge_centrality<G>(
463    graph: G,
464    node_s: &G::NodeId,
465) -> ShortestPathDataWithEdges<G>
466where
467    G: NodeIndexable
468        + IntoNodeIdentifiers
469        + IntoNeighborsDirected
470        + NodeCount
471        + GraphBase
472        + IntoEdges,
473    G::NodeId: Eq + Hash,
474    G::EdgeId: Eq + Hash,
475{
476    let mut verts_sorted_by_distance: Vec<G::NodeId> = Vec::new(); // a stack
477    let c = graph.node_count();
478    let mut predecessors = HashMap::<G::NodeId, Vec<G::NodeId>>::with_capacity(c);
479    let mut predecessor_edges = HashMap::<G::NodeId, Vec<G::EdgeId>>::with_capacity(c);
480    let mut sigma = HashMap::<G::NodeId, f64>::with_capacity(c);
481    let mut distance = HashMap::<G::NodeId, i64>::with_capacity(c);
482    #[allow(non_snake_case)]
483    let mut Q: VecDeque<G::NodeId> = VecDeque::with_capacity(c);
484
485    for node in graph.node_identifiers() {
486        predecessors.insert(node, Vec::new());
487        predecessor_edges.insert(node, Vec::new());
488        sigma.insert(node, 0.0);
489        distance.insert(node, -1);
490    }
491    sigma.insert(*node_s, 1.0);
492    distance.insert(*node_s, 0);
493    Q.push_back(*node_s);
494    while let Some(v) = Q.pop_front() {
495        verts_sorted_by_distance.push(v);
496        let distance_v = distance[&v];
497        for edge in graph.edges(v) {
498            let w = edge.target();
499            if distance[&w] < 0 {
500                Q.push_back(w);
501                distance.insert(w, distance_v + 1);
502            }
503            if distance[&w] == distance_v + 1 {
504                sigma.insert(w, sigma[&w] + sigma[&v]);
505                let e_p = predecessors.get_mut(&w).unwrap();
506                e_p.push(v);
507                predecessor_edges.get_mut(&w).unwrap().push(edge.id());
508            }
509        }
510    }
511    verts_sorted_by_distance.reverse(); // will be effectively popping from the stack
512    ShortestPathDataWithEdges {
513        verts_sorted_by_distance,
514        predecessors,
515        predecessor_edges,
516        sigma,
517    }
518}
519
520#[cfg(test)]
521mod test_edge_betweenness_centrality {
522    use crate::centrality::edge_betweenness_centrality;
523    use petgraph::graph::edge_index;
524    use petgraph::prelude::StableGraph;
525    use petgraph::Undirected;
526
527    macro_rules! assert_almost_equal {
528        ($x:expr, $y:expr, $d:expr) => {
529            if ($x - $y).abs() >= $d {
530                panic!("{} != {} within delta of {}", $x, $y, $d);
531            }
532        };
533    }
534
535    #[test]
536    fn test_undirected_graph_normalized() {
537        let graph = petgraph::graph::UnGraph::<(), ()>::from_edges([
538            (0, 6),
539            (0, 4),
540            (0, 1),
541            (0, 5),
542            (1, 6),
543            (1, 7),
544            (1, 3),
545            (1, 4),
546            (2, 6),
547            (2, 3),
548            (3, 5),
549            (3, 7),
550            (3, 6),
551            (4, 5),
552            (5, 6),
553        ]);
554        let output = edge_betweenness_centrality(&graph, true, 50);
555        let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
556        let expected_values = [
557            0.1023809, 0.0547619, 0.0922619, 0.05654762, 0.09940476, 0.125, 0.09940476, 0.12440476,
558            0.12857143, 0.12142857, 0.13511905, 0.125, 0.06547619, 0.08869048, 0.08154762,
559        ];
560        for i in 0..15 {
561            assert_almost_equal!(result[i], expected_values[i], 1e-4);
562        }
563    }
564
565    #[test]
566    fn test_undirected_graph_unnormalized() {
567        let graph = petgraph::graph::UnGraph::<(), ()>::from_edges([
568            (0, 2),
569            (0, 4),
570            (0, 1),
571            (1, 3),
572            (1, 5),
573            (1, 7),
574            (2, 7),
575            (2, 3),
576            (3, 5),
577            (3, 6),
578            (4, 6),
579            (5, 7),
580        ]);
581        let output = edge_betweenness_centrality(&graph, false, 50);
582        let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
583        let expected_values = [
584            3.83333, 5.5, 5.33333, 3.5, 2.5, 3.0, 3.5, 4.0, 3.66667, 6.5, 3.5, 2.16667,
585        ];
586        for i in 0..12 {
587            assert_almost_equal!(result[i], expected_values[i], 1e-4);
588        }
589    }
590
591    #[test]
592    fn test_directed_graph_normalized() {
593        let graph = petgraph::graph::DiGraph::<(), ()>::from_edges([
594            (0, 1),
595            (1, 0),
596            (1, 3),
597            (1, 2),
598            (1, 4),
599            (2, 3),
600            (2, 4),
601            (2, 1),
602            (3, 2),
603            (4, 3),
604        ]);
605        let output = edge_betweenness_centrality(&graph, true, 50);
606        let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
607        let expected_values = [0.2, 0.2, 0.1, 0.1, 0.1, 0.05, 0.1, 0.3, 0.35, 0.2];
608        for i in 0..10 {
609            assert_almost_equal!(result[i], expected_values[i], 1e-4);
610        }
611    }
612
613    #[test]
614    fn test_directed_graph_unnormalized() {
615        let graph = petgraph::graph::DiGraph::<(), ()>::from_edges([
616            (0, 4),
617            (1, 0),
618            (1, 3),
619            (2, 3),
620            (2, 4),
621            (2, 0),
622            (3, 4),
623            (3, 2),
624            (3, 1),
625            (4, 1),
626        ]);
627        let output = edge_betweenness_centrality(&graph, false, 50);
628        let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
629        let expected_values = [4.5, 3.0, 6.5, 1.5, 1.5, 1.5, 1.5, 4.5, 2.0, 7.5];
630        for i in 0..10 {
631            assert_almost_equal!(result[i], expected_values[i], 1e-4);
632        }
633    }
634
635    #[test]
636    fn test_stable_graph_with_removed_edges() {
637        let mut graph: StableGraph<(), (), Undirected> =
638            StableGraph::from_edges([(0, 1), (1, 2), (2, 3), (3, 0)]);
639        graph.remove_edge(edge_index(1));
640        let result = edge_betweenness_centrality(&graph, false, 50);
641        let expected_values = vec![Some(3.0), None, Some(3.0), Some(4.0)];
642        assert_eq!(result, expected_values);
643    }
644}
645
646/// Compute the eigenvector centrality of a graph
647///
648/// For details on the eigenvector centrality refer to:
649///
650/// Phillip Bonacich. “Power and Centrality: A Family of Measures.”
651/// American Journal of Sociology 92(5):1170–1182, 1986
652/// <https://doi.org/10.1086/228631>
653///
654/// This function uses a power iteration method to compute the eigenvector
655/// and convergence is not guaranteed. The function will stop when `max_iter`
656/// iterations is reached or when the computed vector between two iterations
657/// is smaller than the error tolerance multiplied by the number of nodes.
658/// The implementation of this algorithm is based on the NetworkX
659/// [`eigenvector_centrality()`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.centrality.eigenvector_centrality.html)
660/// function.
661///
662/// In the case of multigraphs the weights of any parallel edges will be
663/// summed when computing the eigenvector centrality.
664///
665/// Arguments:
666///
667/// * `graph` - The graph object to run the algorithm on
668/// * `weight_fn` - An input callable that will be passed the `EdgeRef` for
669///   an edge in the graph and is expected to return a `Result<f64>` of
670///   the weight of that edge.
671/// * `max_iter` - The maximum number of iterations in the power method. If
672///   set to `None` a default value of 100 is used.
673/// * `tol` - The error tolerance used when checking for convergence in the
674///   power method. If set to `None` a default value of 1e-6 is used.
675///
676/// # Example
677/// ```rust
678/// use rustworkx_core::Result;
679/// use rustworkx_core::petgraph;
680/// use rustworkx_core::petgraph::visit::{IntoEdges, IntoNodeIdentifiers};
681/// use rustworkx_core::centrality::eigenvector_centrality;
682///
683/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
684///     (0, 1), (1, 2)
685/// ]);
686/// // Calculate the eigenvector centrality
687/// let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| {Ok(1.)}, None, None);
688/// ```
689pub fn eigenvector_centrality<G, F, E>(
690    graph: G,
691    mut weight_fn: F,
692    max_iter: Option<usize>,
693    tol: Option<f64>,
694) -> Result<Option<Vec<f64>>, E>
695where
696    G: NodeIndexable + IntoNodeIdentifiers + IntoNeighbors + IntoEdges + NodeCount,
697    G::NodeId: Eq + std::hash::Hash,
698    F: FnMut(G::EdgeRef) -> Result<f64, E>,
699{
700    let tol: f64 = tol.unwrap_or(1e-6);
701    let max_iter = max_iter.unwrap_or(100);
702    let mut x: Vec<f64> = vec![1.; graph.node_bound()];
703    let node_count = graph.node_count();
704    for _ in 0..max_iter {
705        let x_last = x.clone();
706        for node_index in graph.node_identifiers() {
707            let node = graph.to_index(node_index);
708            for edge in graph.edges(node_index) {
709                let w = weight_fn(edge)?;
710                let neighbor = edge.target();
711                x[graph.to_index(neighbor)] += x_last[node] * w;
712            }
713        }
714        let norm: f64 = x.iter().map(|val| val.powi(2)).sum::<f64>().sqrt();
715        if norm == 0. {
716            return Ok(None);
717        }
718        for v in x.iter_mut() {
719            *v /= norm;
720        }
721        if (0..x.len())
722            .map(|node| (x[node] - x_last[node]).abs())
723            .sum::<f64>()
724            < node_count as f64 * tol
725        {
726            return Ok(Some(x));
727        }
728    }
729    Ok(None)
730}
731
732/// Compute the Katz centrality of a graph
733///
734/// For details on the Katz centrality refer to:
735///
736/// Leo Katz. “A New Status Index Derived from Sociometric Index.”
737/// Psychometrika 18(1):39–43, 1953
738/// <https://link.springer.com/content/pdf/10.1007/BF02289026.pdf>
739///
740/// This function uses a power iteration method to compute the eigenvector
741/// and convergence is not guaranteed. The function will stop when `max_iter`
742/// iterations is reached or when the computed vector between two iterations
743/// is smaller than the error tolerance multiplied by the number of nodes.
744/// The implementation of this algorithm is based on the NetworkX
745/// [`katz_centrality()`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.centrality.katz_centrality.html)
746/// function.
747///
748/// In the case of multigraphs the weights of any parallel edges will be
749/// summed when computing the eigenvector centrality.
750///
751/// Arguments:
752///
753/// * `graph` - The graph object to run the algorithm on
754/// * `weight_fn` - An input callable that will be passed the `EdgeRef` for
755///   an edge in the graph and is expected to return a `Result<f64>` of
756///   the weight of that edge.
757/// * `alpha` - Attenuation factor. If set to `None`, a default value of 0.1 is used.
758/// * `beta_map` - Immediate neighbourhood weights. Must contain all node indices or be `None`.
759/// * `beta_scalar` - Immediate neighbourhood scalar that replaces `beta_map` in case `beta_map` is None.
760///   Defaults to 1.0 in case `None` is provided.
761/// * `max_iter` - The maximum number of iterations in the power method. If
762///   set to `None` a default value of 100 is used.
763/// * `tol` - The error tolerance used when checking for convergence in the
764///   power method. If set to `None` a default value of 1e-6 is used.
765///
766/// # Example
767/// ```rust
768/// use rustworkx_core::Result;
769/// use rustworkx_core::petgraph;
770/// use rustworkx_core::petgraph::visit::{IntoEdges, IntoNodeIdentifiers};
771/// use rustworkx_core::centrality::katz_centrality;
772///
773/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
774///     (0, 1), (1, 2)
775/// ]);
776/// // Calculate the eigenvector centrality
777/// let output: Result<Option<Vec<f64>>> = katz_centrality(&g, |_| {Ok(1.)}, None, None, None, None, None);
778/// let centralities = output.unwrap().unwrap();
779/// assert!(centralities[1] > centralities[0], "Node 1 is more central than node 0");
780/// assert!(centralities[1] > centralities[2], "Node 1 is more central than node 2");
781/// ```
782pub fn katz_centrality<G, F, E>(
783    graph: G,
784    mut weight_fn: F,
785    alpha: Option<f64>,
786    beta_map: Option<HashMap<usize, f64>>,
787    beta_scalar: Option<f64>,
788    max_iter: Option<usize>,
789    tol: Option<f64>,
790) -> Result<Option<Vec<f64>>, E>
791where
792    G: NodeIndexable + IntoNodeIdentifiers + IntoNeighbors + IntoEdges + NodeCount,
793    G::NodeId: Eq + std::hash::Hash,
794    F: FnMut(G::EdgeRef) -> Result<f64, E>,
795{
796    let alpha: f64 = alpha.unwrap_or(0.1);
797
798    let beta: HashMap<usize, f64> = beta_map.unwrap_or_default();
799    //Initialize the beta vector in case a beta map was not provided
800    let mut beta_v = vec![beta_scalar.unwrap_or(1.0); graph.node_bound()];
801
802    if !beta.is_empty() {
803        // Check if beta contains all node indices
804        for node_index in graph.node_identifiers() {
805            let node = graph.to_index(node_index);
806            if !beta.contains_key(&node) {
807                return Ok(None); // beta_map was provided but did not include all nodes
808            }
809            beta_v[node] = *beta.get(&node).unwrap(); //Initialize the beta vector with the provided values
810        }
811    }
812
813    let tol: f64 = tol.unwrap_or(1e-6);
814    let max_iter = max_iter.unwrap_or(1000);
815
816    let mut x: Vec<f64> = vec![0.; graph.node_bound()];
817    let node_count = graph.node_count();
818    for _ in 0..max_iter {
819        let x_last = x.clone();
820        x = vec![0.; graph.node_bound()];
821        for node_index in graph.node_identifiers() {
822            let node = graph.to_index(node_index);
823            for edge in graph.edges(node_index) {
824                let w = weight_fn(edge)?;
825                let neighbor = edge.target();
826                x[graph.to_index(neighbor)] += x_last[node] * w;
827            }
828        }
829        for node_index in graph.node_identifiers() {
830            let node = graph.to_index(node_index);
831            x[node] = alpha * x[node] + beta_v[node];
832        }
833        if (0..x.len())
834            .map(|node| (x[node] - x_last[node]).abs())
835            .sum::<f64>()
836            < node_count as f64 * tol
837        {
838            // Normalize vector
839            let norm: f64 = x.iter().map(|val| val.powi(2)).sum::<f64>().sqrt();
840            if norm == 0. {
841                return Ok(None);
842            }
843            for v in x.iter_mut() {
844                *v /= norm;
845            }
846
847            return Ok(Some(x));
848        }
849    }
850
851    Ok(None)
852}
853
854#[cfg(test)]
855mod test_eigenvector_centrality {
856
857    use crate::centrality::eigenvector_centrality;
858    use crate::petgraph;
859    use crate::Result;
860
861    macro_rules! assert_almost_equal {
862        ($x:expr, $y:expr, $d:expr) => {
863            if ($x - $y).abs() >= $d {
864                panic!("{} != {} within delta of {}", $x, $y, $d);
865            }
866        };
867    }
868    #[test]
869    fn test_no_convergence() {
870        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
871        let output: Result<Option<Vec<f64>>> =
872            eigenvector_centrality(&g, |_| Ok(1.), Some(0), None);
873        let result = output.unwrap();
874        assert_eq!(None, result);
875    }
876
877    #[test]
878    fn test_undirected_complete_graph() {
879        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([
880            (0, 1),
881            (0, 2),
882            (0, 3),
883            (0, 4),
884            (1, 2),
885            (1, 3),
886            (1, 4),
887            (2, 3),
888            (2, 4),
889            (3, 4),
890        ]);
891        let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(1.), None, None);
892        let result = output.unwrap().unwrap();
893        let expected_value: f64 = (1_f64 / 5_f64).sqrt();
894        let expected_values: Vec<f64> = vec![expected_value; 5];
895        for i in 0..5 {
896            assert_almost_equal!(expected_values[i], result[i], 1e-4);
897        }
898    }
899
900    #[test]
901    fn test_undirected_path_graph() {
902        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
903        let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(1.), None, None);
904        let result = output.unwrap().unwrap();
905        let expected_values: Vec<f64> = vec![0.5, std::f64::consts::FRAC_1_SQRT_2, 0.5];
906        for i in 0..3 {
907            assert_almost_equal!(expected_values[i], result[i], 1e-4);
908        }
909    }
910
911    #[test]
912    fn test_directed_graph() {
913        let g = petgraph::graph::DiGraph::<i32, ()>::from_edges([
914            (0, 1),
915            (0, 2),
916            (1, 3),
917            (2, 1),
918            (2, 4),
919            (3, 1),
920            (3, 4),
921            (3, 5),
922            (4, 5),
923            (4, 6),
924            (4, 7),
925            (5, 7),
926            (6, 0),
927            (6, 4),
928            (6, 7),
929            (7, 5),
930            (7, 6),
931        ]);
932        let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(2.), None, None);
933        let result = output.unwrap().unwrap();
934        let expected_values: Vec<f64> = vec![
935            0.2140437, 0.2009269, 0.1036383, 0.0972886, 0.3113323, 0.4891686, 0.4420605, 0.6016448,
936        ];
937        for i in 0..8 {
938            assert_almost_equal!(expected_values[i], result[i], 1e-4);
939        }
940    }
941}
942
943#[cfg(test)]
944mod test_katz_centrality {
945
946    use crate::centrality::katz_centrality;
947    use crate::petgraph;
948    use crate::Result;
949    use hashbrown::HashMap;
950
951    macro_rules! assert_almost_equal {
952        ($x:expr, $y:expr, $d:expr) => {
953            if ($x - $y).abs() >= $d {
954                panic!("{} != {} within delta of {}", $x, $y, $d);
955            }
956        };
957    }
958    #[test]
959    fn test_no_convergence() {
960        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
961        let output: Result<Option<Vec<f64>>> =
962            katz_centrality(&g, |_| Ok(1.), None, None, None, Some(0), None);
963        let result = output.unwrap();
964        assert_eq!(None, result);
965    }
966
967    #[test]
968    fn test_incomplete_beta() {
969        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
970        let beta_map: HashMap<usize, f64> = [(0, 1.0)].iter().cloned().collect();
971        let output: Result<Option<Vec<f64>>> =
972            katz_centrality(&g, |_| Ok(1.), None, Some(beta_map), None, None, None);
973        let result = output.unwrap();
974        assert_eq!(None, result);
975    }
976
977    #[test]
978    fn test_complete_beta() {
979        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
980        let beta_map: HashMap<usize, f64> =
981            [(0, 0.5), (1, 1.0), (2, 0.5)].iter().cloned().collect();
982        let output: Result<Option<Vec<f64>>> =
983            katz_centrality(&g, |_| Ok(1.), None, Some(beta_map), None, None, None);
984        let result = output.unwrap().unwrap();
985        let expected_values: Vec<f64> =
986            vec![0.4318894504492167, 0.791797325823564, 0.4318894504492167];
987        for i in 0..3 {
988            assert_almost_equal!(expected_values[i], result[i], 1e-4);
989        }
990    }
991
992    #[test]
993    fn test_undirected_complete_graph() {
994        let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([
995            (0, 1),
996            (0, 2),
997            (0, 3),
998            (0, 4),
999            (1, 2),
1000            (1, 3),
1001            (1, 4),
1002            (2, 3),
1003            (2, 4),
1004            (3, 4),
1005        ]);
1006        let output: Result<Option<Vec<f64>>> =
1007            katz_centrality(&g, |_| Ok(1.), Some(0.2), None, Some(1.1), None, None);
1008        let result = output.unwrap().unwrap();
1009        let expected_value: f64 = (1_f64 / 5_f64).sqrt();
1010        let expected_values: Vec<f64> = vec![expected_value; 5];
1011        for i in 0..5 {
1012            assert_almost_equal!(expected_values[i], result[i], 1e-4);
1013        }
1014    }
1015
1016    #[test]
1017    fn test_directed_graph() {
1018        let g = petgraph::graph::DiGraph::<i32, ()>::from_edges([
1019            (0, 1),
1020            (0, 2),
1021            (1, 3),
1022            (2, 1),
1023            (2, 4),
1024            (3, 1),
1025            (3, 4),
1026            (3, 5),
1027            (4, 5),
1028            (4, 6),
1029            (4, 7),
1030            (5, 7),
1031            (6, 0),
1032            (6, 4),
1033            (6, 7),
1034            (7, 5),
1035            (7, 6),
1036        ]);
1037        let output: Result<Option<Vec<f64>>> =
1038            katz_centrality(&g, |_| Ok(1.), None, None, None, None, None);
1039        let result = output.unwrap().unwrap();
1040        let expected_values: Vec<f64> = vec![
1041            0.3135463087489011,
1042            0.3719056758615039,
1043            0.3094350787809586,
1044            0.31527101632646026,
1045            0.3760169058294464,
1046            0.38618584417917906,
1047            0.35465874858087904,
1048            0.38976653416801743,
1049        ];
1050
1051        for i in 0..8 {
1052            assert_almost_equal!(expected_values[i], result[i], 1e-4);
1053        }
1054    }
1055}
1056
1057/// Compute the closeness centrality of each node in the graph.
1058///
1059/// The closeness centrality of a node `u` is the reciprocal of the average
1060/// shortest path distance to `u` over all `n-1` reachable nodes.
1061///
1062/// In the case of a graphs with more than one connected component there is
1063/// an alternative improved formula that calculates the closeness centrality
1064/// as "a ratio of the fraction of actors in the group who are reachable, to
1065/// the average distance".[^WF]
1066/// You can enable this by setting `wf_improved` to `true`.
1067///
1068/// [^WF]: Wasserman, S., & Faust, K. (1994). Social Network Analysis:
1069///     Methods and Applications (Structural Analysis in the Social Sciences).
1070///     Cambridge: Cambridge University Press.
1071///     <https://doi.org/10.1017/CBO9780511815478>
1072///
1073/// This function is multithreaded and will run in parallel if the number
1074/// of nodes in the graph is above the value of ``parallel_threshold``. If the
1075/// function will be running in parallel the env var ``RAYON_NUM_THREADS`` can
1076/// be used to adjust how many threads will be used.
1077///
1078/// # Arguments
1079///
1080/// * `graph` - The graph object to run the algorithm on
1081/// * `wf_improved` - If `true`, scale by the fraction of nodes reachable.
1082/// * `parallel_threshold` - The number of nodes to calculate the betweenness
1083///   centrality in parallel at, if the number of nodes in `graph` is less
1084///   than this value it will run in a single thread. The suggested default to use
1085///   here is `50`.
1086///
1087/// # Example
1088///
1089/// ```rust
1090/// use rustworkx_core::petgraph;
1091/// use rustworkx_core::centrality::closeness_centrality;
1092///
1093/// // Calculate the closeness centrality of Graph
1094/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
1095///     (0, 4), (1, 2), (2, 3), (3, 4), (1, 4)
1096/// ]);
1097/// let output = closeness_centrality(&g, true, 200);
1098/// assert_eq!(
1099///     vec![Some(1./2.), Some(2./3.), Some(4./7.), Some(2./3.), Some(4./5.)],
1100///     output
1101/// );
1102///
1103/// // Calculate the closeness centrality of DiGraph
1104/// let dg = petgraph::graph::DiGraph::<i32, ()>::from_edges(&[
1105///     (0, 4), (1, 2), (2, 3), (3, 4), (1, 4)
1106/// ]);
1107/// let output = closeness_centrality(&dg, true, 200);
1108/// assert_eq!(
1109///     vec![Some(0.), Some(0.), Some(1./4.), Some(1./3.), Some(4./5.)],
1110///     output
1111/// );
1112/// ```
1113pub fn closeness_centrality<G>(
1114    graph: G,
1115    wf_improved: bool,
1116    parallel_threshold: usize,
1117) -> Vec<Option<f64>>
1118where
1119    G: NodeIndexable
1120        + IntoNodeIdentifiers
1121        + GraphBase
1122        + IntoEdges
1123        + Visitable
1124        + NodeCount
1125        + IntoEdgesDirected
1126        + std::marker::Sync,
1127    G::NodeId: Eq + Hash + Send,
1128    G::EdgeId: Eq + Hash + Send,
1129{
1130    let max_index = graph.node_bound();
1131    let mut node_indices: Vec<Option<G::NodeId>> = vec![None; max_index];
1132    graph.node_identifiers().for_each(|node| {
1133        let index = graph.to_index(node);
1134        node_indices[index] = Some(node);
1135    });
1136
1137    let unweighted_shortest_path = |g: Reversed<&G>, s: G::NodeId| -> HashMap<G::NodeId, usize> {
1138        let mut distances = HashMap::new();
1139        let mut bfs = Bfs::new(g, s);
1140        distances.insert(s, 0);
1141        while let Some(node) = bfs.next(g) {
1142            let distance = distances[&node];
1143            for edge in g.edges(node) {
1144                let target = edge.target();
1145                distances.entry(target).or_insert(distance + 1);
1146            }
1147        }
1148        distances
1149    };
1150
1151    let closeness: Vec<Option<f64>> =
1152        CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
1153            .map(|node_s| {
1154                let node_s = node_s?;
1155                let map = unweighted_shortest_path(Reversed(&graph), node_s);
1156                let reachable_nodes_count = map.len();
1157                let dists_sum: usize = map.into_values().sum();
1158                if reachable_nodes_count == 1 {
1159                    return Some(0.0);
1160                }
1161                let mut centrality_s = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64);
1162                if wf_improved {
1163                    let node_count = graph.node_count();
1164                    centrality_s = centrality_s
1165                        .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64);
1166                }
1167                centrality_s
1168            })
1169            .collect();
1170    closeness
1171}
1172
1173/// Compute the weighted closeness centrality of each node in the graph.
1174///
1175/// The weighted closeness centrality is an extension of the standard closeness
1176/// centrality measure where edge weights represent connection strength rather
1177/// than distance. To properly compute shortest paths, weights are inverted
1178/// so that stronger connections correspond to shorter effective distances.
1179/// The algorithm follows the method described by Newman (2001) in analyzing
1180/// weighted graphs.[^Newman]
1181///
1182/// The edges originally represent connection strength between nodes.
1183/// The idea is that if two nodes have a strong connection, the computed
1184/// distance between them should be small (shorter), and vice versa.
1185/// Note that this assume that the graph is modelling a measure of
1186/// connection strength (e.g. trust, collaboration, or similarity).
1187/// If the graph is not modelling a measure of connection strength,
1188/// the function `weight_fn` should invert the weights before calling this
1189/// function, if not it is considered as a logical error.
1190///
1191/// In the case of a graphs with more than one connected component there is
1192/// an alternative improved formula that calculates the closeness centrality
1193/// as "a ratio of the fraction of actors in the group who are reachable, to
1194/// the average distance".[^WF]
1195/// You can enable this by setting `wf_improved` to `true`.
1196///
1197/// [^Newman]: Newman, M. E. J. (2001). Scientific collaboration networks.
1198///     II. Shortest paths, weighted networks, and centrality.
1199///     Physical Review E, 64(1), 016132.
1200///
1201/// [^WF]: Wasserman, S., & Faust, K. (1994). Social Network Analysis:
1202///     Methods and Applications (Structural Analysis in the Social Sciences).
1203///     Cambridge: Cambridge University Press.
1204///     <https://doi.org/10.1017/CBO9780511815478>
1205///
1206/// This function is multithreaded and will run in parallel if the number
1207/// of nodes in the graph is above the value of ``parallel_threshold``. If the
1208/// function will be running in parallel the env var ``RAYON_NUM_THREADS`` can
1209/// be used to adjust how many threads will be used.
1210///
1211/// # Arguments
1212/// * `graph` - The graph object to run the algorithm on
1213/// * `wf_improved` - If `true`, scale by the fraction of nodes reachable.
1214/// * `weight_fn` - An input callable that will be passed the
1215///   `ReversedEdgeReference<<G as IntoEdgeReferences>::EdgeRef>` for
1216///   an edge in the graph and is expected to return a `f64` of
1217///   the weight of that edge.
1218/// * `parallel_threshold` - The number of nodes to calculate the betweenness
1219///   centrality in parallel at, if the number of nodes in `graph` is less
1220///   than this value it will run in a single thread. The suggested default to use
1221///   here is `50`.
1222///
1223/// # Example
1224///
1225/// ```rust
1226/// use rustworkx_core::petgraph;
1227/// use rustworkx_core::centrality::newman_weighted_closeness_centrality;
1228/// use crate::rustworkx_core::petgraph::visit::EdgeRef;
1229///
1230/// // Calculate the closeness centrality of Graph
1231/// let g = petgraph::graph::UnGraph::<i32, f64>::from_edges(&[
1232///     (0, 1, 0.7), (1, 2, 0.2), (2, 3, 0.5),
1233/// ]);
1234/// let output = newman_weighted_closeness_centrality(&g, false, |x| *x.weight(), 200);
1235/// assert!(output[1] > output[3]);
1236///
1237/// // Calculate the closeness centrality of DiGraph
1238/// let g = petgraph::graph::DiGraph::<i32, f64>::from_edges(&[
1239///     (0, 1, 0.7), (1, 2, 0.2), (2, 3, 0.5),
1240/// ]);
1241/// let output = newman_weighted_closeness_centrality(&g, false, |x| *x.weight(), 200);
1242/// assert!(output[1] > output[3]);
1243/// ```
1244pub fn newman_weighted_closeness_centrality<G, F>(
1245    graph: G,
1246    wf_improved: bool,
1247    weight_fn: F,
1248    parallel_threshold: usize,
1249) -> Vec<Option<f64>>
1250where
1251    G: NodeIndexable
1252        + IntoNodeIdentifiers
1253        + GraphBase
1254        + IntoEdges
1255        + Visitable
1256        + NodeCount
1257        + IntoEdgesDirected
1258        + std::marker::Sync,
1259    G::NodeId: Eq + Hash + Send,
1260    G::EdgeId: Eq + Hash + Send,
1261    F: Fn(ReversedEdgeReference<<G as IntoEdgeReferences>::EdgeRef>) -> f64 + Sync,
1262{
1263    // The edges originally represent `connection strength` between nodes.
1264    // As shown in the paper, the weight of the edges should be inverted to
1265    // ensure that stronger ties correspond to shorter effective distances.
1266    // The idea is that if two nodes have a strong connection, the computed
1267    // distance between them should be small (shorter), and vice versa.
1268    //
1269    // Note that this assume that the graph is modelling a measure of
1270    // connection strength (e.g. trust, collaboration, or similarity).
1271    // If the graph is not modelling a measure of connection strength,
1272    // the user should invert the weights before calling this function,
1273    // if not it is considered as a logical error.
1274    let inverted_weight_fn =
1275        |x: ReversedEdgeReference<<G as IntoEdgeReferences>::EdgeRef>| 1.0 / weight_fn(x);
1276
1277    let max_index = graph.node_bound();
1278    let mut node_indices: Vec<Option<G::NodeId>> = vec![None; max_index];
1279    graph.node_identifiers().for_each(|node| {
1280        let index = graph.to_index(node);
1281        node_indices[index] = Some(node);
1282    });
1283
1284    let closeness: Vec<Option<f64>> =
1285        CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
1286            .map(|node_s| {
1287                let node_s = node_s?;
1288                let map = dijkstra(Reversed(&graph), node_s, None, &inverted_weight_fn);
1289                let reachable_nodes_count = map.len();
1290                let dists_sum: f64 = map.into_values().sum();
1291                if reachable_nodes_count == 1 {
1292                    return Some(0.0);
1293                }
1294                let mut centrality_s = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64);
1295                if wf_improved {
1296                    let node_count = graph.node_count();
1297                    centrality_s = centrality_s
1298                        .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64);
1299                }
1300                centrality_s
1301            })
1302            .collect();
1303    closeness
1304}
1305
1306#[cfg(test)]
1307mod test_newman_weighted_closeness_centrality {
1308    use crate::centrality::closeness_centrality;
1309
1310    use super::newman_weighted_closeness_centrality;
1311    use petgraph::visit::EdgeRef;
1312
1313    macro_rules! assert_almost_equal {
1314        ($x:expr, $y:expr, $d:expr) => {
1315            if ($x - $y).abs() >= $d {
1316                panic!("{} != {} within delta of {}", $x, $y, $d);
1317            }
1318        };
1319    }
1320
1321    macro_rules! assert_almost_equal_iter {
1322        ($expected:expr, $computed:expr, $tolerance:expr) => {
1323            for (&expected, &computed) in $expected.iter().zip($computed.iter()) {
1324                assert_almost_equal!(expected.unwrap(), computed.unwrap(), $tolerance);
1325            }
1326        };
1327    }
1328
1329    #[test]
1330    fn test_weighted_closeness_graph() {
1331        let test_case = |parallel_threshold: usize| {
1332            let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1333                (0, 1, 1.0),
1334                (1, 2, 1.0),
1335                (2, 3, 1.0),
1336                (3, 4, 1.0),
1337                (4, 5, 1.0),
1338                (5, 6, 1.0),
1339            ]);
1340            let classic_closeness = closeness_centrality(&g, false, parallel_threshold);
1341            let weighted_closeness = newman_weighted_closeness_centrality(
1342                &g,
1343                false,
1344                |x| *x.weight(),
1345                parallel_threshold,
1346            );
1347
1348            assert_eq!(classic_closeness, weighted_closeness);
1349        };
1350        test_case(200); // sequential
1351        test_case(1); // parallel
1352    }
1353
1354    #[test]
1355    fn test_the_same_as_closeness_centrality_when_weights_are_1_not_improved_digraph() {
1356        let test_case = |parallel_threshold: usize| {
1357            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1358                (0, 1, 1.0),
1359                (1, 2, 1.0),
1360                (2, 3, 1.0),
1361                (3, 4, 1.0),
1362                (4, 5, 1.0),
1363                (5, 6, 1.0),
1364            ]);
1365            let classic_closeness = closeness_centrality(&g, false, parallel_threshold);
1366            let weighted_closeness = newman_weighted_closeness_centrality(
1367                &g,
1368                false,
1369                |x| *x.weight(),
1370                parallel_threshold,
1371            );
1372
1373            assert_eq!(classic_closeness, weighted_closeness);
1374        };
1375        test_case(200); // sequential
1376        test_case(1); // parallel
1377    }
1378
1379    #[test]
1380    fn test_the_same_as_closeness_centrality_when_weights_are_1_improved_digraph() {
1381        let test_case = |parallel_threshold: usize| {
1382            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1383                (0, 1, 1.0),
1384                (1, 2, 1.0),
1385                (2, 3, 1.0),
1386                (3, 4, 1.0),
1387                (4, 5, 1.0),
1388                (5, 6, 1.0),
1389            ]);
1390            let classic_closeness = closeness_centrality(&g, true, parallel_threshold);
1391            let weighted_closeness =
1392                newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1393
1394            assert_eq!(classic_closeness, weighted_closeness);
1395        };
1396        test_case(200); // sequential
1397        test_case(1); // parallel
1398    }
1399
1400    #[test]
1401    fn test_weighted_closeness_two_connected_components_not_improved_digraph() {
1402        let test_case = |parallel_threshold: usize| {
1403            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1404                (0, 1, 1.0),
1405                (1, 2, 0.5),
1406                (2, 3, 0.25),
1407                (4, 5, 1.0),
1408                (5, 6, 0.5),
1409                (6, 7, 0.25),
1410            ]);
1411            let c = newman_weighted_closeness_centrality(
1412                &g,
1413                false,
1414                |x| *x.weight(),
1415                parallel_threshold,
1416            );
1417            let result = [
1418                Some(0.0),
1419                Some(1.0),
1420                Some(0.4),
1421                Some(0.176470),
1422                Some(0.0),
1423                Some(1.0),
1424                Some(0.4),
1425                Some(0.176470),
1426            ];
1427
1428            assert_almost_equal_iter!(result, c, 1e-4);
1429        };
1430        test_case(200); // sequential
1431        test_case(1); // parallel
1432    }
1433
1434    #[test]
1435    fn test_weighted_closeness_two_connected_components_improved_digraph() {
1436        let test_case = |parallel_threshold: usize| {
1437            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1438                (0, 1, 1.0),
1439                (1, 2, 0.5),
1440                (2, 3, 0.25),
1441                (4, 5, 1.0),
1442                (5, 6, 0.5),
1443                (6, 7, 0.25),
1444            ]);
1445            let c =
1446                newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1447            let result = [
1448                Some(0.0),
1449                Some(0.14285714),
1450                Some(0.11428571),
1451                Some(0.07563025),
1452                Some(0.0),
1453                Some(0.14285714),
1454                Some(0.11428571),
1455                Some(0.07563025),
1456            ];
1457
1458            assert_almost_equal_iter!(result, c, 1e-4);
1459        };
1460        test_case(200); // sequential
1461        test_case(1); // parallel
1462    }
1463
1464    #[test]
1465    fn test_weighted_closeness_two_connected_components_improved_different_cardinality_digraph() {
1466        let test_case = |parallel_threshold: usize| {
1467            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1468                (0, 1, 1.0),
1469                (1, 2, 0.5),
1470                (2, 3, 0.25),
1471                (4, 5, 1.0),
1472                (5, 6, 0.5),
1473                (6, 7, 0.25),
1474                (7, 8, 0.125),
1475            ]);
1476            let c =
1477                newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1478            let result = [
1479                Some(0.0),
1480                Some(0.125),
1481                Some(0.1),
1482                Some(0.06617647),
1483                Some(0.0),
1484                Some(0.125),
1485                Some(0.1),
1486                Some(0.06617647),
1487                Some(0.04081632),
1488            ];
1489
1490            assert_almost_equal_iter!(result, c, 1e-4);
1491        };
1492        test_case(200); // sequential
1493        test_case(1); // parallel
1494    }
1495
1496    #[test]
1497    fn test_weighted_closeness_small_ungraph() {
1498        let test_case = |parallel_threshold: usize| {
1499            let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1500                (0, 1, 0.7),
1501                (1, 2, 0.2),
1502                (2, 3, 0.5),
1503            ]);
1504            let c = newman_weighted_closeness_centrality(
1505                &g,
1506                false,
1507                |x| *x.weight(),
1508                parallel_threshold,
1509            );
1510            let result = [
1511                Some(0.1842105),
1512                Some(0.2234042),
1513                Some(0.2234042),
1514                Some(0.1721311),
1515            ];
1516
1517            assert_almost_equal_iter!(result, c, 1e-4);
1518        };
1519        test_case(200); // sequential
1520        test_case(1); // parallel
1521    }
1522    #[test]
1523    fn test_weighted_closeness_small_digraph() {
1524        let test_case = |parallel_threshold: usize| {
1525            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1526                (0, 1, 0.7),
1527                (1, 2, 0.2),
1528                (2, 3, 0.5),
1529            ]);
1530            let c = newman_weighted_closeness_centrality(
1531                &g,
1532                false,
1533                |x| *x.weight(),
1534                parallel_threshold,
1535            );
1536            let result = [Some(0.0), Some(0.7), Some(0.175), Some(0.172131)];
1537
1538            assert_almost_equal_iter!(result, c, 1e-4);
1539        };
1540        test_case(200); // sequential
1541        test_case(1); // parallel
1542    }
1543
1544    #[test]
1545    fn test_weighted_closeness_many_to_one_connected_digraph() {
1546        let test_case = |parallel_threshold: usize| {
1547            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1548                (1, 0, 0.1),
1549                (2, 0, 0.1),
1550                (3, 0, 0.1),
1551                (4, 0, 0.1),
1552                (5, 0, 0.1),
1553                (6, 0, 0.1),
1554                (7, 0, 0.1),
1555                (0, 8, 1.0),
1556            ]);
1557            let c = newman_weighted_closeness_centrality(
1558                &g,
1559                false,
1560                |x| *x.weight(),
1561                parallel_threshold,
1562            );
1563            let result = [
1564                Some(0.1),
1565                Some(0.0),
1566                Some(0.0),
1567                Some(0.0),
1568                Some(0.0),
1569                Some(0.0),
1570                Some(0.0),
1571                Some(0.0),
1572                Some(0.10256),
1573            ];
1574
1575            assert_almost_equal_iter!(result, c, 1e-4);
1576        };
1577        test_case(200); // sequential
1578        test_case(1); // parallel
1579    }
1580
1581    #[test]
1582    fn test_weighted_closeness_many_to_one_connected_ungraph() {
1583        let test_case = |parallel_threshold: usize| {
1584            let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1585                (1, 0, 0.1),
1586                (2, 0, 0.1),
1587                (3, 0, 0.1),
1588                (4, 0, 0.1),
1589                (5, 0, 0.1),
1590                (6, 0, 0.1),
1591                (7, 0, 0.1),
1592                (0, 8, 1.0),
1593            ]);
1594            let c = newman_weighted_closeness_centrality(
1595                &g,
1596                false,
1597                |x| *x.weight(),
1598                parallel_threshold,
1599            );
1600            let result = [
1601                Some(0.112676056),
1602                Some(0.056737588),
1603                Some(0.056737588),
1604                Some(0.056737588),
1605                Some(0.056737588),
1606                Some(0.056737588),
1607                Some(0.056737588),
1608                Some(0.056737588),
1609                Some(0.102564102),
1610            ];
1611
1612            assert_almost_equal_iter!(result, c, 1e-4);
1613        };
1614        test_case(200); // sequential
1615        test_case(1); // parallel
1616    }
1617
1618    #[test]
1619    fn test_weighted_closeness_many_to_one_not_connected_2_digraph() {
1620        let test_case = |parallel_threshold: usize| {
1621            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1622                (1, 0, 0.1),
1623                (2, 0, 0.1),
1624                (3, 0, 0.1),
1625                (4, 0, 0.1),
1626                (5, 0, 0.1),
1627                (6, 0, 0.1),
1628                (7, 0, 0.1),
1629                (1, 7, 1.0),
1630            ]);
1631            let c = newman_weighted_closeness_centrality(
1632                &g,
1633                false,
1634                |x| *x.weight(),
1635                parallel_threshold,
1636            );
1637            let result = [
1638                Some(0.1),
1639                Some(0.0),
1640                Some(0.0),
1641                Some(0.0),
1642                Some(0.0),
1643                Some(0.0),
1644                Some(0.0),
1645                Some(1.0),
1646            ];
1647
1648            assert_eq!(result, *c);
1649        };
1650        test_case(200); // sequential
1651        test_case(1); // parallel
1652    }
1653
1654    #[test]
1655    fn test_weighted_closeness_many_to_one_not_connected_1_digraph() {
1656        let test_case = |parallel_threshold: usize| {
1657            let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1658                (1, 0, 0.1),
1659                (2, 0, 0.1),
1660                (3, 0, 0.1),
1661                (4, 0, 0.1),
1662                (5, 0, 0.1),
1663                (6, 0, 0.1),
1664                (7, 0, 0.1),
1665                (8, 7, 1.0),
1666            ]);
1667            let c = newman_weighted_closeness_centrality(
1668                &g,
1669                false,
1670                |x| *x.weight(),
1671                parallel_threshold,
1672            );
1673            let result = [
1674                Some(0.098765),
1675                Some(0.0),
1676                Some(0.0),
1677                Some(0.0),
1678                Some(0.0),
1679                Some(0.0),
1680                Some(0.0),
1681                Some(1.0),
1682                Some(0.0),
1683            ];
1684
1685            assert_almost_equal_iter!(result, c, 1e-4);
1686        };
1687        test_case(200); // sequential
1688        test_case(1); // parallel
1689    }
1690}