Skip to content

relationalai.std.graphs.Compute

class relationalai.std.graphs.Compute

The Compute class serves as a namespace for various graph algorithms. This class is automatically instantiated when you create a Graph object and is accessible via the graph’s .compute attribute. It provides methods for computing basic graph statistics, centrality and similarity measures, community detection, and more.

Graph algorithms are executed by calling the appropriate method from a Graph object’s .compute attribute. The following example demonstrates how to compute the PageRank of each person in a social network graph:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.friends.extend([bob, carol])
carol.friends.add(daniel)
# Create an undirected graph with Person nodes and edges from people to their friends.
# This graph has three edges: one from Alice to Bob, Alice to Carol, and Carol to Daniel.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
with model.query() as select:
# Get all person objects.
person = Person()
# Compute the PageRank of each person.
pagerank = graph.compute.pagerank(person)
# Select the each person's name and their PageRank value.
response = select(person.name, alias(pagerank, "pagerank"))
print(response.results)
# Output:
# name pagerank
# 0 Alice 0.324562
# 1 Bob 0.175438
# 2 Carol 0.324562
# 3 Daniel 0.175438

See the documentation for each method in the Methods section for more examples.

Compute.num_nodes() -> Expression

Get the number of nodes in the graph. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns an Expression that produces the number of nodes in the graph as an integer value.

Use .num_nodes() to get the number of nodes in a graph. You access the .num_nodes() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the graph and connect them with a 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
alice.friends.add(bob)
bob.friends.add(alice)
# Create an undirected graph with Person nodes and edges between friends.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Get the number of nodes in the graph.
with model.query() as select:
num_nodes = graph.compute.num_nodes()
response = select(alias(num_nodes, "num_nodes"))
print(response.results)
# Output:
# num_nodes
# 0 2
Compute.num_edges() -> Expression

Get the number of edges in the graph. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns an Expression that produces the number of edges in the graph as an integer value.

Use .num_edges() to get the number of edges in a graph. You access the .num_edges() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the graph and connect them with a 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
alice.friends.add(bob)
bob.friends.add(alice)
# Create an undirected graph with Person nodes and edges between friends.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Get the number of edges in the graph.
with model.query() as select:
num_edges = graph.compute.num_edges()
response = select(alias(num_edges, "num_edges"))
print(response.results)
# Output:
# num_edges
# 0 1
Compute.degree(node: Producer) -> Expression

Compute the degree of a node. For an undirected graph, the degree of a node is the number of neighbors it has in the graph. In a directed graph, the degree of a node is the sum of its indegree and outdegree. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the degree of the node as an integer value.

Use .degree() to compute the degree of a node in a graph. You access the .degree() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the graph and connect them with a 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
alice.friends.add(bob)
bob.friends.add(alice)
# Create an undirected graph with Person nodes and edges between friends.
# This graph has one edge between Alice and Bob.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Get the number of nodes in the graph.
with model.query() as select:
# Get all Person objects.
person = Person()
# Compute the degree of each person.
degree = graph.compute.degree(person)
# Select the name of each person and their degree.
response = select(person.name, degree)
print(response.results)
# Output:
# name v
# 0 Alice 1
# 1 Bob 1

In directed graphs, the degree of a node is the sum of its indegree and outdegree:

# Create a directed graph with Person nodes and edges between friends.
# Note that graphs are directed by default.
# This graph has two edges. One from Alice to Bob and one from Bob to Alice.
directed_graph = Graph(model)
directed_graph.Node.extend(Person)
directed_graph.Edge.extend(Person.friends)
# Get the degree of each person in the graph.
with model.query() as select:
person = Person()
degree = directed_graph.compute.degree(person)
response = select(person.name, degree)
print(response.results)
# Output:
# name v
# 0 Alice 2
# 1 Bob 2
Compute.indegree(node: Producer) -> Expression

Compute the indegree of a node. In a directed graph, the indegree of a node is the number of edges that point to the node. For an undirected graph, .indegree() is an alias of .degree(). Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the indegree of the node as an integer value.

Use .indegree() to compute the indegree of a node in a graph. You access the .indegree() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the graph and connect them with a 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
alice.friends.add(bob)
bob.friends.add(alice)
# Create a directed graph with Person nodes and edges between friends.
# Note that graphs are directed by default.
# This graphs has two edges: one from Alice to Bob and one from Bob to Alice.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Get the number of nodes in the graph.
with model.query() as select:
# Get all Person objects.
person = Person()
# Compute the indegree of each person.
indegree = graph.compute.indegree(person)
# Select the name of each person and their indegree.
response = select(person.name, indegree)
print(response.results)
# Output:
# name v
# 0 Alice 1
# 1 Bob 1

For an undirected graph, .indegree() is same as .degree().

Compute.outdegree(node: Producer) -> Expression

Compute the outdegree of a node in the graph. In a directed graph, the outdegree of a node is the number of edges that point away from the node. For an undirected graph, .outdegree() is an alias of .degree(). Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the outdegree of the node as an integer value.

Use .outdegree() to compute the outdegree of a node in a graph. You access the .outdegree() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the graph and connect them with a 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
alice.friends.add(bob)
bob.friends.add(alice)
# Create a directed graph with Person nodes and edges between friends.
# Note that graphs are directed by default.
# This graphs has two edges: one from Alice to Bob and one from Bob to Alice.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Get the number of nodes in the graph.
with model.query() as select:
# Get all Person objects.
person = Person()
# Compute the outdegree of each person.
outdegree = graph.compute.outdegree(person)
# Select the name of each person and their outdegree.
response = select(person.name, outdegree)
print(response.results)
# Output:
# name v
# 0 Alice 1
# 1 Bob 1

In undirected graphs, .outdegree() is same as .degree().

Compute.betweenness_centrality(node: Producer) -> Expression

Computes the betweenness centrality of a node. Betweenness centrality measures the importance of a node by counting how often it appears on the shortest paths between pairs of nodes in a graph. Nodes with high betweenness centrality may play critical roles in the flow of information or resources through a network. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Calculating betweenness centrality requires determining all shortest paths between each pair of nodes within a graph, which makes it computationally intensive to compute exactly for large networks. betweenness_centrality() gives an approximation using the Brandes-Pich algorithm, which samples nodes uniformly at random and performs single-source shortest-path computations from those nodes.

This implementation nominally samples 100 nodes uniformly at random, yielding time complexity of 100 * O(|V|+|E|) where |V| and |E| are the number of nodes and edges respectively. If the graph has fewer than 100 nodes, the implementation reduces to the exact Brandes algorithm, with time complexity O(|V|(|V|+|E|)) for unweighted graphs.

Returns an Expression object that produces the betweenness centrality of the node as a floating-point value.

Use .betweenness_centrality() to compute the betweenness centrality of a node in a graph. You access the .betweenness_centrality() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.extend([bob, carol])
bob.follows.add(alice)
carol.follows.add(alice)
# Create a directed graph with Person nodes and edge from people to the people they follow.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Compute the betweenness centrality of each person in the graph.
with model.query() as select:
# Get all person objects.
person = Person()
# Compute the betweenness centrality of each person.
centrality = graph.compute.betweenness_centrality(person)
# Select the each person's name and their betweenness centrality.
response = select(person.name, alias(centrality, "betweenness_centrality"))
print(response.results)
# Output:
# name betweenness_centrality
# 0 Alice 2.0
# 1 Bob 0.0
# 2 Carol 0.0

If one of the objects produced by the node producer is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Get the betweenness centrality of each person and company in the graph.
with model.query() as select:
# Get all person and company objects.
obj = (Person | Company)()
# Compute the betweenness centrality of each object.
# Objects that are not nodes in the graph are filtered out.
centrality = graph.compute.betweenness_centrality(obj)
# Select the each object's name and their betweenness centrality.
response = select(obj.name, alias(centrality, "betweenness_centrality"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name betweenness_centrality
# 0 Alice 2.0
# 1 Bob 0.0
# 2 Carol 0.0
Compute.degree_centrality(node: Producer) -> Expression

This algorithm computes the degree centrality of a node. For unweighted graphs, it measures the importance of a node based on its degree. Nodes with high degree centrality are well-connected in the graph.

For weighted graphs, the weighted degree centrality of a node is the sum of the weights of the edges incident to the node divided by one less than the number of nodes in the graph.

Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYes
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the degree centrality of the node as a floating-point value, calculated by the following formula:

degree centrality = degree of the node / (total number of nodes - 1)

This value is at most 1.0 for simple graphs with no self loops. In graphs with self loops, a node’s degree centrality can exceed 1.0.

Use .degree_centrality() to compute the degree centrality of a node in a graph. You access the .degree_centrality() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.extend([bob, carol])
bob.follows.add(alice)
carol.follows.add(alice)
# Create a directed graph with Person nodes and edge from people to the people they follow.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Compute the degree centrality of each person in the graph.
with model.query() as select:
# Get all person objects.
person = Person()
# Compute the degree centrality of each person.
centrality = graph.compute.degree_centrality(person)
# Select the each person's name and their degree centrality.
response = select(person.name, alias(centrality, "degree_centrality"))
print(response.results)
# Output:
# name degree_centrality
# 0 Alice 2.0
# 1 Bob 1.0
# 2 Carol 1.0

If one of the objects produced by the node producer is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Get the degree centrality of each person and company in the graph.
with model.query() as select:
# Get all person and company objects.
obj = (Person | Company)()
# Compute the degree centrality of each object.
# Objects that are not nodes in the graph are filtered out.
centrality = graph.compute.degree_centrality(obj)
# Select the each object's name and their degree centrality.
response = select(obj.name, alias(centrality, "degree_centrality"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name degree_centrality
# 0 Alice 2.0
# 1 Bob 1.0
# 2 Carol 1.0

Use .degree_centrality() to compute the weighted degree centrality of a node in a graph. You access the .degree_centrality() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with Person and Friendship types.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
Friendship = model.Type("Friendship")
# Add some people to the model and connect them with friendships.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
Friendship.add(person1=alice, person2=bob, strength=100)
Friendship.add(person1=bob, person2=carol, strength=10)
# Create an weighted, undirected graph with Person nodes and edges between friends.
# This graph has two edges: one from Alice and Bob and one from Bob and Carol.
# The edges are weighted by the strength of each friendship.
graph = Graph(model, undirected=True, weighted=True)
graph.Node.extend(Person)
with model.rule():
friendship = Friendship()
graph.Edge.add(friendship.person1, friendship.person2, weight=friendship.strength)
# Compute the weighted degree centrality of each person in the graph.
with model.query() as select:
person = Person()
centrality = graph.compute.degree_centrality(person)
response = select(person.name, alias(centrality, "degree_centrality"))
print(response.results)
# Output:
# name degree_centrality
# 0 Alice 50.0
# 1 Bob 55.0
# 2 Carol 5.0
Compute.eigenvector_centrality(node: Producer) -> Expression

Computes the eigenvector centrality of a node in an undirected graph. Eigenvector centrality measures a node’s significance in a graph, taking into account not only its direct connections, but also the centrality of those connected nodes. Specifically, it assigns to each node a centrality score that is proportional to the sum of the centrality scores of its neighboring nodes. Thus, nodes with high eigenvector centrality are important because they are connected to other important nodes. Must be called in a rule or query context.

Directed graphs are not currently supported. See .pagerank() for a similar measure that works with directed graphs.

Graph TypeSupportedNotes
DirectedNoCurrently not supported.
UndirectedYesSee Returns for convergence criteria.
WeightedYesWeights must be non-negative.
UnweightedYesEdge weights default to 1.0.
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the eigenvector centrality of the node as a floating-point value.

We use the power iteration method, which converges when there is a unique eigenvalue with the largest absolute magnitude. An important example where this condition does not hold is in the case of bipartite graphs. Specifically, in a bipartite graph, the adjacency spectrum has a unique property: its eigenvalues are symmetric around zero, meaning for every positive eigenvalue, there is a corresponding negative eigenvalue of the same magnitude.

Use .eigenvector_centrality() to compute the eigenvector centrality of a node in an undirected graph. Directed graphs are currently not supported.

You access the .eigenvector_centrality() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.friends.extend([bob, carol])
carol.friends.add(daniel)
# Create an undirected graph with Person nodes and edges from people to their friends.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
with model.query() as select:
# Get all person objects.
person = Person()
# Compute the eigenvector centrality of each person.
centrality = graph.compute.eigenvector_centrality(person)
# Select the each person's name and their eigenvector centrality.
response = select(person.name, alias(centrality, "eigenvector_centrality"))
print(response.results)
# Output:
# name eigenvector_centrality
# 0 Alice 0.601501
# 1 Bob 0.371748
# 2 Carol 0.601501
# 3 Daniel 0.371748

If one of the objects produced by the node producer is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
with model.query() as select:
# Get all person and company objects.
obj = (Person | Company)()
# Compute the eigenvector centrality of each object.
# Objects that are not nodes in the graph are filtered out.
centrality = graph.compute.eigenvector_centrality(obj)
# Select the each object's name and their eigenvector centrality.
response = select(obj.name, alias(centrality, "eigenvector_centrality"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name eigenvector_centrality
# 0 Alice 0.601501
# 1 Bob 0.371748
# 2 Carol 0.601501
# 3 Daniel 0.371748
Compute.pagerank(
node: Producer,
damping_factor: float = 0.85,
tolerance: float = 1e-6,
max_iter: int = 20
) -> Expression

Computes the PageRank centrality of a node in a graph. PageRank measures a node’s influence in a graph based on the quality and quantity of its inbound links. Nodes with high PageRank values may be considered more influential than other nodes in the network. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesOnly non-negative weights are supported.
UnweightedYesEdge weights default to 1.0.
NameTypeDescriptionRange
nodeProducerA node in the graph.Vertex set.
damping_factorfloatThe PageRank damping factor. Must be between 0.0 and 1.0, exclusive of 1.0. Default is 0.85.[0,1).
tolerancefloatThe convergence tolerance for the PageRank algorithm. Default is 1e-6.Small positive number or zero.
max_iterintThe maximum number of iterations allowed in the PageRank algorithm. Default is 20.Positive integer.

Returns an Expression object that produces the PageRank centrality of the node as a floating-point value.

Use .pagerank() to compute the PageRank centrality of a node in a graph.

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued 'friends' property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.friends.extend([bob, carol])
carol.friends.add(daniel)
# Create an undirected graph with Person nodes and edges from people to their friends.
# This graph has three edges: one from Alice to Bob, Alice to Carol, and Carol to Daniel.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
with model.query() as select:
# Get all person objects.
person = Person()
# Compute the PageRank of each person.
pagerank = graph.compute.pagerank(person)
# Select the each person's name and their PageRank value.
response = select(person.name, alias(pagerank, "pagerank"))
print(response.results)
# Output:
# name pagerank
# 0 Alice 0.324562
# 1 Bob 0.175438
# 2 Carol 0.324562
# 3 Daniel 0.175438

If one of the objects produced by the node producer is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
with model.query() as select:
# Get all person and company objects.
obj = (Person | Company)()
# Compute the PageRank of each object.
# Objects that are not nodes in the graph are filtered out.
pagerank = graph.compute.pagerank(obj)
# Select the each object's name and their PageRank value.
response = select(obj.name, alias(pagerank, "pagerank"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name pagerank
# 0 Alice 0.324562
# 1 Bob 0.175438
# 2 Carol 0.324562
# 3 Daniel 0.175438
Compute.ego_network(node: Producer,
hops: int = 1
) -> Expression

This method returns the induced subgraph containing all nodes within k undirected hops from a specified source node (ego-node). It identifies nearby nodes based on undirected shortest path distance, effectively capturing the local structure around the ego-node. This is useful for analyzing local connectivity patterns or extracting ego-networks. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns an Expression that produces the set of edges induced by the ego_network function.

You access the .ego_network(node, hops) method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
model = rai.Model("socialNetwork")
Person = model.Type("Person")
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
claire = Person.add(name="Claire")
dave = Person.add(name="Dave")
eve = Person.add(name="Eve")
frank = Person.add(name="Frank")
gina = Person.add(name="Gina")
alice.follows.add(alice)
alice.follows.add(bob)
bob.follows.add(claire)
claire.follows.add(dave)
dave.follows.add(eve)
eve.follows.add(frank)
frank.follows.add(gina)
graph = Graph(model, undirected=False)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
with model.query() as select:
node = Person()
a,b = graph.compute.ego_network(node, 1)
ego_response = select(alias(node.name, "ego-node") , alias( Person(a).name, "u"), alias(Person(b).name, "v"))
print(ego_response.results)
# output
# ego-node u v
# 0 Alice Alice Alice
# 1 Alice Alice Bob
# 2 Bob Alice Alice
# 3 Bob Alice Bob
# 4 Bob Bob Claire
# 5 Claire Bob Claire
# 6 Claire Claire Dave
# 7 Dave Claire Dave
# 8 Dave Dave Eve
# 9 Eve Dave Eve
# 10 Eve Eve Frank
# 11 Frank Eve Frank
# 12 Frank Frank Gina
# 13 Gina Frank Gina

If you’re interested in retrieving the 2-hop ego-network of a specific node (e.g., Alice), you can apply the following modification to the code above:

with model.query() as select:
node = Person(name="Alice")
a,b = graph.compute.ego_network(node, hops=2)
ego_response = select(alias(node.name, "ego-node") , alias( Person(a).name, "u"), alias(Person(b).name, "v"))
# ego-node u v
# 0 Alice Alice Alice
# 1 Alice Alice Bob
# 2 Alice Bob Claire
Compute.cosine_similarity(node1: Producer, node2: Producer) -> Expression

This algorithm measures the cosine similarity between two nodes in a graph.

For unweighted graphs, it measures the similarity between two nodes based on the inner product between respective neighborhood vectors. Values range from 0.0 to 1.0, inclusive, where 1.0 indicating that the nodes have identical neighborhoods and 0.0 indicating no meaningful relationship.

For weighted graphs, it measures the similarity between two nodes based on the inner product between respective neighborhood vectors, weighted according to the edges. Values range from -1.0 to 1.0, inclusive, with higher value indicating greater similarity. If all weights are 1.0 it degenerates to the unweighted case.

In both cases, pairs of nodes with a similarity of 0.0, indicating no meaningful relationship, are excluded from results for improved performance.

Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYesBased on out-neighbors.
UndirectedYes
WeightedYes
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces the cosine similarity between the two nodes as a floating-point value.

Use .cosine_similarity() to compute the cosine similarity between two nodes in a graph.

You access the .cosine_similarity() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.friends.add(bob)
bob.friends.add(carol)
# Create an undirected graph with Person nodes and edges between friends.
# Note that cosine similarity is only supported for undirected graphs.
# This graph has two edges: one between Alice and Bob, and one between Bob and Carol.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
with model.query() as select:
# Get pairs of people.
person1, person2 = Person(), Person()
# Compute the cosine similarity between each pair of people.
similarity = graph.compute.cosine_similarity(person1, person2)
# Select each person's name and their similarity value.
response = select(person1.name, person2.name, alias(similarity, "cosine_similarity"))
print(response.results)
# Output:
# name name2 cosine_similarity
# 0 Alice Alice 1.0
# 1 Alice Carol 1.0
# 2 Bob Bob 1.0
# 3 Carol Alice 1.0
# 4 Carol Carol 1.0

There is no row for Alice and Bob in the preceding query’s results. That’s because Alice and Bob have a cosine similarity of 0.0. Pairs of nodes with zero similarity, indicating no meaningful similarity, are often excluded from analyses. Consequently, we filter out these pairs to improve performance.

If node1 or node2 is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Create the union of the Person and Company types.
PersonOrCompany = Person | Company
with model.query() as select:
# Get all person and company objects.
obj1, obj2 = PersonOrCompany(), PersonOrCompany()
obj1 < ob2 # Ensure pairs are unique. Compares internal object IDs.
# Compute the cosine similarity between each pair of objects.
# Objects that are not nodes in the graph are filtered out of the results.
similarity = graph.compute.cosine_similarity(obj1, obj2)
response = select(obj1.name, obj2.name, alias(similarity, "cosine_similarity"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name name2 cosine_similarity
# 0 Carol Alice 1.0

Use .cosine_similarity() to compute the weighted cosine similarity between two nodes in a graph.

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with Person and Friendship types.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
Friendship = model.Type("Friendship")
# Add some people to the model and connect them with friendships.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
Friendship.add(person1=alice, person2=bob, strength=20)
Friendship.add(person1=bob, person2=carol, strength=10)
Friendship.add(person1=alice, person2=carol, strength=10)
# Create a weighted, undirected graph with Person nodes and edges between friends.
# This graph has two edges: one between Alice and Bob, and one between Bob and Carol.
# The edges are weighted by the strength of each friendship.
graph = Graph(model, undirected=True, weighted=True)
graph.Node.extend(Person)
with model.rule():
friendship = Friendship()
graph.Edge.add(friendship.person1, friendship.person2, weight=friendship.strength)
# Compute the weighted cosine similarity between each pair of people in the graph.
with model.query() as select:
person1, person2 = Person(), Person()
similarity = graph.compute.cosine_similarity(person1, person2)
response = select(person1.name, person2.name, alias(similarity, "cosine_similarity"))
print(response.results)
# Output:
# name name2 cosine_similarity
# 0 Alice Alice 1.000000
# 1 Alice Bob 0.200000
# 2 Alice Carol 0.894427
# 3 Bob Alice 0.200000
# 4 Bob Bob 1.000000
# 5 Bob Carol 0.894427
# 6 Carol Alice 0.894427
# 7 Carol Bob 0.894427
# 8 Carol Carol 1.000000
Compute.jaccard_similarity(node1: Producer, node2: Producer) -> Expression

This algorithm measures the Jaccard similarity between two nodes in a graph.

For unweighted graphs, it measures the similarity between two nodes based on how many neighbors (or out-neighbors if the graph is directed) they share. Values range from 0.0 to 1.0, inclusive, with 1.0 indicating that the nodes have identical neighborhoods and 0.0 indicating no meaningful relationship.

For weighted graphs, it measures the similarity between two nodes as the ratio of the sums of the minimum and maximum edge weights connecting them. Values range from 0.0 to 1.0, inclusive, with higher values indicating greater similarity. If all weights are 1.0 it degenerates to the unweighted case.

In both cases, pairs of nodes with a similarity of 0.0, indicating no meaningful relationship, are excluded from results for improved performance.

Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYesBased on out-neighbors.
UndirectedYes
WeightedYes
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces the Jaccard similarity between the two nodes as a floating-point value, calculated by the following formula:

Jaccard similarity = (number of shared neighbors) / (total number of unique neighbors)

Use .jaccard_similarity() to compute the Jaccard similarity between two nodes in a graph. You access the .jaccard_similarity() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.friends.add(bob)
bob.friends.add(carol)
# Create an undirected graph with Person nodes and edges between friends.
# This graph has two edges: one between Alice and Bob, and one between Bob and Carol.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
with model.query() as select:
# Get pairs of people.
person1, person2 = Person(), Person()
# Compute the Jaccard similarity between each pair of people.
similarity = graph.compute.jaccard_similarity(person1, person2)
# Select each person's name and their similarity value.
response = select(person1.name, person2.name, alias(similarity, "jaccard_similarity"))
print(response.results)
# Output:
# name name2 jaccard_similarity
# 0 Alice Alice 1.0
# 1 Alice Carol 1.0
# 2 Bob Bob 1.0
# 3 Carol Alice 1.0
# 4 Carol Carol 1.0

There is no row for Alice and Bob in the preceding query’s results. That’s because Alice and Bob have a Jaccard similarity of 0.0. Pairs of nodes with zero similarity, indicating no meaningful similarity, are often excluded from analyses. Consequently, we filter out these pairs to improve performance.

If node1 or node2 is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Create the union of the Person and Company types.
PersonOrCompany = Person | Company
with model.query() as select:
# Get all person and company objects.
obj1, obj2 = PersonOrCompany(), PersonOrCompany()
obj1 < obj2 # Ensure pairs are unique. Compares internal object IDs.
# Compute the Jaccard similarity between each pair of objects.
# Objects that are not nodes in the graph are filtered out of the results.
similarity = graph.compute.jaccard_similarity(obj1, obj2)
response = select(obj1.name, obj2.name, alias(similarity, "jaccard_similarity"))
# Only rows for people are returned, since companies are not nodes in the graph.
# Also note that unlike in the preceding case, only distinct pairs of people are returned. We ensure this by requiring obj1 < obj2,
# where obj1 and obj2 represent the internal IDs of the individuals, and the IDs are ordered numerically.
print(response.results)
# Output:
# name name2 jaccard_similarity
# 0 Carol Alice 1.0

Use .jaccard_similarity() to compute the weighted Jaccard similarity between two nodes in a graph.

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with Person and Friendship types.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
Friendship = model.Type("Friendship")
# Add some people to the model and connect them with friendships.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
Friendship.add(person1=alice, person2=bob, strength=20)
Friendship.add(person1=bob, person2=carol, strength=10)
Friendship.add(person1=alice, person2=carol, strength=10)
# Create a weighted, undirected graph with Person nodes and edges between friends.
# This graph has two edges: one between Alice and Bob, and one between Bob and Carol.
# The edges are weighted by the strength of each friendship.
graph = Graph(model, undirected=True, weighted=True)
graph.Node.extend(Person)
with model.rule():
friendship = Friendship()
graph.Edge.add(friendship.person1, friendship.person2, weight=friendship.strength)
# Compute the weighted Jaccard similarity between each pair of people in the graph.
with model.query() as select:
person1, person2 = Person(), Person()
similarity = graph.compute.jaccard_similarity(person1, person2)
response = select(person1.name, person2.name, alias(similarity, "jaccard_similarity"))
print(response.results)
# Output:
# name name2 jaccard_similarity
# 0 Alice Alice 1.00
# 1 Alice Bob 0.20
# 2 Alice Carol 0.25
# 3 Bob Alice 0.20
# 4 Bob Bob 1.00
# 5 Bob Carol 0.25
# 6 Carol Alice 0.25
# 7 Carol Bob 0.25
# 8 Carol Carol 1.00
Compute.adamic_adar(node1: Producer, node2: Producer) -> Expression

Compute the Adamic-Adar index between two nodes in a graph. The Adamic-Adar index quantifies node similarity based on shared neighbors. Values are non-negative. The algorithm accepts directed graphs as input but processes them as undirected by ignoring edge directionality. In link prediction analysis, a high Adamic-Adar values may indicate that two nodes are likely to form a connection if they do not already have one. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYesDirected edges are treated as undirected by the algorithm.
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces the Adamic-Adar index between the two nodes as a floating-point value, calculated by the following formula:

Adamic-Adar index = sum(1 / log(degree of shared neighbor))

The sum is over all shared neighbors of the two nodes.

Use .adamic_adar() to compute the Adamic-Adar index between two nodes in a graph. You access the .adamic_adar() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.friends.extend([bob, carol])
# Create an undirected graph with Person nodes and edges between friends.
# This graph has two edges: one between Alice and Bob, and one between Bob and Carol.
# Even when creating a directed graph by setting `undirected=False`,
# the output remains identical to the undirected case.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Compute the Adamic-Adar index between each pair of people in the graph.
with model.query() as select:
person1, person2 = Person(), Person()
similarity = graph.compute.adamic_adar(person1, person2)
response = select(person1.name, person2.name, alias(similarity, "adamic_adar"))
print(response.results)
# Output:
# name name2 adamic_adar
# 0 Alice Alice inf
# 1 Bob Bob 1.442695
# 2 Bob Carol 1.442695
# 3 Carol Bob 1.442695
# 4 Carol Carol 1.442695

There is no row for Alice and Bob in the preceding query’s results. That’s because Alice and Bob have a an Adamic-Adar index of 0.0. Pairs of nodes with zero index, indicating no meaningful similarity, are often excluded from analyses. Consequently, we filter out these pairs to improve performance.

If node1 or node2 is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Create the union of the Person and Company types.
PersonOrCompany = Person | Company
with model.query() as select:
# Get all person and company objects.
obj1, obj2 = PersonOrCompany(), PersonOrCompany()
obj1 < obj2 # Ensure pairs are unique. Compares internal object IDs.
# Compute the Adamic-Adar index between each pair of objects.
# Objects that are not nodes in the graph are filtered.
similarity = graph.compute.adamic_adar(obj1, obj2)
response = select(obj1.name, obj2.name, alias(similarity, "adamic_adar"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name name2 adamic_adar
# 0 Carol Bob 1.442695
Compute.common_neighbor(node1: Producer, node2: Producer) -> Expression

Find the common neighbors between two nodes in a graph. In an undirected graph, a node w is a common neighbor of nodes u and v if there are edges connecting w to both u and v. The algorithm accepts directed graphs as input but processes them as undirected by ignoring edge directionality. Namely, in a directed graph, a node w is a common neighbor of nodes u and v if there are edges between w and both u and v, regardless of the direction of those edges.

Must be called in a rule or query context.

Graph TypeSupportedNotes
UndirectedYes
DirectedYesDirected edges are treated as undirected by the algorithm.
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces nodes that are common neighbors of node1 and node2.

Use .common_neighbor() to find the common neighbors between two nodes in a graph. You access the .common_neighbor() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.friends.extend([bob, carol])
# Create an undirected graph with Person nodes and edges between friends.
# This graph has two edges: one between Alice and Bob, and one between Alice and Carol.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Find the common neighbors of Bob and Carol.
with model.query() as select:
neighbor = graph.compute.common_neighbor(Person(name="Bob"), Person(name="Carol"))
response = select(neighbor.name)
print(response.results)
# Output:
# name
# 0 Alice

If node1 or node2 is not a node in the graph, no exception is raised. Instead, that pair of objects is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Create the union of the Person and Company types.
PersonOrCompany = Person | Company
with model.query() as select:
# Get all pairs of person and company objects.
obj1, obj2 = PersonOrCompany(), PersonOrCompany()
obj1 < obj2 # Ensure pairs are unique.
neighbor = graph.compute.common_neighbor(obj1, obj2)
response = select(obj1.name, obj2.name, neighbor.name)
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name name2 name3
# 0 Carol Bob Alice

Note that pairs of nodes with no common neighbors are filtered by .common_neighbor(). For example, there is no row for Alice and Bob in the above output because they have no common neighbors. Alice is neighbors with Bob and Carol, but Bob’s only neighbor is Alice.

.preferential_attachment() Preview

Section titled “.preferential_attachment() ”
Compute.preferential_attachment(node1: Producer, node2: Producer) -> Expression

Compute the preferential attachment score between two nodes in a graph. The preferential attachment score quantifies node similarity based on the product of their number of neighbors. In link prediction analysis, a high preferential attachment score may indicate that two nodes are likely to form a connection under the assumption that connections are more likely between nodes with higher degrees. Must be called in a rule or query context.

Graph TypeSupportedNotes
UndirectedYes
DirectedYesOperates on the undirected version of the graph.
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces the preferential attachment score between the two nodes as an integer value, calculated by the following formula:

preferential attachment = number of neighbors of node1 * number of neighbors of node2

Use .preferential_attachment() to compute the preferential attachment score between two nodes in a graph. You access the .preferential_attachment() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.friends.add(bob)
# Create an undirected graph with Person nodes and edges between friends.
# This graph has one edge between Alice and Bob. Carol is not connected to anyone.
# Even when creating a directed graph by setting `undirected=False`,
# the output remains identical to the undirected case.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Compute the preferential attachment score between Alice and Bob.
with model.query() as select:
person1, person2 = Person(), Person()
similarity = graph.compute.preferential_attachment(person1, person2)
response = select(person1.name, person2.name, alias(similarity, "preferential_attachment"))
print(response.results)
# Output:
# name name2 preferential_attachment
# 0 Alice Alice 1
# 1 Alice Bob 1
# 2 Alice Carol 0
# 3 Bob Alice 1
# 4 Bob Bob 1
# 5 Bob Carol 0
# 6 Carol Alice 0
# 7 Carol Bob 0
# 8 Carol Carol 0

While many graph algorithms produce sparse results (node pairs with a zero score are omitted), the preferential_attachment algorithm returns dense results, meaning node pairs with a zero score are still returned.

For example, consider Alice and Carol. Even though Carol has no neighbors, the preferential_attachment algorithm still calculates a score (in this case, 0) for the pair (Alice, Carol).

If node1 or node2 is not a node in the graph, no exception is raised. Instead, that object is filtered from the rule or query:

# Add a Company type to the model.
Company = model.Type("Company")
# Add some companies to the model.
with model.rule():
apple = Company.add(name="Apple")
google = Company.add(name="Google")
# Create the union of the Person and Company types.
PersonOrCompany = Person | Company
with model.query() as select:
# Get all person and company objects.
obj1, obj2 = PersonOrCompany(), PersonOrCompany()
obj1 < obj2 # Ensure pairs are unique. Compares internal object IDs.
# Compute the preferential attachment score between each pair of objects.
# Objects that are not nodes in the graph are filtered.
similarity = graph.compute.preferential_attachment(obj1, obj2)
response = select(obj1.name, obj2.name, alias(similarity, "preferential_attachment"))
# Only rows for people are returned, since companies are not nodes in the graph.
print(response.results)
# Output:
# name name2 preferential_attachment
# 0 Alice Bob 1
# 1 Carol Alice 0
# 2 Carol Bob 0
Compute.infomap(
node: Producer
max_levels: int = 1,
max_sweeps: int = 20,
level_tolerance: float = 0.01,
sweep_tolerance: float = 0.0001,
teleportation_rate: float = 0.15,
visit_rate_tolerance: float = 1e-15,
randomization_seed: int | None = None,
) -> Expression

Assign a community label to node using the Infomap algorithm. Infomap leverages principles from information theory to detect communities within networks. It employs the map equation, a powerful tool that quantifies the information needed to describe random walks on a network. This method aims to identify the most efficient encoding of network structure, thereby optimizing for the most compact representation of community divisions. Nodes assigned to the same community are more likely to interact or communicate with one another as information flows through the network. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesOnly positive weights are supported.
UnweightedYes
NameTypeDescriptionRange
nodeProducerA node in the graph.Vertex set.
max_levelsintThe maximum number of levels at which to optimize. Default is 1.Positive integer.
max_sweepsintThe maximum number of iterations to run at each level. Default is 20.Non-negative integer.
level_tolerancefloatThe minimum change in the map equation required to continue to the next level. Default is 0.01.Small positive number or zero.
sweep_tolerancefloatThe minimum change in the map equation required to continue to the next sweep. Default is 0.0001.Small positive number or zero.
teleportation_ratefloatThe non-zero probability of teleporting to a random node. Default is 0.15.[1e-4, 1].
visit_rate_tolerancefloatThe minimum change in the visit rate required to continue to the next sweep. Default is 1e-15.Small positive number.
randomization_seedint or NoneThe seed for the algorithm’s random number generator. Default is None.None or non-negative integer.

Returns an Expression object that produces the integer community label assigned by Infomap to node as an integer value.

Use .infomap() to assign community labels to nodes in a graph using the Infomap algorithm. You access the .infomap() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
evelyn = Person.add(name="Evelyn")
alice.follows.add(bob)
carol.follows.add(daniel)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Find the community label for a single person using the Infomap algorithm.
with model.query() as select:
community = graph.compute.infomap(Person(name="Alice"))
response = select(alias(community, "community_label"))
print(response.results)
# Output:
# community_label
# 0 2
# Find the community label for each person in the graph.
with model.query() as select:
person = Person()
community = graph.compute.infomap(person)
response = select(person.name, alias(community, "community_label"))
print(response.results)
# name community_label
# 0 Alice 2
# 1 Bob 2
# 2 Carol 1
# 3 Daniel 1
# 4 Evelyn 3

In this example, .infomap() finds three communities in the graph: Alice and Bob are in one community, and Carol and Daniel are in another. Evelyn is an isolated node and has been assigned a unique community ID.

Compute.label_propagation(
node: Producer,
max_sweeps: int = 20,
randomization_seed: int | None = None
) -> Expression

Assign a community label to node using the label propagation algorithm. Label propagation is an algorithm that begins by assigning each node a unique community label and iteratively updates the label of each node to the most common label among its neighbors until convergence or a maximum number of iterations is reached. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesOnly positive weights are supported.
UnweightedYes
NameTypeDescriptionRange
nodeProducerA node in the graph.Vertex set.
max_sweepsintThe maximum number of iterations to run the label propagation algorithm. Default is 20.Non-negative integer.
randomization_seedint or NoneThe seed for the algorithm’s random number generator. Default is None.None or non-negative integer.

Returns an Expression object that produces the integer community label assigned by label propagation to node.

Use .label_propagation() to assign a community label to a node in a graph. You access the .label_propagation() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
evelyn = Person.add(name="Evelyn")
alice.follows.add(bob)
carol.follows.add(daniel)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Find the community label for a single person using label propagation.
with model.query() as select:
person = Person(name="Alice")
community = graph.compute.label_propagation(person)
response = select(alias(community, "community_label"))
print(response.results)
# Output:
# community_label
# 0 2
# Find the community label for each person in the graph.
with model.query() as select:
person = Person()
community = graph.compute.label_propagation(person)
response = select(person.name, alias(community, "community_label"))
print(response.results)
# Output:
# name community_label
# 0 Alice 2
# 1 Bob 2
# 2 Carol 1
# 3 Daniel 1
# 4 Evelyn 3

In this example, .label_propagation() identifies three communities: (1) Alice and Bob, (2) Carol and Daniel, and (3) Evelyn as an isolated community.

Compute.louvain(
node: Producer,
max_levels: int = 1,
max_sweeps: int = 20,
level_tolerance: float = 0.01,
sweep_tolerance: float = 0.0001,
randomization_seed: int | None = None
) -> Expression

Assign a community label to node using the Louvain method. The Louvain algorithm is a hierarchical community detection technique that initially assigns each node to its own community. It iteratively merges nodes and communities to maximize modularity, a metric that evaluates the density of edges within communities relative to edges between them. The process continues until no further improvements in modularity can be made, or the maximum number of iterations is reached. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedNoCurrently not supported.
UndirectedYes
WeightedYesOnly positive weights are supported.
UnweightedYes
NameTypeDescriptionRange
nodeProducerA node in the graph.Vertex set.
max_levelsintThe maximum number of levels at which to optimize. Default is 1.Positive integer.
max_sweepsintThe maximum number of iterations to run at each level. Default is 20.Non-negative integer.
level_tolerancefloatThe minimum change in modularity required to continue to the next level. Default is 0.01.Small positive number or zero.
sweep_tolerancefloatThe minimum change in modularity required to continue to the next sweep. Default is 0.0001.Small positive number or zero.
randomization_seedint or NoneThe seed for the algorithm’s random number generator. Default is None.None or non-negative integer.

Returns an Expression object that produces the integer community label assigned to node by the Louvain algorithm.

Use .louvain() to assign community labels to nodes in a graph using the Louvain algorithm. You access the .louvain() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friends` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
evelyn = Person.add(name="Evelyn")
alice.friends.add(bob)
carol.friends.add(daniel)
# Create an undirected graph with Person nodes and edges between friends.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Find the community label for a single person using Louvain.
with model.query() as select:
community = graph.compute.louvain(Person(name="Alice"))
response = select(alias(community, "community_label"))
print(response.results)
# Output:
# community_label
# 0 2
# Find the community label for each person using Louvain.
with model.query() as select:
person = Person()
community = graph.compute.louvain(person)
response = select(person.name, alias(community, "community_label"))
print(response.results)
# name community_label
# 0 Alice 2
# 1 Bob 2
# 2 Carol 1
# 3 Daniel 1
# 4 Evelyn 3

In this example, .louvain() finds three communities in the graph: Alice and Bob are in one community, and Carol and Daniel are in another. Evelyn is an isolated node and has been assigned a unique community ID.

Compute.triangle_community(node: Producer) -> Expression

Assign a community label to node using the percolation method. The percolation method identifies communities within a graph by detecting densely connected clusters of nodes. It operates by initially locating all triangles within the graph—groups of three interconnected nodes. The process involves iteratively merging these triangles that share a common edge. This merging continues until no further triangles can be combined, effectively revealing the network’s densely connected communities. Nodes that are not part of any triangles are excluded. This method is only applicable to undirected graphs. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedNoNot applicable.
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the community label of node as an integer value.

Use .triangle_community() to assign a community label to a node in a graph. You access the .triangle_community() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friends` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
charlie = Person.add(name="Charlie")
diana = Person.add(name="Diana")
alice.friends.add(bob)
bob.friends.add(charlie)
charlie.friends.extend([alice, diana])
# Create an undirected graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Find the community label for a single person using the percolation method.
with model.query() as select:
community = graph.compute.triangle_community(Person(name="Alice"))
response = select(alias(community, "community_label"))
print(response.results)
# Output:
# community_label
# 0 1
# Compute the community label for each person in the graph.
with model.query() as select:
person = Person()
community = graph.compute.triangle_community(person)
response = select(person.name, alias(community, "community_label"))
print(response.results)
# Output:
# name community_label
# 0 Alice 1
# 1 Bob 1
# 2 Charlie 1

The above graph has edges from Alice to Bob, Bob to Charlie, Charlie to Alice, and Charlie to Diana. Alice, Bob, and Charlie form a triangle, but Diana is not part of any triangles. The percolation method assigns Alice and Bob to the same community. Diana is filtered out because she is not part of any triangles.

Use std.aggregate.count() to count the number of communities identified in the graph:

from relationalai.std.aggregates import count
with model.query() as select:
person = Person()
community = graph.compute.triangle_community(person)
response = select(alias(count(community), "num_communities"))
print(response.results)
# Output:
# num_communities
# 0 1

.avg_clustering_coefficient() Preview

Section titled “.avg_clustering_coefficient() ”
Compute.avg_clustering_coefficient() -> Expression

Compute the average clustering coefficient of all nodes in an undirected graph. The average clustering coefficient is the average of the local clustering coefficients for each node in the graph. Values range from 0 to 1. Higher average clustering coefficients indicate nodes’ increased tendency to form triangles with neighbors. Standard definitions of clustering coefficients primarily apply to undirected graphs. Directed graphs are not supported as a valid input.

Must be called in a rule or query context.

  • Directed graphs are not supported.
Graph TypeSupportedNotes
DirectedNo
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns an Expression object that produces the average clustering coefficient of all nodes in the graph as a floating-point value.

Use .avg_clustering_coefficient() to compute the average clustering coefficient of all nodes in an undirected graph. You access the .avg_clustering_coefficient() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.friends.extend([bob, carol])
bob.friends.extend([alice, carol, daniel])
# Create an undirected graph with Person nodes and edges between friends.
# This graph has four edges: Alice, Bob, and Carol form a triangle, and Daniel is only connected to Bob.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Compute the average clustering coefficient of the graph.
with model.query() as select:
acc = graph.compute.avg_clustering_coefficient()
response = select(alias(acc, "avg_clustering_coefficient"))
print(response.results)
# Output:
# avg_clustering_coefficient
# 0 0.583333
Compute.is_triangle(node1: Producer, node2: Producer, node3: Producer) -> Expression

Filters node1, node2, and node3 for combinations of that form a triangle. In an undirected graph, a triangle is a set of three nodes where each node is connected to the other two nodes. In a directed graph, a triangle is a set of three nodes where there is an edge from the first node to the second node, an edge from the second node to the third node, and an edge from the third node to the first node. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.
node3ProducerA node in the graph.

An Expression object.

Use .is_triangle() to check if three nodes form a triangle in a graph. You access the .is_triangle() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
charlie = Person.add(name="Charlie")
diana = Person.add(name="Diana")
alice.follows.add(bob)
bob.follows.add(charlie)
charlie.follows.extend([alice, diana])
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by Default.
# This graph has edges from Alice to Bob, Bob to Charlie, Charlie to Alice, and Charlie to Diana.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Find all nodes that form a triangle
with model.query() as select:
# Get triples of Person objects.
person1, person2, person3 = Person(), Person(), Person()
# Filter triples based on whether they form a triangle.
graph.compute.is_triangle(person1, person2, person3)
# Select the names of people that form triangles.
response = select(person1.name, person2.name, person3.name)
print(response.results)
# Output:
# name name2 name3
# 0 Alice Bob Charlie
# 1 Bob Charlie Alice
# 2 Charlie Alice Bob

The output has three rows, but each row is a permutation of the same three nodes.

Compute.local_clustering_coefficient(node: Producer) -> Expression

Compute the local clustering coefficient of a node in an undirected graph. The local clustering coefficient of a node represents the proportion of pairs among the node’s neighbors that are also connected to each other. Values range from 0 to 1, where 0 indicates none of the node’s neighbors are connected, and 1 indicates that the node’s neighbors are fully connected, forming a clique. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedNoNot applicable.
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces the local clustering coefficient of node as a floating-point value, calculated by the following formula:

local clustering coefficient = 2 * num_edges / (degree * (degree - 1))

Here, num_edges is the total number of edges between the neighbors of node and degree is the degree of node. Values range from 0 to 1, where 0 indicates none of the node’s neighbors are connected, and 1 indicates that the node’s neighbors are fully connected, forming a clique.

Use .local_clustering_coefficient() to compute the local clustering coefficient of a node in an undirected graph. You access the .local_clustering_coefficient() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `friend` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.friends.extend([bob, carol])
bob.friends.extend([alice, carol, daniel])
# Create an undirected graph with Person nodes and edges between friends.
# This graph has four edges: Alice, Bob, and Carol form a triangle, and Daniel is only connected to Bob.
graph = Graph(model, undirected=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.friends)
# Compute the local clustering coefficient of each person.
with model.query() as select:
person = Person()
lcc = graph.compute.local_clustering_coefficient(person)
response = select(person.name, alias(lcc, "clustering_coefficient"))
print(response.results)
# Output:
# name clustering_coefficient
# 0 Alice 1.000000
# 1 Bob 0.333333
# 2 Carol 1.000000
# 3 Daniel 0.000000
# Compute the local clustering coefficient of a specific person.
with model.query() as select:
lcc = graph.compute.local_clustering_coefficient(Person(name="Alice"))
response = select(alias(lcc, "clustering_coefficient"))
print(response.results)
# Output:
# clustering_coefficient
# 0 1.0
Compute.num_triangles(node: Producer | None = None) -> Expression

Compute the number of unique triangles in the graph. A triangle is a set of three nodes x, y, and z such that there is an edge between x and y, y and z, and z and x. If node is not None, the number of unique triangles that node is part of is computed. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducer or NoneA node in the graph. If not None, the number of unique triangles that node is part of is computed. Otherwise, the total number of unique triangles in the graph is computed. Default is None.

Returns an Expression object that produces the number of unique triangles in the graph as an integer value, if node is None, or the number of unique triangles that node is part of as an integer value, if node is not None.

Use .num_triangles() to compute the number of unique triangles in a graph. You access the .num_triangles() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
charlie = Person.add(name="Charlie")
diana = Person.add(name="Diana")
alice.follows.add(bob)
bob.follows.add(charlie)
charlie.follows.extend([alice, diana])
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Compute the number of unique triangles in the graph.
with model.query() as select:
num_triangles = graph.compute.num_triangles()
response = select(alias(num_triangles, "num_triangles"))
print(response.results)
# Output:
# num_triangles
# 0 1
# Compute the number of unique triangles that each node is part of.
with model.query() as select:
person = Person()
num_triangles = graph.compute.num_triangles(person)
response = select(person.name, alias(num_triangles, "num_triangles"))
print(response.results)
# Output:
# name num_triangles
# 0 Alice 1
# 1 Bob 1
# 2 Charlie 1
# 3 Diana 0
Compute.triangles(node: Producer | None = None) -> tuple[Expression, Expression, Expression]

Find all unique triangles in the graph. A triangle is a set of three nodes x, y, and z such that there is an edge between x and y, y and z, and z and x. If node is not None, the unique triangles that node is part of are computed. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducer or NoneA node in the graph. If not None, the unique triangles that node is part of are computed. Otherwise, the total number of unique triangles in the graph is computed. Default is None.

Returns a tuple (node1, node2, node3) of three Expression objects that produce triples of nodes that form unique triangles in the graph.

For undirected graphs, triples are produced so that the nodes are ordered in ascending order by their internal identifiers. In directed graphs, .triangles() produces triples of nodes such that node1 < node2, node1 < node3, and node2 != node3. This ensures that each triangle is unique based on the ordering of nodes and edge directions. For instance, (1, 2, 3) and (1, 3, 2) denote distinct directed triangles.

Use .triangles() to find all unique triangles in a graph. You access the .triangles() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
charlie = Person.add(name="Charlie")
diana = Person.add(name="Diana")
alice.follows.add(bob)
bob.follows.add(charlie)
charlie.follows.extend([alice, diana])
diana.follows.add(bob)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
graph = Graph(model)
graph.Edge.extend(Person.follows)
# Compute the unique triangles in the graph.
with model.query() as select:
person1, person2, person3 = graph.compute.triangles()
response = select(person1.name, person2.name, person3.name)
print(response.results)
# Output:
# name name2 name3
# 0 Charlie Alice Bob
# 1 Charlie Diana Bob
# Compute the unique triangles that include Alice.
with model.query() as select:
alice = Person(name="Alice")
person1, person2, person3 = graph.compute.triangles(alice)
response = select(person1.name, person2.name, person3.name)
print(response.results)
# Output:
# name name2 name3
# 0 Charlie Alice Bob
Compute.is_connected() -> Expression

Check if the graph is connected. A graph is connected if every node is reachable from every other node in the undirected version of the graph. For directed graphs, connected is the same as weakly connected. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns an Expression object that filters connected graphs.

Use .is_connected() to check if a graph is connected. You access the .is_connected() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork1000")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.add(bob)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has one edge from Alice to Bob. Carol is not connected to anyone.
graph = Graph(model, with_isolated_nodes=True)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Is the graph connected?
with model.query() as select:
with model.match() as connected:
with graph.compute.is_connected():
connected.add(True)
with model.case():
connected.add(False)
response = select(alias(connected, "is_connected"))
print(response.results)
# Output:
# is_connected
# 0 False
Compute.is_reachable(node1: Producer, node2: Producer) -> Expression

Check if node2 is reachable from node1 in the graph. One node is reachable from another if there is a path from the first node to the second. Every node is reachable from itself. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression that filters pairs of nodes in which there is a path from node1 to node2.

Use .is_reachable() to check if one node is reachable from another in a graph. You access the .is_reachable() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.add(bob)
bob.follows.add(carol)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has edges from Alice to Bob and Bob to Carol.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Can Alice reach Carol?
with model.query() as select:
alice = Person(name="Alice")
carol = Person(name="Carol")
with model.match() as reachable:
with model.case():
graph.compute.is_reachable(alice, carol)
reachable.add(True)
with model.case():
reachable.add(False)
response = select(alias(reachable, "is_reachable"))
print(response.results)
# Output:
# is_reachable
# 0 True
# Who can reach Alice?
with model.query() as select:
person = Person()
graph.compute.is_reachable(person, Person(name="Alice"))
response = select(person.name)
# The only person that can reach Alice is herself.
print(response.results)
# Output:
# name
# 0 Alice

To find all nodes reachable from a given node, use .reachable_from(). Use .distance() to find the shortest path length between two nodes.

Compute.reachable_from(node: Producer) -> Expression

Find all nodes reachable from node in a graph. One node is reachable from another if there is a path from the first node to the second. Every node is reachable from itself. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYes
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

Returns an Expression object that produces model objects that are reachable from node.

Use .reachable_from() to find all nodes reachable from a node in a graph. You access the .reachable_from() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.add(bob)
bob.follows.add(carol)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has edges from Alice to Bob and Bob to Carol.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Who is reachable from Alice?
with model.query() as select:
reachable = graph.compute.reachable_from(Person(name="Alice"))
response = select(reachable.name)
print(response.results)
# Output:
# name
# 0 Alice
# 1 Bob
# 2 Carol

In the example above, both Bob and Carol are reachable from Alice because there is a path from Alice to Bob and a path from Alice to Carol that passes through Bob. Every node is reachable from itself, so Alice is also reachable from Alice.

To check if one node is reachable from another, use .is_reachable(). Use .distance() to find the shortest path length between two nodes.

Compute.weakly_connected_component(node: Producer) -> Expression

Find the weakly connected component containing node in a graph. The weakly connected component of a node is the set of nodes that are reachable from the node in an undirected version of the graph. Components are identified by the node in the component with the smallest internal identifier. In an undirected graph, the weakly connected component is the same as the connected component. Must be called in a rule or query context.

Graph TypeSupportedNotes
DirectedYesOperates on the undirected version of the graph.
UndirectedYes
WeightedYesWeights are ignored.
UnweightedYes
NameTypeDescription
nodeProducerA node in the graph.

An Expression object that produces the representative node of the weakly connected component containing node. Component representatives are the nodes with the smallest internal identifiers in the component.

Use .weakly_connected_component() to find the weakly connected component containing a node in a graph. You access the .weakly_connected_component() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.add(bob)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has one edge from Alice to Bob. Carol is not connected to anyone.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Compute the weakly connected component for each person in the graph.
with model.query() as select:
person = Person()
component = graph.compute.weakly_connected_component(person)
response = select(person.name, alias(component.name, "component_representative"))
print(response.results)
# Output:
# name component_representative
# 0 Alice Alice
# 1 Bob Alice
# 2 Carol Carol

Component representatives are the objects with the smallest internal identifiers in the component. Above, Alice and Bob are in the same component, with Alice as the representative. Carol is in a component by herself because she is not connected to anyone.

Use std.aggregates.count to count the number of weakly connected components in a graph:

from relationalai.std.aggregates import count
with model.query() as select:
person = Person()
component = graph.compute.weakly_connected_component(person)
response = select(alias(count(component), "num_components"))
print(response.results)
# Output:
# num_components
# 0 2
Compute.diameter_range() -> tuple[Expression, Expression]

Compute lower and upper bounds for the maximum shortest-path distance between any two path-connected pairs of nodes. Edge weights are ignored in weighted graphs. If the graph is disconnected, the upper and lower bounds of the connected component with the most nodes is returned. Must be called in a rule or query context.

Graph TypeSupportedNotes
UndirectedYes
DirectedYes
WeightedYesWeights are ignored.
UnweightedYes

Returns a tuple of two Expression objects that produce the lower and upper bounds for the diameter of the graph. In the case of directed graphs, these bounds apply only to pairs of nodes where a path exists from one to the other.

The diameter range is computed by selecting a subset of highest degree nodes and, for each node, finding the length of the longest shortest path from that node to the rest of the graph. The minimum and maximum of these lengths are returned as the lower and upper bounds of the diameter, respectively.

Use .diameter_range() to compute the range of possible diameters in a graph. You access the .diameter_range() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
alice.follows.add(bob)
bob.follows.add(carol)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has edges from Alice to Bob and Bob to Carol.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Compute the diameter range of the graph.
with model.query() as select:
diam_min, diam_max = graph.compute.diameter_range()
response = select(alias(diam_min, "min"), alias(diam_max, "max"))
print(response.results)
# Output:
# min max
# 0 2 2

In cases like this where the lower and upper bounds are the same, the diameter of the graph is known exactly. This may not always be the case, especially for larger and more complex graphs.

Compute.distance(node1: Producer, node2: Producer) -> Expression

This algorithm computes the shortest path length between two nodes in a graph. Must be called in a rule or query context.

Graph TypeSupportedNotes
UndirectedYes
DirectedYes
WeightedYes
UnweightedYes
NameTypeDescription
node1ProducerA node in the graph.
node2ProducerA node in the graph.

Returns an Expression object that produces the shortest path length between node1 and node2 as an integer value.

Use .distance() to compute the shortest path length between two nodes in a graph. You access the .distance() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with a Person type.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
# Add some people to the model and connect them with a multi-valued `follows` property.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
alice.follows.add(bob)
bob.follows.add(carol)
carol.follows.add(daniel)
# Create a directed graph with Person nodes and edges between followers.
# Note that graphs are directed by default.
# This graph has three edges: one from Alice to Bob, Bob to Carol, and Carol to Daniel.
graph = Graph(model)
graph.Node.extend(Person)
graph.Edge.extend(Person.follows)
# Find the distance between Alice and Daniel.
with model.query() as select:
dist = graph.compute.distance(Person(name="Alice"), Person(name="Daniel"))
response = select(alias(dist, "distance"))
print(response.results)
# Output:
# distance
# 0 3
# Find all nodes at distance at most two from Alice.
with model.query() as select:
node = Person()
dist = graph.compute.distance(Person(name="Alice"), node)
dist <= 2
response = select(node.name, alias(dist, "distance"))
print(response.results)
# Output:
# name distance
# 0 Alice 0
# 1 Bob 1
# 2 Carol 2

Note that the distance between a node and itself is 0.

Use .distance() to compute the shortest path length between two nodes in a weighted graph. You access the .distance() method from a Graph object’s .compute attribute:

import relationalai as rai
from relationalai.std import alias
from relationalai.std.graphs import Graph
# Create a model named "socialNetwork" with Person and Friendship types.
model = rai.Model("socialNetwork")
Person = model.Type("Person")
Friendship = model.Type("Friendship")
# Add some people to the model and connect them with the Friendship type.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
daniel = Person.add(name="Daniel")
Friendship.add(person1=alice, person2=bob, strength=1.0)
Friendship.add(person1=bob, person2=carol, strength=0.5)
Friendship.add(person1=carol, person2=daniel, strength=0.75)
# Create a directed graph with Person nodes and edges between friends.
# Note that graphs are directed by default.
# This graph has three edges: one from Alice to Bob, Bob to Carol, and Carol to Daniel.
# The edges are weighted by the strength of each friendship.
graph = Graph(model, weighted=True)
graph.Node.extend(Person)
with model.rule():
friendship = Friendship()
graph.Edge.add(friendship.person1, friendship.person2, weight=friendship.strength)
# Find the weighted distance between Alice and Daniel.
with model.query() as select:
dist = graph.compute.distance(Person(name="Alice"), Person(name="Daniel"))
response = select(alias(dist, "distance"))
print(response.results)
# Output:
# distance
# 0 2.25
# Find all nodes with weighted distance at most two from Alice.
with model.query() as select:
node = Person()
dist = graph.compute.distance(Person(name="Alice"), node)
dist <= 2.0
response = select(node.name, alias(dist, "distance"))
print(response.results)
# Output:
# name distance
# 0 Alice 0.0
# 1 Bob 1.0
# 2 Carol 1.5