diff --git a/Cargo.lock b/Cargo.lock index f1f4cc636a9011212f306fa3f575d2f00aaa3cbf..0e06666e0c76a58cabcf2001a1a516943092eee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ [[package]] name = "duniter-wotb" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/wotb/Cargo.toml b/wotb/Cargo.toml index b37f08c63dca1e32ea16e19de9c27fc8343f536a..2977ee583d4aff8441d85394d8daa3e5377df931 100644 --- a/wotb/Cargo.toml +++ b/wotb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duniter-wotb" -version = "0.7.0" +version = "0.7.1" authors = ["nanocryk <nanocryk@duniter.org>", "elois <elois@duniter.org>"] description = "Makes Web of Trust computations for the Duniter project." repository = "https://git.duniter.org/nodes/rust/duniter-rs" diff --git a/wotb/lib.rs b/wotb/lib.rs index 1a6fed792e1465fd3cb9491b7730cea7b86827ca..21767540d87c164dc6de54cdeadbfd35cf174c9d 100644 --- a/wotb/lib.rs +++ b/wotb/lib.rs @@ -828,5 +828,39 @@ mod tests { outdistanced: false, },) ); + + // Test centralities computation in g1_genesis wot + 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); + let mut centralities = vec![0; wot_size]; + for i in 0..wot_size { + for j in 0..wot_size { + let paths = wot3.get_paths(NodeId(i), NodeId(j), 5); + let mut intermediate_members: Vec<NodeId> = Vec::new(); + for path in paths { + if path.len() > 2 { + for node_id in &path[1..path.len() - 1] { + if !intermediate_members.contains(node_id) { + intermediate_members.push(*node_id); + } + } + } + } + let centralities_copy = centralities.clone(); + for node_id in intermediate_members { + let centrality = ¢ralities_copy[node_id.0]; + if let Some(tmp) = centralities.get_mut(node_id.0) { + *tmp = *centrality + 1; + } + } + } + } + let mut relative_centralities = Vec::with_capacity(wot_size); + for centrality in centralities { + relative_centralities.push((centrality * 100_000 / oriented_couples_count) as usize); + } + assert_eq!(relative_centralities.len(), 59); } } diff --git a/wotb/rusty.rs b/wotb/rusty.rs index 9429690ccba6a7312b76dbb24ecb78c2c03f9cd5..8506ed13dd8d295b6f6afdccd73dcf5f34a984c8 100644 --- a/wotb/rusty.rs +++ b/wotb/rusty.rs @@ -219,33 +219,67 @@ impl WebOfTrust for RustyWebOfTrust { } fn get_paths(&self, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>> { - if from == to { - vec![vec![to]] - } else if k_max > 0 { - self.nodes[to.0] - .links_source - .par_iter() - .map(|&source| self.get_paths(from, source, k_max - 1)) - .map(|paths| { - paths - .iter() - .map(|path| { - let mut path = path.clone(); - path.push(to); - path - }) - .collect::<Vec<Vec<NodeId>>>() - }) - .reduce( - || vec![], - |mut acc, mut paths| { - acc.append(&mut paths); - acc - }, - ) - } else { - vec![] + // 1. We explore the k_max area around `to`, and only remember backward + // links of the smallest distance. + + // Stores for each node its distance to `to` node and its backward links. + // By default all nodes are out of range (`k_max + 1`) and links are known. + let mut graph: Vec<(u32, Vec<usize>)> = + self.nodes.iter().map(|_| (k_max + 1, vec![])).collect(); + // `to` node is at distance 0, and have no backward links. + graph[to.0] = (0, vec![]); + // Explored zone border. + let mut border = HashSet::new(); + border.insert(to.0); + + for distance in 1..(k_max + 1) { + let mut next_border = HashSet::new(); + + for node in border { + for source in &self.nodes[node].links_source { + if graph[source.0].0 > distance { + // shorter path, we replace + graph[source.0] = (distance, vec![node]); + next_border.insert(source.0); + } else if graph[source.0].0 == distance { + // same length, we combine + graph[source.0].1.push(node); + next_border.insert(source.0); + } + } + } + + border = next_border; + } + + // 2. If `from` is found, we follow the backward links and build paths. + // For each path, we look at the last element sources and build new paths with them. + let mut paths = vec![vec![from]]; + + for _ in 1..(k_max + 1) { + let mut new_paths = vec![]; + + for path in &paths { + let node = path.last().unwrap(); + + if node == &to { + // If path is complete, we keep it. + new_paths.push(path.clone()) + } else { + // If not complete we comlete paths + let sources = &graph[node.0]; + for source in &sources.1 { + let mut new_path = path.clone(); + new_path.push(NodeId(*source)); + new_paths.push(new_path); + } + } + } + + paths = new_paths; } + + paths } fn compute_distance(&self, params: WotDistanceParameters) -> Option<WotDistance> {