From e018f2054d424c45d6af3f72885dd5804534d540 Mon Sep 17 00:00:00 2001 From: ljeub-pometry <97447091+ljeub-pometry@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:41:43 +0100 Subject: [PATCH] Add distance from starting node for in- and out-components (#1877) * add distances to out-component * add distances to in-component --- python/python/raphtory/__init__.pyi | 22 +++++-- .../python/raphtory/algorithms/__init__.pyi | 4 +- .../tests/graphql/edit_graph/test_graphql.py | 21 +------ python/tests/test_algorithms.py | 8 +-- raphtory-graphql/src/model/graph/node.rs | 10 +-- .../algorithms/components/in_components.rs | 61 +++++++++++-------- .../algorithms/components/out_components.rs | 59 +++++++++++------- raphtory/src/python/packages/algorithms.rs | 8 +-- 8 files changed, 110 insertions(+), 83 deletions(-) diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi index 5a50e12a4..8a143329e 100644 --- a/python/python/raphtory/__init__.pyi +++ b/python/python/raphtory/__init__.pyi @@ -432,9 +432,11 @@ class Edge(object): """ def explode(self): - """Explodes an edge and returns all instances it had been updated as seperate edges""" + """Explodes returns an edge object for each update within the original edge.""" + + def explode_layers(self): + """Explode layers returns an edge object for each layer within the original edge. These new edge object contains only updates from respective layers.""" - def explode_layers(self): ... def has_layer(self, name: str): """ Check if Edge has the layer `"name"` @@ -908,9 +910,11 @@ class Edges(object): """ def explode(self): - """Explodes an edge and returns all instances it had been updated as seperate edges""" + """Explodes returns an edge object for each update within the original edge.""" + + def explode_layers(self): + """Explode layers returns an edge object for each layer within the original edge. These new edge object contains only updates from respective layers.""" - def explode_layers(self): ... def has_layer(self, name: str): """ Check if Edges has the layer `"name"` @@ -1965,6 +1969,16 @@ class GraphView(object): GraphView """ + def cache_view(self): + """ + Applies the filters to the graph and retains the node ids and the edge ids + in the graph that satisfy the filters + creates bitsets per layer for nodes and edges + + Returns: + MaskedGraph: Returns the masked graph + """ + def count_edges(self) -> int: """ Number of edges in the graph diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi index c98f4dbbb..84a1c21c7 100644 --- a/python/python/raphtory/algorithms/__init__.pyi +++ b/python/python/raphtory/algorithms/__init__.pyi @@ -575,7 +575,7 @@ def min_out_degree(g: GraphView): int : value of the smallest outdegree """ -def out_component(node: Node): +def out_component(node: Node) -> NodeStateUsize: """ Out component -- Finding the "out-component" of a node in a directed graph involves identifying all nodes that can be reached following only outgoing edges. @@ -583,7 +583,7 @@ def out_component(node: Node): node (Node) : The node whose out-component we wish to calculate Returns: - An array containing the Nodes within the given nodes out-component + NodeStateUsize: A NodeState mapping the nodes in the out-component to their distance from the starting node. """ def out_components(g: GraphView): diff --git a/python/tests/graphql/edit_graph/test_graphql.py b/python/tests/graphql/edit_graph/test_graphql.py index ba863c99e..047f5f1c6 100644 --- a/python/tests/graphql/edit_graph/test_graphql.py +++ b/python/tests/graphql/edit_graph/test_graphql.py @@ -633,24 +633,9 @@ def test_edge_id(): "graph": { "edges": { "list": [ - { - "id": [ - "ben", - "shivam" - ] - }, - { - "id": [ - "oogway", - "po" - ] - }, - { - "id": [ - "po", - "ben" - ] - } + {"id": ["ben", "shivam"]}, + {"id": ["oogway", "po"]}, + {"id": ["po", "ben"]}, ] } } diff --git a/python/tests/test_algorithms.py b/python/tests/test_algorithms.py index ba90487ab..e67666f79 100644 --- a/python/tests/test_algorithms.py +++ b/python/tests/test_algorithms.py @@ -69,10 +69,10 @@ def test_in_component(): actual = algorithms.in_component(g.node(3)) correct = [g.node(7), g.node(1)] - assert set(actual) == set(correct) + assert set(actual.nodes()) == set(correct) actual = algorithms.in_component(g.node(3).window(1, 6)) correct = [g.node(1)] - assert set(actual) == set(correct) + assert set(actual.nodes()) == set(correct) def test_out_components(): @@ -104,10 +104,10 @@ def test_out_component(): actual = algorithms.out_component(g.node(3)) correct = [g.node(4), g.node(5), g.node(6)] - assert set(actual) == set(correct) + assert set(actual.nodes()) == set(correct) actual = algorithms.out_component(g.node(4).at(4)) correct = [g.node(5)] - assert set(actual) == set(correct) + assert set(actual.nodes()) == set(correct) def test_empty_algo(): diff --git a/raphtory-graphql/src/model/graph/node.rs b/raphtory-graphql/src/model/graph/node.rs index 947fc30db..a393414e3 100644 --- a/raphtory-graphql/src/model/graph/node.rs +++ b/raphtory-graphql/src/model/graph/node.rs @@ -8,7 +8,9 @@ use raphtory::{ api::{properties::dyn_props::DynProperties, view::*}, graph::node::NodeView, }, + prelude::NodeStateOps, }; + #[derive(ResolvedObject)] pub(crate) struct Node { pub(crate) vv: NodeView, @@ -172,15 +174,15 @@ impl Node { async fn in_component(&self) -> Vec { in_component(self.vv.clone()) - .iter() - .map(|n| n.clone().into()) + .nodes() + .map(|n| n.cloned().into()) .collect() } async fn out_component(&self) -> Vec { out_component(self.vv.clone()) - .iter() - .map(|n| n.clone().into()) + .nodes() + .map(|n| n.cloned().into()) .collect() } diff --git a/raphtory/src/algorithms/components/in_components.rs b/raphtory/src/algorithms/components/in_components.rs index 473594de1..74833fcc3 100644 --- a/raphtory/src/algorithms/components/in_components.rs +++ b/raphtory/src/algorithms/components/in_components.rs @@ -5,7 +5,10 @@ use crate::{ algorithms::algorithm_result::AlgorithmResult, core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ - api::view::{NodeViewOps, StaticGraphViewOps}, + api::{ + state::{Index, NodeState}, + view::{NodeViewOps, StaticGraphViewOps}, + }, graph::node::NodeView, task::{ context::Context, @@ -16,7 +19,8 @@ use crate::{ }, prelude::GraphViewOps, }; -use std::collections::HashSet; +use itertools::Itertools; +use std::collections::{hash_map::Entry, HashMap, HashSet}; #[derive(Clone, Debug, Default)] struct InState { @@ -101,31 +105,38 @@ where /// /// Returns: /// -/// A Vec containing the Nodes within the given nodes in-component +/// The nodes within the given nodes in-component and their distances from the starting node. /// -pub fn in_component<'graph, G: GraphViewOps<'graph>>(node: NodeView) -> Vec> { - let mut in_components = HashSet::new(); +pub fn in_component<'graph, G: GraphViewOps<'graph>>( + node: NodeView, +) -> NodeState<'graph, usize, G> { + let mut in_components = HashMap::new(); let mut to_check_stack = Vec::new(); node.in_neighbours().iter().for_each(|node| { let id = node.node; - if in_components.insert(id) { - to_check_stack.push(id); - } + in_components.insert(id, 1usize); + to_check_stack.push((id, 1usize)); }); - while let Some(neighbour_id) = to_check_stack.pop() { + while let Some((neighbour_id, d)) = to_check_stack.pop() { + let d = d + 1; if let Some(neighbour) = &node.graph.node(neighbour_id) { neighbour.in_neighbours().iter().for_each(|node| { let id = node.node; - if in_components.insert(id) { - to_check_stack.push(id); + if let Entry::Vacant(entry) = in_components.entry(id) { + entry.insert(d); + to_check_stack.push((id, d)); } }); } } - in_components - .iter() - .filter_map(|vid| node.graph.node(*vid)) - .collect() + + let (nodes, distances): (Vec<_>, Vec<_>) = in_components.into_iter().sorted().unzip(); + NodeState::new( + node.graph.clone(), + node.graph.clone(), + distances, + Some(Index::new(nodes, node.graph.unfiltered_num_nodes())), + ) } #[cfg(test)] @@ -152,10 +163,10 @@ mod components_test { graph.add_edge(ts, src, dst, NO_PROPS, None).unwrap(); } - fn check_node(graph: &Graph, node_id: u64, mut correct: Vec) { - let mut results: Vec = in_component(graph.node(node_id).unwrap()) + fn check_node(graph: &Graph, node_id: u64, mut correct: Vec<(u64, usize)>) { + let mut results: Vec<_> = in_component(graph.node(node_id).unwrap()) .iter() - .map(|n| n.id().as_u64().unwrap()) + .map(|(n, d)| (n.id().as_u64().unwrap(), *d)) .collect(); results.sort(); correct.sort(); @@ -163,13 +174,13 @@ mod components_test { } check_node(&graph, 1, vec![]); - check_node(&graph, 2, vec![1]); - check_node(&graph, 3, vec![1]); - check_node(&graph, 4, vec![1, 2, 5]); - check_node(&graph, 5, vec![1, 2]); - check_node(&graph, 6, vec![1, 2, 4, 5]); - check_node(&graph, 7, vec![1, 2, 4, 5]); - check_node(&graph, 8, vec![1, 2, 5]); + check_node(&graph, 2, vec![(1, 1)]); + check_node(&graph, 3, vec![(1, 1)]); + check_node(&graph, 4, vec![(1, 2), (2, 1), (5, 1)]); + check_node(&graph, 5, vec![(1, 2), (2, 1)]); + check_node(&graph, 6, vec![(1, 3), (2, 2), (4, 1), (5, 2)]); + check_node(&graph, 7, vec![(1, 3), (2, 2), (4, 1), (5, 2)]); + check_node(&graph, 8, vec![(1, 3), (2, 2), (5, 1)]); } #[test] diff --git a/raphtory/src/algorithms/components/out_components.rs b/raphtory/src/algorithms/components/out_components.rs index 357d2471a..ed7d11982 100644 --- a/raphtory/src/algorithms/components/out_components.rs +++ b/raphtory/src/algorithms/components/out_components.rs @@ -2,7 +2,10 @@ use crate::{ algorithms::algorithm_result::AlgorithmResult, core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ - api::view::{NodeViewOps, StaticGraphViewOps}, + api::{ + state::{Index, NodeState}, + view::{NodeViewOps, StaticGraphViewOps}, + }, graph::node::NodeView, task::{ context::Context, @@ -13,9 +16,10 @@ use crate::{ }, prelude::GraphViewOps, }; +use itertools::Itertools; use raphtory_api::core::entities::GID; use rayon::prelude::*; -use std::collections::HashSet; +use std::collections::{hash_map::Entry, HashMap, HashSet}; #[derive(Clone, Debug, Default)] struct OutState { @@ -104,31 +108,38 @@ where /// /// Returns: /// -/// A Vec containing the Nodes within the given nodes out-component +/// Nodes in the out-component with their distances from the starting node. /// -pub fn out_component<'graph, G: GraphViewOps<'graph>>(node: NodeView) -> Vec> { - let mut out_components = HashSet::new(); +pub fn out_component<'graph, G: GraphViewOps<'graph>>( + node: NodeView, +) -> NodeState<'graph, usize, G> { + let mut out_components = HashMap::new(); let mut to_check_stack = Vec::new(); node.out_neighbours().iter().for_each(|node| { let id = node.node; - if out_components.insert(id) { - to_check_stack.push(id); - } + out_components.insert(id, 1usize); + to_check_stack.push((id, 1usize)); }); - while let Some(neighbour_id) = to_check_stack.pop() { + while let Some((neighbour_id, d)) = to_check_stack.pop() { + let d = d + 1; if let Some(neighbour) = &node.graph.node(neighbour_id) { neighbour.out_neighbours().iter().for_each(|node| { let id = node.node; - if out_components.insert(id) { - to_check_stack.push(id); + if let Entry::Vacant(entry) = out_components.entry(id) { + entry.insert(d); + to_check_stack.push((id, d)); } }); } } - out_components - .iter() - .filter_map(|vid| node.graph.node(*vid)) - .collect() + + let (nodes, distances): (Vec<_>, Vec<_>) = out_components.into_iter().sorted().unzip(); + NodeState::new( + node.graph.clone(), + node.graph.clone(), + distances, + Some(Index::new(nodes, node.graph.unfiltered_num_nodes())), + ) } #[cfg(test)] @@ -155,21 +166,25 @@ mod components_test { graph.add_edge(ts, src, dst, NO_PROPS, None).unwrap(); } - fn check_node(graph: &Graph, node_id: u64, mut correct: Vec) { - let mut results: Vec = out_component(graph.node(node_id).unwrap()) + fn check_node(graph: &Graph, node_id: u64, mut correct: Vec<(u64, usize)>) { + let mut results: Vec<_> = out_component(graph.node(node_id).unwrap()) .iter() - .map(|n| n.id().as_u64().unwrap()) + .map(|(n, d)| (n.id().as_u64().unwrap(), *d)) .collect(); results.sort(); correct.sort(); assert_eq!(results, correct); } - check_node(&graph, 1, vec![2, 3, 4, 5, 6, 7, 8]); - check_node(&graph, 2, vec![4, 5, 6, 7, 8]); + check_node( + &graph, + 1, + vec![(2, 1), (3, 1), (4, 2), (5, 2), (6, 3), (7, 3), (8, 3)], + ); + check_node(&graph, 2, vec![(4, 1), (5, 1), (6, 2), (7, 2), (8, 2)]); check_node(&graph, 3, vec![]); - check_node(&graph, 4, vec![6, 7]); - check_node(&graph, 5, vec![4, 6, 7, 8]); + check_node(&graph, 4, vec![(6, 1), (7, 1)]); + check_node(&graph, 5, vec![(4, 1), (6, 2), (7, 2), (8, 1)]); check_node(&graph, 6, vec![]); check_node(&graph, 7, vec![]); check_node(&graph, 8, vec![]); diff --git a/raphtory/src/python/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs index 875bf5777..65e32281a 100644 --- a/raphtory/src/python/packages/algorithms.rs +++ b/raphtory/src/python/packages/algorithms.rs @@ -64,9 +64,9 @@ use rand::{prelude::StdRng, SeedableRng}; use raphtory_api::core::{entities::GID, Direction}; use std::collections::{HashMap, HashSet}; -use crate::algorithms::bipartite::max_weight_matching::Matching; #[cfg(feature = "storage")] use crate::python::graph::disk_graph::PyDiskGraph; +use crate::{algorithms::bipartite::max_weight_matching::Matching, db::api::state::NodeState}; #[cfg(feature = "storage")] use pometry_storage::algorithms::connected_components::connected_components as connected_components_rs; @@ -157,7 +157,7 @@ pub fn in_components(g: &PyGraphView) -> AlgorithmResult, /// An array containing the Nodes within the given nodes in-component #[pyfunction] #[pyo3(signature = (node))] -pub fn in_component(node: &PyNode) -> Vec> { +pub fn in_component(node: &PyNode) -> NodeState<'static, usize, DynamicGraph> { components::in_component(node.node.clone()) } @@ -180,10 +180,10 @@ pub fn out_components(g: &PyGraphView) -> AlgorithmResult /// node (Node) : The node whose out-component we wish to calculate /// /// Returns: -/// An array containing the Nodes within the given nodes out-component +/// NodeStateUsize: A NodeState mapping the nodes in the out-component to their distance from the starting node. #[pyfunction] #[pyo3(signature = (node))] -pub fn out_component(node: &PyNode) -> Vec> { +pub fn out_component(node: &PyNode) -> NodeState<'static, usize, DynamicGraph> { components::out_component(node.node.clone()) }