diff --git a/python/python/raphtory/__init__.pyi b/python/python/raphtory/__init__.pyi index e51f67eb0..9fd6286b5 100644 --- a/python/python/raphtory/__init__.pyi +++ b/python/python/raphtory/__init__.pyi @@ -1768,6 +1768,14 @@ class Graph: path (str): The path to the file. """ + def save_to_zip(self, path): + """ + Saves the Graph to the given path. + + Arguments: + path (str): The path to the file. + """ + def serialise(self): """ Serialise Graph to bytes. @@ -1941,6 +1949,7 @@ class Graph: graph_template=None, node_template=None, edge_template=None, + graph_name=None, verbose=False, ): """ @@ -4696,6 +4705,14 @@ class PersistentGraph: path (str): The path to the file. """ + def save_to_zip(self, path): + """ + Saves the PersistentGraph to the given path. + + Arguments: + path (str): The path to the file. + """ + def serialise(self): """ Serialise PersistentGraph to bytes. @@ -4870,6 +4887,7 @@ class PersistentGraph: graph_template=None, node_template=None, edge_template=None, + graph_name=None, verbose=False, ): """ diff --git a/python/python/raphtory/algorithms/__init__.pyi b/python/python/raphtory/algorithms/__init__.pyi index 2f5881186..02bce9da8 100644 --- a/python/python/raphtory/algorithms/__init__.pyi +++ b/python/python/raphtory/algorithms/__init__.pyi @@ -247,6 +247,17 @@ def hits(g, iter_count=20, threads=None): An AlgorithmResult object containing the mapping from node ID to the hub and authority score of the node """ +def in_component(node): + """ + In component -- Finding the "in-component" of a node in a directed graph involves identifying all nodes that can be reached following only incoming edges. + + Arguments: + node (Node) : The node whose in-component we wish to calculate + + Returns: + An array containing the Nodes within the given nodes in-component + """ + def in_components(g): """ In components -- Finding the "in-component" of a node in a directed graph involves identifying all nodes that can be reached following only incoming edges. @@ -397,6 +408,17 @@ def min_out_degree(g): int : value of the smallest outdegree """ +def out_component(node): + """ + 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 + """ + def out_components(g): """ Out components -- Finding the "out-component" of a node in a directed graph involves identifying all nodes that can be reached following only outgoing edges. diff --git a/python/python/raphtory/graphql/__init__.pyi b/python/python/raphtory/graphql/__init__.pyi index 5f85d9cbb..1d684da77 100644 --- a/python/python/raphtory/graphql/__init__.pyi +++ b/python/python/raphtory/graphql/__init__.pyi @@ -32,31 +32,35 @@ class GraphServer: port (int): The port to use (defaults to 1736). """ - def start(self, port=1736, timeout_ms=None): + def set_embeddings( + self, + cache, + embedding=None, + graph_template=None, + node_template=None, + edge_template=None, + ): """ - Start the server and return a handle to it. + Setup the server to vectorise graphs with a default template. Arguments: - port (int): the port to use (defaults to 1736). - timeout_ms (int): wait for server to be online (defaults to 5000). The server is stopped if not online within timeout_ms but manages to come online as soon as timeout_ms finishes! - """ + cache (str): the directory to use as cache for the embeddings. + embedding (Function): the embedding function to translate documents to embeddings. + graph_template (String): the template to use for graphs. + node_template (String): the template to use for nodes. + edge_template (String): the template to use for edges. - def with_document_search_function(self, name, input, function): + Returns: + GraphServer: A new server object with embeddings setup. """ - Register a function in the GraphQL schema for document search over a graph. - The function needs to take a `VectorisedGraph` as the first argument followed by a - pre-defined set of keyword arguments. Supported types are `str`, `int`, and `float`. - They have to be specified using the `input` parameter as a dict where the keys are the - names of the parameters and the values are the types, expressed as strings. + def start(self, port=1736, timeout_ms=None): + """ + Start the server and return a handle to it. Arguments: - name (str): The name of the function in the GraphQL schema. - input (dict): The keyword arguments expected by the function. - function (Function): the function to run. - - Returns: - GraphServer: A new server object containing the vectorised graphs. + port (int): the port to use (defaults to 1736). + timeout_ms (int): wait for server to be online (defaults to 5000). The server is stopped if not online within timeout_ms but manages to come online as soon as timeout_ms finishes! """ def with_global_search_function(self, name, input, function): @@ -74,30 +78,17 @@ class GraphServer: function (Function): the function to run. Returns: - GraphServer: A new server object containing the vectorised graphs. + GraphServer: A new server object with the function registered """ - def with_vectorised( - self, - cache, - graph_names=None, - embedding=None, - graph_template=None, - node_template=None, - edge_template=None, + def with_vectorised_graphs( + self, graph_names, graph_template=None, node_template=None, edge_template=None ): """ Vectorise a subset of the graphs of the server. - Note: - If no embedding function is provided, the server will attempt to use the OpenAI API - embedding model, which will only work if the env variable OPENAI_API_KEY is set - appropriately - Arguments: graph_names (List[str]): the names of the graphs to vectorise. All by default. - cache (str): the directory to use as cache for the embeddings. - embedding (Function): the embedding function to translate documents to embeddings. graph_template (String): the template to use for graphs. node_template (String): the template to use for nodes. edge_template (String): the template to use for edges. diff --git a/python/tests/graphql/misc/test_components.py b/python/tests/graphql/misc/test_components.py new file mode 100644 index 000000000..17d1a049d --- /dev/null +++ b/python/tests/graphql/misc/test_components.py @@ -0,0 +1,76 @@ +from raphtory.graphql import RaphtoryClient +from raphtory.graphql import GraphServer +from raphtory import Graph +import tempfile + + +def test_in_out_components(): + + def sort_components(data): + if "inComponent" in data: + data["inComponent"] = sorted(data["inComponent"], key=lambda x: x["name"]) + if "outComponent" in data: + data["outComponent"] = sorted(data["outComponent"], key=lambda x: x["name"]) + + def prepare_for_comparison(structure): + if "node" in structure: + sort_components(structure["node"]) + if "window" in structure: + sort_components(structure["window"]["node"]) + if "at" in structure: + sort_components(structure["at"]["node"]) + + query = """ + { + graph(path: "graph") { + node(name: "3") { + inComponent { + name + } + outComponent { + name + } + } + window(start:1,end:6){ + node(name:"3"){ + inComponent{ + name + } + } + } + at(time:4){ + node(name:"4"){ + outComponent{ + name + } + } + } + } + } + """ + result = { + "graph": { + "node": { + "inComponent": [{"name": "7"}, {"name": "1"}], + "outComponent": [{"name": "6"}, {"name": "4"}, {"name": "5"}], + }, + "window": {"node": {"inComponent": [{"name": "1"}]}}, + "at": {"node": {"outComponent": [{"name": "5"}]}}, + } + } + work_dir = tempfile.mkdtemp() + g = Graph() + g.add_edge(1, 1, 2) + g.add_edge(2, 1, 3) + g.add_edge(3, 3, 4) + g.add_edge(4, 4, 5) + g.add_edge(5, 3, 6) + g.add_edge(6, 7, 3) + + g.save_to_file(work_dir + "/graph") + with GraphServer(work_dir).start(): + client = RaphtoryClient("http://localhost:1736") + query_res = client.query(query) + prepare_for_comparison(query_res["graph"]) + prepare_for_comparison(result["graph"]) + assert query_res == result diff --git a/python/tests/graphql/misc/test_graphql_vectors.py b/python/tests/graphql/misc/test_graphql_vectors.py index 4c866f8b2..91bf5250e 100644 --- a/python/tests/graphql/misc/test_graphql_vectors.py +++ b/python/tests/graphql/misc/test_graphql_vectors.py @@ -3,18 +3,22 @@ from raphtory.graphql import GraphServer, RaphtoryClient from raphtory import Graph + def embedding(texts): return [[text.count("a"), text.count("b")] for text in texts] + def test_embedding(): result = embedding(texts=["aaa", "b", "ab", "ba"]) assert result == [[3, 0], [0, 1], [1, 1], [1, 1]] + def setup_graph(g): g.update_constant_properties({"name": "abb"}) g.add_node(1, "aab") g.add_edge(1, "aab", "bbb") + def assert_correct_documents(client): query = """{ plugins { @@ -38,34 +42,38 @@ def assert_correct_documents(client): }""" result = client.query(query) assert result == { - 'plugins': { - 'globalSearch': [ + "plugins": { + "globalSearch": [ { - 'content': 'abb', - 'embedding': [1.0, 2.0], - 'entityType': 'graph', - 'name': ['abb'], + "content": "abb", + "embedding": [1.0, 2.0], + "entityType": "graph", + "name": ["abb"], }, ], }, - 'vectorisedGraph': { - 'algorithms': { - 'similaritySearch': [{ - 'content': 'aab', - 'embedding': [2.0, 1.0], - 'entityType': 'node', - 'name': ['aab']}] + "vectorisedGraph": { + "algorithms": { + "similaritySearch": [ + { + "content": "aab", + "embedding": [2.0, 1.0], + "entityType": "node", + "name": ["aab"], + } + ] } - } + }, } + def setup_server(work_dir): server = GraphServer(work_dir) server = server.set_embeddings( cache="/tmp/graph-cache", embedding=embedding, node_template="{{ name }}", - graph_template="{{ props.name }}" + graph_template="{{ props.name }}", ) return server @@ -81,6 +89,7 @@ def test_new_graph(): setup_graph(rg) assert_correct_documents(client) + def test_upload_graph(): print("test_upload_graph") work_dir = tempfile.mkdtemp() @@ -95,6 +104,7 @@ def test_upload_graph(): client.upload_graph(path="abb", file_path=g_path, overwrite=True) assert_correct_documents(client) + def test_include_graph(): work_dir = tempfile.mkdtemp() g_path = work_dir + "/abb" @@ -106,5 +116,6 @@ def test_include_graph(): client = RaphtoryClient("http://localhost:1736") assert_correct_documents(client) + test_upload_graph() test_include_graph() diff --git a/python/tests/test_algorithms.py b/python/tests/test_algorithms.py index 5fccc4851..8f9cb0be5 100644 --- a/python/tests/test_algorithms.py +++ b/python/tests/test_algorithms.py @@ -57,6 +57,23 @@ def test_in_components(): assert actual == expected +def test_in_component(): + g = Graph() + g.add_edge(1, 1, 2) + g.add_edge(2, 1, 3) + g.add_edge(3, 3, 4) + g.add_edge(4, 4, 5) + g.add_edge(5, 3, 6) + g.add_edge(6, 7, 3) + + actual = algorithms.in_component(g.node(3)) + correct = [g.node(7), g.node(1)] + assert set(actual) == set(correct) + actual = algorithms.in_component(g.node(3).window(1, 6)) + correct = [g.node(1)] + assert set(actual) == set(correct) + + def test_out_components(): g = gen_graph() actual = algorithms.out_components(g).get_all_with_names() @@ -75,6 +92,23 @@ def test_out_components(): assert actual == expected +def test_out_component(): + g = Graph() + g.add_edge(1, 1, 2) + g.add_edge(2, 1, 3) + g.add_edge(3, 3, 4) + g.add_edge(4, 4, 5) + g.add_edge(5, 3, 6) + g.add_edge(6, 7, 3) + + actual = algorithms.out_component(g.node(3)) + correct = [g.node(4), g.node(5), g.node(6)] + assert set(actual) == set(correct) + actual = algorithms.out_component(g.node(4).at(4)) + correct = [g.node(5)] + assert set(actual) == set(correct) + + def test_empty_algo(): g = Graph() assert algorithms.weakly_connected_components(g, 20).get_all_with_names() == {} diff --git a/raphtory-graphql/src/graph.rs b/raphtory-graphql/src/graph.rs index 017353fd4..0dcf0cb77 100644 --- a/raphtory-graphql/src/graph.rs +++ b/raphtory-graphql/src/graph.rs @@ -1,13 +1,10 @@ -use std::{fs, sync::Arc}; +use std::sync::Arc; use once_cell::sync::OnceCell; #[cfg(feature = "storage")] use raphtory::disk_graph::DiskGraphStorage; use raphtory::{ - core::{ - entities::nodes::node_ref::AsNodeRef, - utils::errors::{GraphError, InvalidPathReason::*}, - }, + core::{entities::nodes::node_ref::AsNodeRef, utils::errors::GraphError}, db::{ api::{ mutation::internal::InheritMutationOps, @@ -19,8 +16,7 @@ use raphtory::{ search::IndexedGraph, serialise::GraphFolder, vectors::{ - embedding_cache::EmbeddingCache, embeddings::openai_embedding, - vectorised_graph::VectorisedGraph, EmbeddingFunction, + embedding_cache::EmbeddingCache, vectorised_graph::VectorisedGraph, EmbeddingFunction, }, }; diff --git a/raphtory-graphql/src/model/algorithms/global_search.rs b/raphtory-graphql/src/model/algorithms/global_search.rs index 86a34bb41..24ddbb353 100644 --- a/raphtory-graphql/src/model/algorithms/global_search.rs +++ b/raphtory-graphql/src/model/algorithms/global_search.rs @@ -11,7 +11,7 @@ use async_graphql::{ }; use dynamic_graphql::internal::TypeName; use futures_util::future::BoxFuture; -use raphtory::vectors::{embeddings::openai_embedding, vectorised_cluster::VectorisedCluster}; +use raphtory::vectors::vectorised_cluster::VectorisedCluster; use std::ops::Deref; use tracing::info; diff --git a/raphtory-graphql/src/model/algorithms/similarity_search.rs b/raphtory-graphql/src/model/algorithms/similarity_search.rs index d1a6c4315..66ac0852b 100644 --- a/raphtory-graphql/src/model/algorithms/similarity_search.rs +++ b/raphtory-graphql/src/model/algorithms/similarity_search.rs @@ -11,7 +11,6 @@ use async_graphql::{ }; use dynamic_graphql::internal::TypeName; use futures_util::future::BoxFuture; -use raphtory::vectors::embeddings::openai_embedding; use tracing::info; pub(crate) struct SimilaritySearch; diff --git a/raphtory-graphql/src/model/graph/node.rs b/raphtory-graphql/src/model/graph/node.rs index 9b60bcb3f..2424697a6 100644 --- a/raphtory-graphql/src/model/graph/node.rs +++ b/raphtory-graphql/src/model/graph/node.rs @@ -2,11 +2,13 @@ use crate::model::graph::{ edges::GqlEdges, path_from_node::GqlPathFromNode, property::GqlProperties, }; use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; -use raphtory::db::{ - api::{properties::dyn_props::DynProperties, view::*}, - graph::node::NodeView, +use raphtory::{ + algorithms::components::{in_component, out_component}, + db::{ + api::{properties::dyn_props::DynProperties, view::*}, + graph::node::NodeView, + }, }; - #[derive(ResolvedObject)] pub(crate) struct Node { pub(crate) vv: NodeView, @@ -160,6 +162,20 @@ impl Node { self.vv.in_degree() } + async fn in_component(&self) -> Vec { + in_component(self.vv.clone()) + .iter() + .map(|n| n.clone().into()) + .collect() + } + + async fn out_component(&self) -> Vec { + out_component(self.vv.clone()) + .iter() + .map(|n| n.clone().into()) + .collect() + } + async fn edges(&self) -> GqlEdges { GqlEdges::new(self.vv.edges()) } diff --git a/raphtory-graphql/src/python/client/raphtory_client.rs b/raphtory-graphql/src/python/client/raphtory_client.rs index 5e15f21c9..098bfbe4a 100644 --- a/raphtory-graphql/src/python/client/raphtory_client.rs +++ b/raphtory-graphql/src/python/client/raphtory_client.rs @@ -16,7 +16,6 @@ use raphtory::{ use reqwest::{multipart, multipart::Part, Client}; use serde_json::{json, Value as JsonValue}; use std::{collections::HashMap, fs::File, io::Read, path::Path}; -use tokio::{self, runtime::Runtime}; use tracing::debug; /// A client for handling GraphQL operations in the context of Raphtory. diff --git a/raphtory/src/algorithms/components/in_components.rs b/raphtory/src/algorithms/components/in_components.rs index 9a466fd81..473594de1 100644 --- a/raphtory/src/algorithms/components/in_components.rs +++ b/raphtory/src/algorithms/components/in_components.rs @@ -6,6 +6,7 @@ use crate::{ core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ api::view::{NodeViewOps, StaticGraphViewOps}, + graph::node::NodeView, task::{ context::Context, node::eval_node::EvalNodeView, @@ -13,6 +14,7 @@ use crate::{ task_runner::TaskRunner, }, }, + prelude::GraphViewOps, }; use std::collections::HashSet; @@ -42,15 +44,15 @@ where let mut to_check_stack = Vec::new(); vv.in_neighbours().iter().for_each(|node| { let id = node.node; - in_components.insert(id); - to_check_stack.push(id); + if in_components.insert(id) { + to_check_stack.push(id); + } }); while let Some(neighbour_id) = to_check_stack.pop() { if let Some(neighbour) = vv.graph().node(neighbour_id) { neighbour.in_neighbours().iter().for_each(|node| { let id = node.node; - if !in_components.contains(&id) { - in_components.insert(id); + if in_components.insert(id) { to_check_stack.push(id); } }); @@ -91,6 +93,40 @@ where ); AlgorithmResult::new(graph.clone(), "In Components", results_type, res) } +/// Computes the in-component of a given node in the graph +/// +/// # Arguments +/// +/// * `node` - The node whose in-component we wish to calculate +/// +/// Returns: +/// +/// A Vec containing the Nodes within the given nodes in-component +/// +pub fn in_component<'graph, G: GraphViewOps<'graph>>(node: NodeView) -> Vec> { + let mut in_components = HashSet::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); + } + }); + while let Some(neighbour_id) = to_check_stack.pop() { + 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); + } + }); + } + } + in_components + .iter() + .filter_map(|vid| node.graph.node(*vid)) + .collect() +} #[cfg(test)] mod components_test { @@ -98,6 +134,44 @@ mod components_test { use crate::{db::api::mutation::AdditionOps, prelude::*, test_storage}; use std::collections::HashMap; + #[test] + fn in_component_test() { + let graph = Graph::new(); + let edges = vec![ + (1, 1, 2), + (1, 1, 3), + (1, 2, 4), + (1, 2, 5), + (1, 5, 4), + (1, 4, 6), + (1, 4, 7), + (1, 5, 8), + ]; + + for (ts, src, dst) in edges { + 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()) + .iter() + .map(|n| n.id().as_u64().unwrap()) + .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]); + } + #[test] fn in_components_test() { let graph = Graph::new(); diff --git a/raphtory/src/algorithms/components/mod.rs b/raphtory/src/algorithms/components/mod.rs index fa0fb7abb..54cc7a4c0 100644 --- a/raphtory/src/algorithms/components/mod.rs +++ b/raphtory/src/algorithms/components/mod.rs @@ -5,7 +5,7 @@ mod out_components; mod scc; pub use connected_components::weakly_connected_components; -pub use in_components::in_components; +pub use in_components::{in_component, in_components}; pub use lcc::LargestConnectedComponent; -pub use out_components::out_components; +pub use out_components::{out_component, out_components}; pub use scc::strongly_connected_components; diff --git a/raphtory/src/algorithms/components/out_components.rs b/raphtory/src/algorithms/components/out_components.rs index ca7b5bcb2..72b0e9eb3 100644 --- a/raphtory/src/algorithms/components/out_components.rs +++ b/raphtory/src/algorithms/components/out_components.rs @@ -3,6 +3,7 @@ use crate::{ core::{entities::VID, state::compute_state::ComputeStateVec}, db::{ api::view::{NodeViewOps, StaticGraphViewOps}, + graph::node::NodeView, task::{ context::Context, node::eval_node::EvalNodeView, @@ -10,6 +11,7 @@ use crate::{ task_runner::TaskRunner, }, }, + prelude::GraphViewOps, }; use raphtory_api::core::entities::GID; use rayon::prelude::*; @@ -44,15 +46,15 @@ where let mut to_check_stack = Vec::new(); vv.out_neighbours().iter().for_each(|node| { let id = node.node; - out_components.insert(id); - to_check_stack.push(id); + if out_components.insert(id) { + to_check_stack.push(id); + } }); while let Some(neighbour_id) = to_check_stack.pop() { if let Some(neighbour) = vv.graph().node(neighbour_id) { neighbour.out_neighbours().iter().for_each(|node| { let id = node.node; - if !out_components.contains(&id) { - out_components.insert(id); + if out_components.insert(id) { to_check_stack.push(id); } }); @@ -94,12 +96,88 @@ where AlgorithmResult::new(graph.clone(), "Out Components", results_type, res) } +/// Computes the out-component of a given node in the graph +/// +/// # Arguments +/// +/// * `node` - The node whose out-component we wish to calculate +/// +/// Returns: +/// +/// A Vec containing the Nodes within the given nodes out-component +/// +pub fn out_component<'graph, G: GraphViewOps<'graph>>(node: NodeView) -> Vec> { + let mut out_components = HashSet::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); + } + }); + while let Some(neighbour_id) = to_check_stack.pop() { + 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); + } + }); + } + } + out_components + .iter() + .filter_map(|vid| node.graph.node(*vid)) + .collect() +} + #[cfg(test)] mod components_test { use super::*; - use crate::{db::api::mutation::AdditionOps, prelude::*, test_storage}; + use crate::{ + algorithms::components::in_components::in_component, db::api::mutation::AdditionOps, + prelude::*, test_storage, + }; use std::collections::HashMap; + #[test] + fn out_component_test() { + let graph = Graph::new(); + let edges = vec![ + (1, 1, 2), + (1, 1, 3), + (1, 2, 4), + (1, 2, 5), + (1, 5, 4), + (1, 4, 6), + (1, 4, 7), + (1, 5, 8), + ]; + + for (ts, src, dst) in edges { + 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()) + .iter() + .map(|n| n.id().as_u64().unwrap()) + .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, 3, vec![]); + check_node(&graph, 4, vec![6, 7]); + check_node(&graph, 5, vec![4, 6, 7, 8]); + check_node(&graph, 6, vec![]); + check_node(&graph, 7, vec![]); + check_node(&graph, 8, vec![]); + } + #[test] fn out_components_test() { let graph = Graph::new(); diff --git a/raphtory/src/core/entities/nodes/node_store.rs b/raphtory/src/core/entities/nodes/node_store.rs index ac7ad74b9..5f131893a 100644 --- a/raphtory/src/core/entities/nodes/node_store.rs +++ b/raphtory/src/core/entities/nodes/node_store.rs @@ -204,7 +204,7 @@ impl NodeStore { layer: &'a Adj, d: Direction, self_id: VID, - ) -> impl Iterator + Send + '_ { + ) -> impl Iterator + Send + 'a { let iter: Box + Send> = match d { Direction::IN => Box::new( layer @@ -291,7 +291,7 @@ impl NodeStore { &'a self, layer: &'a Adj, d: Direction, - ) -> Box + Send + '_> { + ) -> Box + Send + 'a> { let iter: Box + Send> = match d { Direction::IN => Box::new(layer.iter(d).map(|(from_v, _)| from_v)), Direction::OUT => Box::new(layer.iter(d).map(|(to_v, _)| to_v)), diff --git a/raphtory/src/db/api/state/lazy_node_state.rs b/raphtory/src/db/api/state/lazy_node_state.rs index fa8e6dd6f..ebaced8da 100644 --- a/raphtory/src/db/api/state/lazy_node_state.rs +++ b/raphtory/src/db/api/state/lazy_node_state.rs @@ -169,7 +169,7 @@ impl< ) -> impl Iterator< Item = ( NodeView<&'a Self::BaseGraph, &'a Self::Graph>, - Self::Value<'_>, + Self::Value<'a>, ), > + 'a where @@ -191,7 +191,7 @@ impl< ) -> impl ParallelIterator< Item = ( NodeView<&'a Self::BaseGraph, &'a Self::Graph>, - Self::Value<'_>, + Self::Value<'a>, ), > where diff --git a/raphtory/src/db/api/state/ops.rs b/raphtory/src/db/api/state/ops.rs index 9d488bd43..b1fc2381b 100644 --- a/raphtory/src/db/api/state/ops.rs +++ b/raphtory/src/db/api/state/ops.rs @@ -43,7 +43,7 @@ pub trait NodeStateOps<'graph>: IntoIterator { ) -> impl Iterator< Item = ( NodeView<&'a Self::BaseGraph, &'a Self::Graph>, - Self::Value<'_>, + Self::Value<'a>, ), > + 'a where @@ -63,7 +63,7 @@ pub trait NodeStateOps<'graph>: IntoIterator { ) -> impl ParallelIterator< Item = ( NodeView<&'a Self::BaseGraph, &'a Self::Graph>, - Self::Value<'_>, + Self::Value<'a>, ), > where diff --git a/raphtory/src/db/api/storage/graph/nodes/node_entry.rs b/raphtory/src/db/api/storage/graph/nodes/node_entry.rs index 4210dd716..9da92e86c 100644 --- a/raphtory/src/db/api/storage/graph/nodes/node_entry.rs +++ b/raphtory/src/db/api/storage/graph/nodes/node_entry.rs @@ -69,7 +69,7 @@ impl<'b> NodeStorageEntry<'b> { self, layers: &'b LayerIds, dir: Direction, - ) -> impl Iterator + '_ { + ) -> impl Iterator + 'b { match self { NodeStorageEntry::Mem(entry) => StorageVariants::Mem(entry.edges_iter(layers, dir)), NodeStorageEntry::Unlocked(entry) => { diff --git a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs index 225e8d5eb..03b9e3f19 100644 --- a/raphtory/src/db/api/storage/graph/storage_ops/mod.rs +++ b/raphtory/src/db/api/storage/graph/storage_ops/mod.rs @@ -638,7 +638,7 @@ impl GraphStorage { node: VID, dir: Direction, view: &'a G, - ) -> impl Iterator + Send + '_ { + ) -> impl Iterator + Send + 'a { self.node_edges_iter(node, dir, view) .map(|e| e.remote()) .dedup() diff --git a/raphtory/src/python/packages/algorithms.rs b/raphtory/src/python/packages/algorithms.rs index 4c878f6a4..a38845fd3 100644 --- a/raphtory/src/python/packages/algorithms.rs +++ b/raphtory/src/python/packages/algorithms.rs @@ -1,4 +1,6 @@ #![allow(non_snake_case)] +#[cfg(feature = "storage")] +use crate::python::graph::disk_graph::PyDiskGraph; use crate::{ algorithms::{ algorithm_result::AlgorithmResult, @@ -51,22 +53,18 @@ use crate::{ core::{entities::nodes::node_ref::NodeRef, Prop}, db::{api::view::internal::DynamicGraph, graph::node::NodeView}, python::{ - graph::{edge::PyDirection, views::graph_view::PyGraphView}, + graph::{edge::PyDirection, node::PyNode, views::graph_view::PyGraphView}, utils::PyTime, }, }; use ordered_float::OrderedFloat; +#[cfg(feature = "storage")] +use pometry_storage::algorithms::connected_components::connected_components as connected_components_rs; use pyo3::{prelude::*, types::PyIterator}; use rand::{prelude::StdRng, SeedableRng}; use raphtory_api::core::entities::GID; use std::collections::{HashMap, HashSet}; -#[cfg(feature = "storage")] -use pometry_storage::algorithms::connected_components::connected_components as connected_components_rs; - -#[cfg(feature = "storage")] -use crate::python::graph::disk_graph::PyDiskGraph; - /// Implementations of various graph algorithms that can be run on a graph. /// /// To run an algorithm simply import the module and call the function with the graph as the argument @@ -145,6 +143,19 @@ pub fn in_components(g: &PyGraphView) -> AlgorithmResult, components::in_components(&g.graph, None) } +/// In component -- Finding the "in-component" of a node in a directed graph involves identifying all nodes that can be reached following only incoming edges. +/// +/// Arguments: +/// node (Node) : The node whose in-component we wish to calculate +/// +/// Returns: +/// An array containing the Nodes within the given nodes in-component +#[pyfunction] +#[pyo3(signature = (node))] +pub fn in_component(node: &PyNode) -> Vec> { + components::in_component(node.node.clone()) +} + /// Out components -- Finding the "out-component" of a node in a directed graph involves identifying all nodes that can be reached following only outgoing edges. /// /// Arguments: @@ -158,6 +169,19 @@ pub fn out_components(g: &PyGraphView) -> AlgorithmResult components::out_components(&g.graph, None) } +/// 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 +#[pyfunction] +#[pyo3(signature = (node))] +pub fn out_component(node: &PyNode) -> Vec> { + components::out_component(node.node.clone()) +} + /// Pagerank -- pagerank centrality value of the nodes in a graph /// /// This function calculates the Pagerank value of each node in a graph. See https://en.wikipedia.org/wiki/PageRank for more information on PageRank centrality. diff --git a/raphtory/src/python/packages/base_modules.rs b/raphtory/src/python/packages/base_modules.rs index 4769c9b52..8c07d0f93 100644 --- a/raphtory/src/python/packages/base_modules.rs +++ b/raphtory/src/python/packages/base_modules.rs @@ -77,7 +77,9 @@ pub fn base_algorithm_module(py: Python<'_>) -> Result<&PyModule, PyErr> { weakly_connected_components, strongly_connected_components, in_components, + in_component, out_components, + out_component, global_temporal_three_node_motif, global_temporal_three_node_motif_multi, local_temporal_three_node_motifs, diff --git a/raphtory/src/python/packages/vectors.rs b/raphtory/src/python/packages/vectors.rs index 5a49fb3c0..64bb6ea90 100644 --- a/raphtory/src/python/packages/vectors.rs +++ b/raphtory/src/python/packages/vectors.rs @@ -13,7 +13,6 @@ use crate::{ utils::{execute_async_task, PyTime}, }, vectors::{ - embedding_cache::EmbeddingCache, template::DocumentTemplate, vector_selection::DynamicVectorSelection, vectorisable::Vectorisable, diff --git a/raphtory/src/vectors/entity_id.rs b/raphtory/src/vectors/entity_id.rs index 524ed5a1e..b710119f4 100644 --- a/raphtory/src/vectors/entity_id.rs +++ b/raphtory/src/vectors/entity_id.rs @@ -1,8 +1,5 @@ use crate::{ - db::{ - api::view::StaticGraphViewOps, - graph::{edge::EdgeView, node::NodeView}, - }, + db::graph::{edge::EdgeView, node::NodeView}, prelude::{EdgeViewOps, GraphViewOps, NodeViewOps}, }; use raphtory_api::core::entities::GID; diff --git a/raphtory/src/vectors/vector_selection.rs b/raphtory/src/vectors/vector_selection.rs index fb4ffb9ed..371a386e6 100644 --- a/raphtory/src/vectors/vector_selection.rs +++ b/raphtory/src/vectors/vector_selection.rs @@ -467,7 +467,7 @@ impl VectorSelection { edge_documents: &'a HashMap>, windowed_graph: &'a W, window: Option<(i64, i64)>, - ) -> Box + '_> { + ) -> Box + 'a> { match &document.entity_id { EntityId::Graph { .. } => Box::new(std::iter::empty()), EntityId::Node { id } => { @@ -519,7 +519,7 @@ impl VectorSelection { nodes: impl Iterator> + 'static, windowed_graph: &'a W, window: Option<(i64, i64)>, - ) -> Box)> + '_> { + ) -> Box)> + 'a> { let groups = nodes .map(move |node| { let entity_id = EntityId::from_node(node); @@ -546,7 +546,7 @@ impl VectorSelection { edges: impl Iterator> + 'a, windowed_graph: &'a W, window: Option<(i64, i64)>, - ) -> Box)> + '_> { + ) -> Box)> + 'a> { let groups = edges .map(move |edge| { let entity_id = EntityId::from_edge(edge); @@ -573,7 +573,7 @@ impl VectorSelection { document: &'a DocumentRef, windowed_graph: &'a W, window: Option<(i64, i64)>, - ) -> Box)> + '_> { + ) -> Box)> + 'a> { match &document.entity_id { EntityId::Graph { .. } => Box::new(std::iter::empty()), EntityId::Node { id } => match windowed_graph.node(id) { @@ -598,7 +598,7 @@ impl VectorSelection { document: &DocumentRef, windowed_graph: &'a W, window: Option<(i64, i64)>, - ) -> Box)> + '_> { + ) -> Box)> + 'a> { match &document.entity_id { EntityId::Graph { .. } => Box::new(std::iter::empty()), EntityId::Node { id } => match windowed_graph.node(id) {