Skip to content

Commit

Permalink
Add distance from starting node for in- and out-components (#1877)
Browse files Browse the repository at this point in the history
* add distances to out-component

* add distances to in-component
  • Loading branch information
ljeub-pometry authored Nov 29, 2024
1 parent c596410 commit e018f20
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 83 deletions.
22 changes: 18 additions & 4 deletions python/python/raphtory/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions python/python/raphtory/algorithms/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -575,15 +575,15 @@ 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.
Arguments:
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):
Expand Down
21 changes: 3 additions & 18 deletions python/tests/graphql/edit_graph/test_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]},
]
}
}
Expand Down
8 changes: 4 additions & 4 deletions python/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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():
Expand Down
10 changes: 6 additions & 4 deletions raphtory-graphql/src/model/graph/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynamicGraph>,
Expand Down Expand Up @@ -172,15 +174,15 @@ impl Node {

async fn in_component(&self) -> Vec<Node> {
in_component(self.vv.clone())
.iter()
.map(|n| n.clone().into())
.nodes()
.map(|n| n.cloned().into())
.collect()
}

async fn out_component(&self) -> Vec<Node> {
out_component(self.vv.clone())
.iter()
.map(|n| n.clone().into())
.nodes()
.map(|n| n.cloned().into())
.collect()
}

Expand Down
61 changes: 36 additions & 25 deletions raphtory/src/algorithms/components/in_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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<G>) -> Vec<NodeView<G>> {
let mut in_components = HashSet::new();
pub fn in_component<'graph, G: GraphViewOps<'graph>>(
node: NodeView<G>,
) -> 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)]
Expand All @@ -152,24 +163,24 @@ mod components_test {
graph.add_edge(ts, src, dst, NO_PROPS, None).unwrap();
}

fn check_node(graph: &Graph, node_id: u64, mut correct: Vec<u64>) {
let mut results: Vec<u64> = 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();
assert_eq!(results, correct);
}

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]
Expand Down
59 changes: 37 additions & 22 deletions raphtory/src/algorithms/components/out_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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<G>) -> Vec<NodeView<G>> {
let mut out_components = HashSet::new();
pub fn out_component<'graph, G: GraphViewOps<'graph>>(
node: NodeView<G>,
) -> 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)]
Expand All @@ -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<u64>) {
let mut results: Vec<u64> = 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![]);
Expand Down
8 changes: 4 additions & 4 deletions raphtory/src/python/packages/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -157,7 +157,7 @@ pub fn in_components(g: &PyGraphView) -> AlgorithmResult<DynamicGraph, Vec<GID>,
/// An array containing the Nodes within the given nodes in-component
#[pyfunction]
#[pyo3(signature = (node))]
pub fn in_component(node: &PyNode) -> Vec<NodeView<DynamicGraph>> {
pub fn in_component(node: &PyNode) -> NodeState<'static, usize, DynamicGraph> {
components::in_component(node.node.clone())
}

Expand All @@ -180,10 +180,10 @@ pub fn out_components(g: &PyGraphView) -> AlgorithmResult<DynamicGraph, Vec<GID>
/// 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<NodeView<DynamicGraph>> {
pub fn out_component(node: &PyNode) -> NodeState<'static, usize, DynamicGraph> {
components::out_component(node.node.clone())
}

Expand Down

0 comments on commit e018f20

Please sign in to comment.