grand icon indicating copy to clipboard operation
grand copied to clipboard

Multi-edges between two nodes

Open spranger opened this issue 9 months ago • 4 comments

Hello, I use the new version 0.5.1 of grand-graph:

from grand import Graph
from grandcypher import GrandCypher
from grand.backends._sqlbackend import SQLBackend

backend=SQLBackend(db_url="sqlite:///demo2.db")
G = Graph(backend=backend)

G.nx.add_node("spranger", type="Person")
G.nx.add_node("meier", type="Person")
G.nx.add_node("krause", type="Person")
G.nx.add_node("Berlin", type="City")
G.nx.add_node("Paris", type="City")
G.nx.add_node("London", type="City")

G.nx.add_edge("spranger", "Paris", type="LIVES_IN")
G.nx.add_edge("krause", "Berlin", type="LIVES_IN")
G.nx.add_edge("meier", "London", type="LIVES_IN")
G.nx.add_edge("spranger", "Berlin", type="BORN_IN")
G.nx.add_edge("krause", "Berlin", type="BORN_IN")
G.nx.add_edge("meier", "Berlin", type="BORN_IN")


result1 = GrandCypher(G.nx).run("""
MATCH (n)-[r]->(c)
WHERE
    n.type == "Person"
    and
    c.type == "City"
    
RETURN n, r, c    
""")

from lark.lexer import Token
n = result1[Token('CNAME', 'n')]
r = result1[Token('CNAME', 'r')]
c = result1[Token('CNAME', 'c')]

for i in range(len(n)):
    print(f"{n[i]} - {r[i].get('type')} -> {c[i]}")

backend.commit()
backend.close()

results in

  • spranger - BORN_IN -> Berlin
  • spranger - LIVES_IN -> Paris
  • meier - BORN_IN -> Berlin
  • meier - LIVES_IN -> London
  • krause - BORN_IN -> Berlin-
  1. The "krause - LIVES_IN -> Berlin" relation is not stored (as any second relation between two same nodes) . This might be due to the cause that our "G.nx" doesn't cope with multigraphs.
  2. In plain grandcypher I can query "Match (p:Person)" . How do I do this in my cypher query above?
  3. Would it be a good idea to have the backend and the graph layer (e.g. netwrokx) completely transparent and just run Cypher queries, also for creating nodes and relations?

Kind Regards. Steffen, the graphologist

spranger avatar May 14 '24 12:05 spranger

Hi @spranger! @jackboyla just added support for multigraphs in Grand-Cypher here: https://github.com/aplbrain/grand-cypher/pull/42

So that's certainly going to help here. I also think (need to do a deeper dive before I can confirm) that this is indeed a limitation of Grand right now; I don't think we support MultiGraphs very well. (Maybe @acthecoder23 this would be a fun project to chew on?)

Just thinking out loud, probably the correct answer is to split Graph/DiGraph/MultiGraph/MultiDiGraph implementations like we briefly discuss in this issue: https://github.com/aplbrain/grand/issues/44 ...

A short-term solution might be to create a MultiDiGraph in networkx itself (rather than in Grand) and try out @jackboyla's new implementation (which I'll have on PyPI / pip-installable shortly in grand-cypher==0.8.0). If that works for you, then we'll know the next step is support for multigraphs in the Grand backends!

j6k4m8 avatar May 14 '24 12:05 j6k4m8

PS: Just got back from a week in Berlin, and totally thinking about adding a LIVES_IN edge there someday... :)

j6k4m8 avatar May 14 '24 12:05 j6k4m8

@j6k4m8 I can give this a shot. I'll have to do a little digging but it'd be worthwhile

acthecoder23 avatar May 14 '24 23:05 acthecoder23

@spranger also to answer 2. -- to MATCH (a:friend) you need to assign the node label to the __labels__ attribute, in a set. We should probably include this in the README as it's not immediately obvious :) hopefully the multigraph support helps too:

from grandcypher import GrandCypher
import networkx as nx

host = nx.MultiDiGraph()
host.add_node("a", name="Alice", age=30)
host.add_node("b", name="Bob", age=40)
host.add_node("c", name="Charlie", age=50)
host.add_edge("a", "b", __labels__={"friend"}, years=3)  # <---
host.add_edge("a", "c", __labels__={"colleague"}, years=10)
host.add_edge("a", "c", __labels__={"parent"}, duration='forever')
host.add_edge("b", "c", __labels__={"colleague"}, duration=10)
host.add_edge("b", "c", __labels__={"mentor"}, years=2)

qry = """
MATCH (a)-[r]->(b)
RETURN a.name, b.name, r.__labels__, r.duration
"""
res = GrandCypher(host).run(qry)
print(res)

'''
{'a.name': ['Alice', 'Alice', 'Bob'], 
'b.name': ['Bob', 'Charlie', 'Charlie'], 
'r.__labels__': [{0: {'friend'}}, {0: {'colleague'}, 1: {'parent'}}, {0: {'colleague'}, 1: {'mentor'}}], 
'r.duration': [{0: None}, {0: None, 1: 'forever'}, {0: 10, 1: None}]}
'''

jackboyla avatar May 15 '24 07:05 jackboyla