Skip to content

Commit

Permalink
add fast_rp algorithm
Browse files Browse the repository at this point in the history
Added Rust implementation; Rust test; Python integration; Python test
  • Loading branch information
wyatt-joyner-pometry committed Nov 22, 2024
1 parent fb7dba7 commit 145a5b2
Show file tree
Hide file tree
Showing 12 changed files with 921 additions and 118 deletions.
39 changes: 39 additions & 0 deletions python/python/raphtory/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,25 @@ class Graph(GraphView):
path (str): The path to the cache file
"""

def create_node(
self,
timestamp: TimeInput,
id: str | int,
properties: Optional[PropInput] = None,
node_type: Optional[str] = None,
) -> MutableNode:
"""
Creates a new node with the given id and properties to the graph. It fails if the node already exists.
Arguments:
timestamp (TimeInput): The timestamp of the node.
id (str|int): The id of the node.
properties (PropInput, optional): The properties of the node.
node_type (str, optional): The optional string which will be used as a node type
Returns:
MutableNode: The created node
"""

@staticmethod
def deserialise(bytes: bytes):
"""
Expand Down Expand Up @@ -3506,6 +3525,26 @@ class PersistentGraph(GraphView):
path (str): The path to the cache file
"""

def create_node(
self,
timestamp: TimeInput,
id: str | int,
properties: dict = None,
node_type: str = None,
):
"""
Creates a new node with the given id and properties to the graph. It fails if the node already exists.
Arguments:
timestamp (TimeInput): The timestamp of the node.
id (str | int): The id of the node.
properties (dict): The properties of the node.
node_type (str) : The optional string which will be used as a node type
Returns:
MutableNode
"""

def delete_edge(
self, timestamp: int, src: str | int, dst: str | int, layer: str = None
):
Expand Down
23 changes: 23 additions & 0 deletions python/python/raphtory/algorithms/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,29 @@ def directed_graph_density(g: GraphView):
float : Directed graph density of G.
"""

def fast_rp(
g: GraphView,
embedding_dim: int,
normalization_strength: float,
iter_weights: list[float],
seed: Optional[int] = None,
threads: Optional[int] = None,
) -> AlgorithmResult:
"""
Computes embedding vectors for each vertex of an undirected/bidirectional graph according to the Fast RP algorithm.
Original Paper: https://doi.org/10.48550/arXiv.1908.11512
Arguments:
g (GraphView): The graph view on which embeddings are generated.
embedding_dim (int): The size (dimension) of the generated embeddings.
normalization_strength (float): The extent to which high-degree vertices should be discounted (range: 1-0)
iter_weights (list[float]): The scalar weights to apply to the results of each iteration
seed (int, optional): The seed for initialisation of random vectors
threads (int, optional): The number of threads to be used for parallel execution.
Returns:
AlgorithmResult: Vertices mapped to their corresponding embedding vectors
"""

def fruchterman_reingold(
graph: GraphView,
iterations: int | None = 100,
Expand Down
22 changes: 19 additions & 3 deletions python/python/raphtory/graphql/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,6 @@ class RemoteEdgeAddition(object):
"""Create and return a new object. See help(type) for accurate signature."""

class RemoteGraph(object):
def __new__(self, path, client) -> RemoteGraph:
"""Create and return a new object. See help(type) for accurate signature."""

def add_constant_properties(self, properties: dict):
"""
Adds constant properties to the remote graph.
Expand Down Expand Up @@ -406,6 +403,25 @@ class RemoteGraph(object):
properties (dict): The temporal properties of the graph.
"""

def create_node(
self,
timestamp: int | str | datetime,
id: str | int,
properties: Optional[dict] = None,
node_type: Optional[str] = None,
):
"""
Create a new node with the given id and properties to the remote graph and fail if the node already exists.
Arguments:
timestamp (int|str|datetime): The timestamp of the node.
id (str|int): The id of the node.
properties (dict, optional): The properties of the node.
node_type (str, optional): The optional string which will be used as a node type
Returns:
RemoteNode
"""

def delete_edge(
self,
timestamp: int,
Expand Down
133 changes: 102 additions & 31 deletions python/tests/graphql/edit_graph/test_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def test_encode_graph():

encoded = encode_graph(g)
assert (
encoded
== "EgxaCgoIX2RlZmF1bHQSDBIKCghfZGVmYXVsdBoFCgNiZW4aCQoFaGFtemEYARoLCgdoYWFyb29uGAIiAhABIgYIAhABGAEiBBACGAIqAhoAKgQSAhABKgQSAhADKgIKACoGEgQIARABKgYSBAgBEAIqBAoCCAEqBhIECAIQAioGEgQIAhADKgQKAggCKgQ6AhABKgIyACoIOgYIARACGAEqBDICCAEqCDoGCAIQAxgCKgQyAggC"
encoded
== "EgxaCgoIX2RlZmF1bHQSDBIKCghfZGVmYXVsdBoFCgNiZW4aCQoFaGFtemEYARoLCgdoYWFyb29uGAIiAhABIgYIAhABGAEiBBACGAIqAhoAKgQSAhABKgQSAhADKgIKACoGEgQIARABKgYSBAgBEAIqBAoCCAEqBhIECAIQAioGEgQIAhADKgQKAggCKgQ6AhABKgIyACoIOgYIARACGAEqBDICCAEqCDoGCAIQAxgCKgQyAggC"
)


Expand All @@ -42,8 +42,8 @@ def test_wrong_url():
with pytest.raises(Exception) as excinfo:
client = RaphtoryClient("http://broken_url.com")
assert (
str(excinfo.value)
== "Could not connect to the given server - no response --error sending request for url (http://broken_url.com/)"
str(excinfo.value)
== "Could not connect to the given server - no response --error sending request for url (http://broken_url.com/)"
)


Expand Down Expand Up @@ -393,16 +393,14 @@ def test_create_node():

query_nodes = """{graph(path: "g") {nodes {list {name}}}}"""
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben"}, {"name": "shivam"}]
}
}
"graph": {"nodes": {"list": [{"name": "ben"}, {"name": "shivam"}]}}
}

create_node_query = """{updateGraph(path: "g") { createNode(time: 0, name: "oogway") { success } }}"""

assert client.query(create_node_query) == {"updateGraph": {"createNode": {"success": True}}}
assert client.query(create_node_query) == {
"updateGraph": {"createNode": {"success": True}}
}
assert client.query(query_nodes) == {
"graph": {
"nodes": {
Expand All @@ -428,11 +426,7 @@ def test_create_node_using_client():

query_nodes = """{graph(path: "g") {nodes {list {name}}}}"""
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben"}, {"name": "shivam"}]
}
}
"graph": {"nodes": {"list": [{"name": "ben"}, {"name": "shivam"}]}}
}

remote_graph = client.remote_graph(path="g")
Expand Down Expand Up @@ -460,23 +454,56 @@ def test_create_node_using_client_with_properties():
client = RaphtoryClient("http://localhost:1737")
client.send_graph(path="g", graph=g)

query_nodes = """{graph(path: "g") {nodes {list {name, properties { keys }}}}}"""
query_nodes = (
"""{graph(path: "g") {nodes {list {name, properties { keys }}}}}"""
)
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben", 'properties': {'keys': []}}, {"name": "shivam", 'properties': {'keys': []}}]
"list": [
{"name": "ben", "properties": {"keys": []}},
{"name": "shivam", "properties": {"keys": []}},
]
}
}
}

remote_graph = client.remote_graph(path="g")
remote_graph.create_node(timestamp=0, id="oogway", properties={"prop1": 60, "prop2": 31.3, "prop3": "abc123", "prop4": True, "prop5": [1, 2, 3]})
nodes = json.loads(json.dumps(client.query(query_nodes)))['graph']['nodes']['list']
node_oogway = next(node for node in nodes if node['name'] == 'oogway')
assert sorted(node_oogway['properties']['keys']) == ['prop1', 'prop2', 'prop3', 'prop4', 'prop5']
remote_graph.create_node(
timestamp=0,
id="oogway",
properties={
"prop1": 60,
"prop2": 31.3,
"prop3": "abc123",
"prop4": True,
"prop5": [1, 2, 3],
},
)
nodes = json.loads(json.dumps(client.query(query_nodes)))["graph"]["nodes"][
"list"
]
node_oogway = next(node for node in nodes if node["name"] == "oogway")
assert sorted(node_oogway["properties"]["keys"]) == [
"prop1",
"prop2",
"prop3",
"prop4",
"prop5",
]

with pytest.raises(Exception) as excinfo:
remote_graph.create_node(timestamp=0, id="oogway", properties={"prop1": 60, "prop2": 31.3, "prop3": "abc123", "prop4": True, "prop5": [1, 2, 3]})
remote_graph.create_node(
timestamp=0,
id="oogway",
properties={
"prop1": 60,
"prop2": 31.3,
"prop3": "abc123",
"prop4": True,
"prop5": [1, 2, 3],
},
)

assert "Node already exists" in str(excinfo.value)

Expand All @@ -494,20 +521,57 @@ def test_create_node_using_client_with_properties_node_type():
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben", 'nodeType': None, 'properties': {'keys': []}}, {"name": "shivam", 'nodeType': None, 'properties': {'keys': []}}]
"list": [
{"name": "ben", "nodeType": None, "properties": {"keys": []}},
{
"name": "shivam",
"nodeType": None,
"properties": {"keys": []},
},
]
}
}
}

remote_graph = client.remote_graph(path="g")
remote_graph.create_node(timestamp=0, id="oogway", properties={"prop1": 60, "prop2": 31.3, "prop3": "abc123", "prop4": True, "prop5": [1, 2, 3]}, node_type="master")
nodes = json.loads(json.dumps(client.query(query_nodes)))['graph']['nodes']['list']
node_oogway = next(node for node in nodes if node['name'] == 'oogway')
assert node_oogway['nodeType'] == 'master'
assert sorted(node_oogway['properties']['keys']) == ['prop1', 'prop2', 'prop3', 'prop4', 'prop5']
remote_graph.create_node(
timestamp=0,
id="oogway",
properties={
"prop1": 60,
"prop2": 31.3,
"prop3": "abc123",
"prop4": True,
"prop5": [1, 2, 3],
},
node_type="master",
)
nodes = json.loads(json.dumps(client.query(query_nodes)))["graph"]["nodes"][
"list"
]
node_oogway = next(node for node in nodes if node["name"] == "oogway")
assert node_oogway["nodeType"] == "master"
assert sorted(node_oogway["properties"]["keys"]) == [
"prop1",
"prop2",
"prop3",
"prop4",
"prop5",
]

with pytest.raises(Exception) as excinfo:
remote_graph.create_node(timestamp=0, id="oogway", properties={"prop1": 60, "prop2": 31.3, "prop3": "abc123", "prop4": True, "prop5": [1, 2, 3]}, node_type="master")
remote_graph.create_node(
timestamp=0,
id="oogway",
properties={
"prop1": 60,
"prop2": 31.3,
"prop3": "abc123",
"prop4": True,
"prop5": [1, 2, 3],
},
node_type="master",
)

assert "Node already exists" in str(excinfo.value)

Expand All @@ -525,7 +589,10 @@ def test_create_node_using_client_with_node_type():
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben", 'nodeType': None}, {"name": "shivam", 'nodeType': None}]
"list": [
{"name": "ben", "nodeType": None},
{"name": "shivam", "nodeType": None},
]
}
}
}
Expand All @@ -535,7 +602,11 @@ def test_create_node_using_client_with_node_type():
assert client.query(query_nodes) == {
"graph": {
"nodes": {
"list": [{"name": "ben", 'nodeType': None}, {"name": "shivam", 'nodeType': None}, {"name": "oogway", 'nodeType': "master"}]
"list": [
{"name": "ben", "nodeType": None},
{"name": "shivam", "nodeType": None},
{"name": "oogway", "nodeType": "master"},
]
}
}
}
Expand Down
Loading

0 comments on commit 145a5b2

Please sign in to comment.