diff --git a/wotb/lib.rs b/wotb/lib.rs index 5169d7ca657780ab9bb48eab7e02f322c2f284d6..459355b995bed10684e87b6f6b204a4a7cd2f93b 100644 --- a/wotb/lib.rs +++ b/wotb/lib.rs @@ -27,9 +27,11 @@ //! [js-tests]: https://github.com/duniter/wotb/blob/master/wotcpp/webOfTrust.cpp #![cfg_attr(feature = "strict", deny(warnings))] -#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, - trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, - unused_qualifications)] +#![deny( + missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, + trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, + unused_qualifications +)] extern crate bincode; extern crate byteorder; @@ -47,9 +49,10 @@ pub use data::{NodeId, WebOfTrust}; mod tests { use super::*; use data::*; + use operations::centrality::*; use operations::distance::*; - use operations::path::*; use operations::file::*; + use operations::path::*; /// Test translated from https://github.com/duniter/wotb/blob/master/tests/test.js /// @@ -59,8 +62,9 @@ mod tests { where W: WebOfTrust + Sync, { - let path_finder = RustyPathFinder {}; + let centralities_calculator = UlrikBrandesCentralityCalculator {}; let distance_calculator = RustyDistanceCalculator {}; + let path_finder = RustyPathFinder {}; let mut wot = W::new(3); // should have an initial size of 0 @@ -561,8 +565,31 @@ mod tests { },) ); - // Test centralities computation in g1_genesis wot - let wot_size = wot3.size(); + // Test betweenness centralities computation in g1_genesis wot + let centralities = centralities_calculator.betweenness_centralities(&wot3); + assert_eq!(centralities.len(), 59); + assert_eq!( + centralities, + vec![ + 148, 30, 184, 11, 60, 51, 40, 115, 24, 140, 47, 69, 16, 34, 94, 126, 151, 0, 34, + 133, 20, 103, 38, 144, 73, 523, 124, 23, 47, 17, 9, 64, 77, 281, 6, 105, 54, 0, + 111, 21, 6, 2, 0, 1, 47, 59, 28, 236, 0, 0, 0, 0, 60, 6, 0, 1, 8, 33, 169, + ] + ); + + // Test stress centralities computation in g1_genesis wot + let stress_centralities = centralities_calculator.stress_centralities(&wot3); + assert_eq!(stress_centralities.len(), 59); + assert_eq!( + stress_centralities, + vec![ + 848, 240, 955, 80, 416, 203, 290, 645, 166, 908, 313, 231, 101, 202, 487, 769, 984, + 0, 154, 534, 105, 697, 260, 700, 496, 1726, 711, 160, 217, 192, 89, 430, 636, 1276, + 41, 420, 310, 0, 357, 125, 50, 15, 0, 12, 275, 170, 215, 1199, 0, 0, 0, 0, 201, 31, + 0, 9, 55, 216, 865, + ] + ); + /*let wot_size = wot3.size(); let members_count = wot3.get_enabled().len() as u64; assert_eq!(members_count, 59); let oriented_couples_count: u64 = members_count * (members_count - 1); @@ -593,6 +620,6 @@ mod tests { for centrality in centralities { relative_centralities.push((centrality * 100_000 / oriented_couples_count) as usize); } - assert_eq!(relative_centralities.len(), 59); + assert_eq!(relative_centralities.len(), 59);*/ } } diff --git a/wotb/operations/centrality.rs b/wotb/operations/centrality.rs new file mode 100644 index 0000000000000000000000000000000000000000..a9a6d69f94c081d94e1db911de94879aff64b443 --- /dev/null +++ b/wotb/operations/centrality.rs @@ -0,0 +1,141 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide a trait and implementations to find paths between nodes. + +use data::NodeId; +use data::WebOfTrust; +use std::collections::{HashMap, VecDeque}; + +/// Find paths between 2 nodes of a `WebOfTrust`. +pub trait CentralitiesCalculator<T: WebOfTrust> { + /// Compute betweenness centrality of all members. + fn betweenness_centralities(&self, wot: &T) -> Vec<u64>; + /// Compute stress centrality of all members. + fn stress_centralities(&self, wot: &T) -> Vec<u64>; +} + +/// A new "rusty-er" implementation of `WoT` path finding. +#[derive(Debug, Clone, Copy)] +pub struct UlrikBrandesCentralityCalculator; + +impl<T: WebOfTrust> CentralitiesCalculator<T> for UlrikBrandesCentralityCalculator { + fn betweenness_centralities(&self, wot: &T) -> Vec<u64> { + let wot_size = wot.size(); + let mut centralities = vec![0.0; wot_size]; + let enabled_nodes = wot.get_enabled(); + + // The source of any path belongs to enabled_nodes + for s in enabled_nodes.clone() { + let mut stack: Vec<NodeId> = Vec::with_capacity(wot_size); + let mut paths: HashMap<NodeId, Vec<NodeId>> = HashMap::with_capacity(wot_size); + let mut sigma = vec![0.0; wot_size]; + let mut d: Vec<isize> = vec![-1; wot_size]; + let mut q: VecDeque<NodeId> = VecDeque::with_capacity(wot_size); + + sigma[s.0] = 1.0; + d[s.0] = 0; + q.push_back(s); + while !q.is_empty() { + let v = q.pop_front().unwrap(); + stack.push(v); + for w in wot.get_links_source(v).expect("v don't have any source !") { + // w found for the first time ? + if d[w.0] < 0 { + q.push_back(w); + d[w.0] = d[v.0] + 1; + } + // Shortest path to w via v + if d[w.0] == d[v.0] + 1 { + sigma[w.0] += sigma[v.0]; + paths.entry(w).or_insert(vec![]).push(v); + } + } + } + let mut delta = vec![0.0; wot_size]; + // stack returns vertices in order of non-increasing distance from s + while !stack.is_empty() { + let w = stack.pop().unwrap(); + if paths.contains_key(&w) { + for v in paths.get(&w).expect("Not found w in p !") { + if enabled_nodes.contains(&w) { + delta[v.0] += (sigma[v.0] / sigma[w.0]) * (1.0 + delta[w.0]); + } else { + // If w not in enabled_nodes, no path can end at w + delta[v.0] += (sigma[v.0] / sigma[w.0]) * delta[w.0]; + } + } + } + if w != s { + centralities[w.0] += delta[w.0]; + } + } + } + centralities.into_iter().map(|c| c as u64).collect() + } + fn stress_centralities(&self, wot: &T) -> Vec<u64> { + let wot_size = wot.size(); + let mut centralities = vec![0.0; wot_size]; + let enabled_nodes = wot.get_enabled(); + + // The source of any path belongs to enabled_nodes + for s in enabled_nodes.clone() { + let mut stack: Vec<NodeId> = Vec::with_capacity(wot_size); + let mut paths: HashMap<NodeId, Vec<NodeId>> = HashMap::with_capacity(wot_size); + let mut sigma = vec![0.0; wot_size]; + let mut d: Vec<isize> = vec![-1; wot_size]; + let mut q: VecDeque<NodeId> = VecDeque::with_capacity(wot_size); + + sigma[s.0] = 1.0; + d[s.0] = 0; + q.push_back(s); + while !q.is_empty() { + let v = q.pop_front().unwrap(); + stack.push(v); + for w in wot.get_links_source(v).expect("v don't have any source !") { + // w found for the first time ? + if d[w.0] < 0 { + q.push_back(w); + d[w.0] = d[v.0] + 1; + } + // Shortest path to w via v + if d[w.0] == d[v.0] + 1 { + sigma[w.0] = sigma[w.0] + sigma[v.0]; + paths.entry(w).or_insert(vec![]).push(v); + } + } + } + let mut delta = vec![0.0; wot_size]; + // stack returns vertices in order of non-increasing distance from s + while !stack.is_empty() { + let w = stack.pop().unwrap(); + if paths.contains_key(&w) { + for v in paths.get(&w).expect("Not found w in p !") { + if enabled_nodes.contains(&w) { + delta[v.0] += sigma[v.0] * (1.0 + (delta[w.0] / sigma[w.0])); + } else { + // If w not in enabled_nodes, no path can end at w + delta[v.0] += sigma[v.0] * (delta[w.0] / sigma[w.0]); + } + } + } + if w != s { + centralities[w.0] += delta[w.0]; + } + } + } + centralities.into_iter().map(|c| c as u64).collect() + } +} diff --git a/wotb/operations/mod.rs b/wotb/operations/mod.rs index 2bd2782e2ec65ae636df61d6dde65cb4b055fccb..bc9564190043d15cecbbfc8c2ecdecd98dff0857 100644 --- a/wotb/operations/mod.rs +++ b/wotb/operations/mod.rs @@ -15,6 +15,7 @@ //! Provide operation traits and implementations on `WebOfTrust` objects. -pub mod path; +pub mod centrality; pub mod distance; pub mod file; +pub mod path;