Skip to content

Working with Graphs

The RelationalAI (RAI) Python API provides a Graph class for creating graphs from entities and relationships in a model. In this guide, you’ll learn how to create Graph objects and run graph algorithms.

Graphs are comprised of:

  • Nodes, which represent entities from a RAI model.
  • Edges, which represent relationships between entities.

Before you can create a graph, you first need to define entities and relationships in the model.

In a sense, models are a kind of graph. Entities are like nodes, and relationships, such as properties, are like edges. What a graph represents, then, is a subset — sometimes called a projection — of the model’s entities and relationships that can be visualized and analyzed using graph algorithms.

Note that the graphs created with the Graph class are not labeled property graphs and do not retain the structure of the model. Graphs are homogenous, meaning they have only one type of node and one type of edge. They are primarily used for running graph algorithms on a subset of the model’s entities and relationships.

For the examples in this section, we’ll use the following model:

import relationalai as rai
model = rai.Model("MyModel")
Person = model.Type("Person")
Product = model.Type("Product")
with model.rule():
# Define some Product entities.
flashlight = Product.add(description="Flashlight")
batteries = Product.add(description="Batteries")
toothpaste = Product.add(description="Toothpaste")
# Define some Person entities.
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
# Define a multi-valued friends property that connect Person entities.
alice.friends.extend([bob, carol])
bob.friends.add(alice)
carol.friends.add(alice)
# Define a multi-valued purchases property that connect Person and Product entities.
alice.purchases.extend([flashlight, batteries])
bob.purchases.add(batteries)

This model has three Product entities and three Person entities:

  • Alice, who is friends with Bob and Carol, has purchased a flashlight and batteries.
  • Bob, who is friends with Alice, has purchased batteries.
  • Carol, who is friends with Alice, has not purchased any products.

The friends and purchases properties are multi-valued and represent relationships between entities:

  • friends is a symmetric relationships between Person entities. If Person A is friends with Person B, then Person B is friends with Person A.
  • purchases is an asymmetric relationship between Person and Product entities. A person may purchase a product, but a product does not purchase a person.

We’ll use this model to create two different graph objects, one for each relationship type.

A directed graph is a graph where edges have a direction. That is, an edge from node A to node B is not the same as an edge from node B to node A.

Directed graphs model asymmetric relationships, such as the purchases relationship between Person and Product entities in the preceding model described above. The following snippet creates a directed graph from the model:

from relationalai.std.graphs import Graph
# Create a graph object from the model.
purchases_graph = Graph(model)
# Add nodes to the graph.
purchases_graph.Node.extend(Person)
purchases_graph.Node.extend(Product)
# Add edges to the graph.
purchases_graph.Edge.extend(Person.purchases)
# Visualize the graph.
purchases_graph.visualize()

Let’s break down the code, step by step:

  1. First, you must import the Graph class from the relationalai.std.graphs module.

  2. Then, you instantiate a Graph object from the model. By default, the Graph constructor creates a directed graph. If you prefer to be explicit, you may set the undirected parameter to False:

    purchases_graph = Graph(model, undirected=False)
  3. You add nodes to the graph using the purchase_graph.Node type. Here, .extend() is used to add all Person and all Product entities to the graph.

  4. You add edges to the graph using the purchase_graph.Edge class. Unlike Node, the Edge class is not a Type. However, it does have an .extend() method that you can use to add edges to the graph based on a property in the model.

  5. Finally, you can visualize the graph using the .visualize() method. In a Jupyter notebook, this method will render an interactive visualization of the graph below the cell. In non-interactive environments, the method will open a new browser tab with the visualization.

    For this graph, the visualization looks like this:

    There are two isolated nodes in the graph. These are nodes that are not connected to any other nodes by edges. In this case, the isolated nodes represent the toothbrush, since no one has purchased it, and Carol, since she has not purchased any products.

    Because the graph is directed, the edges have arrows indicating the direction of the relationship. Note that the nodes are not labeled in the visualization. See the Graph Visualization guide for details on customizing the visualization.

An undirected graph is a graph where edges do not have a direction. An edge from node A to node B is the same as an edge from node B to node A.

Undirected graphs model symmetric relationships, such as the friends relationship between Person entities in the preceding model described above. The following snippet creates an undirected graph from the model:

from relationalai.std.graphs import Graph
# Create a graph object from the model. Set the `undirected` parameter to `True`
# to create an undirected graph.
friends_graph = Graph(model, undirected=True)
# Add nodes to the graph.
friends_graph.Node.extend(Person)
# Add edges to the graph.
friends_graph.Edge.extend(Person.friends)
# Visualize the graph.
friends_graph.visualize()

The code is similar to the directed graph example, except that the undirected parameter is set to True when creating the Graph object.

Here’s the visualization of the undirected graph:

Edges in undirected graphs do not have arrows, indicating that the relationships are symmetric. See the Graph Visualization guide for details on customizing the visualization.

  • Multigraphs are not supported. That is, a graph cannot have multiple edges between the same pair of nodes. Note that in a directed graph an edge from node A to node B is distinct from an edge from node B to node A, so this limitation only applies to edges in the same direction.

  • Node weights are not supported. Although weighted graphs are supported, the weights are associated with edges, not nodes. You may set properties on nodes that represent weights, but RAI graph algorithms do not make use of node weights.

A graph’s node set is an instance of the Node class, which is a subclass of Type. To illustrate how to work with nodes, we’ll use the following model:

import relationalai as rai
model = rai.Model("MyModel")
Person = model.Type("Person")
Product = model.Type("Product")
with model.rule():
# Define some Product entities.
flashlight = Product.add(description="Flashlight")
batteries = Product.add(description="Batteries")
toothpaste = Product.add(description="Toothpaste")
# Define some Person entities.
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
# Define a multi-valued purchases property that connect Person and Product entities.
alice.purchases.extend([flashlight, batteries])
bob.purchases.add(toothbrush)

There are three ways to add nodes to a graph:

  1. Add all entities of a Type to the graph using the Node.extend() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add all Person entities as nodes in the graph.
    purchases_graph.Node.extend(Person)
    # Add all Product entities as nodes in the graph.
    purchases_graph.Node.extend(Product)
  2. Add specific entities to the graph using the Node.add() method inside of a model.rule() block:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add specific Person entities as nodes in the graph.
    with model.rule():
    purchases_graph.Node.add(Person(name="Alice"))
    # Add specific Product entities as nodes in the graph.
    with model.rule():
    purchases_graph.Node.add(Product(description="Flashlight"))
    purchases_graph.Node.add(Product(description="Batteries"))
  3. Automatically add nodes when edges are added to the graph:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add all Person entities as nodes in the graph.
    purchases_graph.Node.extend(Person)
    # Add edges to the graph from the Person.purchases property. Products that
    # people have purchased are automatically added to the graph as nodes.
    purchases_graph.Edge.extend(Person.purchases)

    In the example above, each Person entity is added to the graph. Then edges are created from the Person.purchases property. This automatically adds the flashlight and batteries products to the graph’s node set. The toothbrush product is not added to the node set because it is not connected to any Person entities by the purchases property.

There is no way to directly remove nodes from a graph. You declare graphs and their nodes in your Python code. A graph is not materialized until it is queried or visualized.

To remove nodes from a graph, you must redeclare the graph without the nodes you want to remove. You could do this by either:

  1. Editing the code that adds nodes to the graph to exclude the nodes you want to remove.

  2. Creating a new graph object and add only the nodes you want to keep:

    # Create a copy of the purchases_graph without the node for Alice.
    purchases_graph_without_alice = Graph(model)
    with model.rule():
    # Get all Person entities that are nodes in the purchases_graph.
    person = Person(purchases_graph.Node)
    # Exclude Alice.
    person.name != "Alice"
    # Add all remaining Person entities as nodes in the new graph.
    purchases_graph_without_alice.Node.add(person)
    with model.rule():
    # Get all edges in the purchases_graph.
    edge = purchases_graph.Edge()
    # Exclude edges that connect to Alice.
    alice = Person(name="Alice")
    edge.from_ != alice
    edge.to != alice
    # Add all remaining edges to the new graph.
    purchases_graph_without_alice.Edge.add(from_=edge.from_, to=edge.to)

Nodes support both single-valued and multi-valued properties. There are four ways to set properties on nodes:

  1. Pass single-valued properties to keyword arguments of the Node.extend() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add all Person entities as nodes in the graph. Set the label property of
    # each node to the Person's name.
    purchases_graph.Node.extend(Person, label=Person.name)
  2. Pass single-valued properties to keyword arguments of the Node.add() method inside of a model.rule() block:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add specific Person entities as nodes in the graph. Set the label property
    # of each node to the Person's name.
    with model.rule():
    alice = Person(name="Alice")
    purchases_graph.Node.add(alice, label=alice.name)
  3. Set single-valued properties on node instances using the .set() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Set the label property of each node to the Person's name.
    with model.rule():
    person = Person()
    node = purchases_graph.Node.add(person)
    node.set(label=person.name)
  4. Set multi-valued properties on nodes:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add edges to the graph from the Person.purchases property.
    purchases_graph.Edge.extend(Person.purchases)
    # Set a neighbors property of each node.
    with model.rule():
    node = purchases_graph.Node()
    neighbor = purchases_graph.Edge(from_=node).to
    node.neighbors.add(neighbor)

Properties set on nodes can be queried by getting an Instance of a graph’s Node type and selecting the properties you want:

from relationalai.std.graphs import Graph
purchases_graph = Graph(model)
purchases_graph.Node.extend(Person, label=Person.name)
purchases_graph.Edge.extend(Person.purchases)
with model.query() as select:
# Get an instance of the graph.Node() type.
node = purchases_graph.Node()
# Select the label property of each node.
response = select(node.label)
# Display the results.
print(response.results)
# label
# 0 Alice
# 1 Bob
# 2 Carol

Nodes cannot directly access properties of the entities they represent. For instance, although each node in the graph is a Person entity, you cannot access the Person entity’s name property directly from the node:

with model.query() as select:
node = purchases_graph.Node()
# The following raises an UninitializedProperty warning.
response = select(node.name)
# --- Uninitialized property -----------------------------------------------------
# The property graph224_name has never been set or added to and so will always
# cause the rule or query to fail.
# 1 | with model.query() as select:
# 2 | node = purchases_graph.Node()
# 3 | response = select(node.name)
# --------------------------------------------------------------------------------

To access the name property, you may either:

  1. Set the name property on the node when you add it to the graph:

    purchases_graph.Node.extend(Person, name=Person.name)
    # Now the following query works:
    with model.query() as select:
    node = purchases_graph.Node()
    # The following raises an UninitializedProperty warning.
    response = select(node.name)
    print(response.results)
    # name
    # 0 Alice
    # 1 Bob
    # 2 Carol

    See Set Properties on Nodes for details.

  2. Get an Instance of the Person type, filter it for people who are nodes in the graph, and select the name property:

    with model.query() as select:
    # Get people who are also nodes in the graph.
    person = Person(purchases_graph.Node)
    # Select the name property of each person.
    response = select(person.name)
    print(response.results)
    # name
    # 0 Alice
    # 1 Bob
    # 2 Carol

    This approach is more flexible than setting properties on nodes because it allows you to access any property of the Person entity, not just those set on the node.

    Note that person = Person(purchase_graph.Node) is equivalent to:

    person = Person()
    purchase_graph.Node(person)

    See Filtering Objects by Type for more information.

A graph’s edge set is an instance of the Edge class. Edge is analogous to a Type in that it represents a set of things – namely the edges of the graph. However, Edge is not a subclass of Type because edges are not entities in the model.

Instances of edges are represented by the EdgeInstance class, which is analogous to an Instance of a Type. EdgeInstance objects have two special properties named from_ and to that represent the nodes at either end of the edge.

To illustrate how to work with edges, we’ll use the following model:

import relationalai as rai
model = rai.Model("MyModel")
Person = model.Type("Person")
Product = model.Type("Product")
with model.rule():
# Define some Product entities.
flashlight = Product.add(description="Flashlight")
batteries = Product.add(description="Batteries")
toothpaste = Product.add(description="Toothpaste")
# Define some Person entities.
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
# Define a multi-valued friends property that connect Person entities.
alice.friends.extend([bob, carol])
bob.friends.add(alice)
carol.friends.add(alice)
# Define a multi-valued purchases property that connect Person and Product entities.
alice.purchases.extend([flashlight, batteries])
bob.purchases.add(toothbrush)

There are two ways to add edges to a graph:

  1. Add all edges defined by a property to the graph using the Edge.extend() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add edges to the graph from the Person.purchases property. Note that entities
    # that are connected by the purchases property are automatically added to the
    # nodes of the graph.
    purchases_graph.Edge.extend(Person.purchases)
  2. Add specific edges to the graph using the Edge.add() method inside of a model.rule() block:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add specific edges to the graph. Connect Alice to the Flashlight and Batteries
    # products. Note that Alice, Flashlight, and Batteries are automatically added to
    # the graph's node set.
    with model.rule():
    alice = Person(name="Alice")
    purchases_graph.Edge.add(from_=alice, to=Product(description="Flashlight"))
    purchases_graph.Edge.add(from_=alice, to=Product(description="Batteries"))

    This method allows you to add edges that are not defined by a property in the model. For example, you could add edges from an entity that connects to multiple other entities, such as a Purchase entity that connects a Person entity to a Product entity:

    # Define a Purchase type.
    Purchase = model.Type("Purchase")
    # Add some Purchase entities to the model.
    with model.rule():
    Purchase.add(by=Person(name="Alice"), product=Product(description="Flashlight"), quantity=1)
    Purchase.add(by=Person(name="Bob"), product=Product(description="Batteries"), quantity=2)
    # Add edges to the graph from the Purchase entities.
    with model.rule():
    purchase = Purchase()
    purchases_graph.Edge.add(from=purchase.by, to=purchase.product)

    Note that calling .add() multiple times with the same from_ and to arguments does not create multiple edges between the same pair of nodes, since multigraphs are not supported. Only one edge is created between the nodes and no error is raised.

There is no way to directly remove edges from a graph. You declare graphs and their edges in your Python code. A graph is not materialized until it is queried or visualized.

To remove edges from a graph, you must redeclare the graph without the edges you want to remove. You could do this by either:

  1. Editing the code that adds edges to the graph to exclude the edges you want to remove.

  2. Creating a new graph object and add only the edges you want to keep:

    # Create a copy of the purchases_graph without the edges involving Batteries.
    purchases_graph_without_edges = Graph(model)
    # Add all of the nodes from the purchases_graph to the new graph.
    purchases_graph_without_edges.Node.extend(purchases_graph.Node)
    with model.rule():
    # Get all edges in the purchases_graph.
    edge = purchases_graph.Edge()
    # Exclude edges that connect to Flashlight.
    batteries = Product(description="Flashlight")
    edge.from_ != batteries
    edge.to != batteries
    # Add all remaining edges to the new graph.
    purchases_graph_without_edges.Edge.add(from_=edge.from_, to=edge.to)

Edges support only single-valued properties. There are three ways to set properties on edges:

  1. Pass properties to keyword arguments of the Edge.extend() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add edges to the graph from the Person.purchases property. Set the label
    # property of each edge to the string "purchased."
    purchases_graph.Edge.extend(Person.purchases, label="purchased")
  2. Pass properties to keyword arguments of the Edge.add() method inside of a model.rule() block:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add specific edges to the graph. Connect Alice to Flashlight and set the
    # label to the string "purchased."
    with model.rule():
    alice = Person(name="Alice")
    purchases_graph.Edge.add(from_=alice, to=Product(description="Flashlight"), label="purchased")

    Avoid calling .add() multiple times with the same from_ and to arguments. Although it is possible to do so without error, the behavior is undefined:

    with model.rule():
    alice = Person(name="Alice")
    flashlight = Product(description="flashlight")
    purchases_graph.Edge.add(from_=alice, to=flashlight, label="purchased")
    # The following does not create a new edge between Alice and the flashlight,
    # and the label property of the existing edge is not guaranteed to be updated.
    purchases_graph.Edge.add(from_=alice, to=flashlight, label="bought")

    Since multigraphs are not supported, only one edge is created between the nodes. Moreover, the property’s value is indeterminate. Edge properties, like label, behave like single-valued properties of other entities in the model. Two values have been declared, but only one will be used and there’s no guarantee it will always be the first or the last value.

  3. Set properties on edge instances using the EdgeInstance.set() method:

    from relationalai.std.graphs import Graph
    # Create a graph object from the model.
    purchases_graph = Graph(model)
    # Add edges from the Person.purchases property to the graph.
    purchases_graph.Edge.extend(Person.purchases)
    # Set the label property of each edge to the string "purchased."
    with model.rule():
    edge = purchases_graph.Edge()
    edge.set(label="purchased")

Properties set on edges can be queried by getting an EdgeInstance from a graph’s Edge set and selecting the properties you want:

from relationalai.std.graphs import Graph
purchases_graph = Graph(model)
purchases_graph.Edge.extend(Person.purchases)
# Get the entities at either end of the edge.
with model.query() as select:
edge = purchases_graph.Edge()
response = select(edge.from_.name, edge.to.name)
print(response.results)
# name name2
# 0 Alice Flashlight
# 1 Alice Batteries
# 2 Bob Batteries

Every edge instance has from_ and to properties that return Instance objects representing the nodes at either end of the edge. Properties of the nodes can be accessed directly from the Instance objects. In a directed graph, like the purchase_graph in the preceding example, from_ is the source node and to is the target node.

In an undirected graph, from_ and to are not interchangeable. The direction of the edge is determined by the order in which the nodes are added to the edge:

friends_graph = Graph(model, undirected=True)
friends_graph.Edge.extend(Person.friends)
with model.query() as select:
edge = friends_graph.Edge()
edge.from_ == Person(name="Alice")
response = select(edge.from_.name, edge.to.name)
print(response.results)
# name name2
# 0 Alice Bob
# 1 Alice Carol
with model.query() as select:
edge = friends_graph.Edge()
edge.to == Person(name="Alice")
response = select(edge.from_.name, edge.to.name)
print(response.results)
# Empty DataFrame
# Columns: []
# Index: []

The Person.friends property connects Alice to Bob and Carol. Even though the graph is undirected, Alice is considered the source node and Bob and Carol are considered target nodes.

You can ensure that from_ and to are interchangeable by adding a rule that makes the Person.friends relationship symmetric:

with model.rule():
# Make the Person.friends relationship symmetric.
person = Person()
friend = person.friends
friend.friends.add(person)
with model.query() as select:
edge = friends_graph.Edge()
response = select(edge.from_.name, edge.to.name)
print(response.results)
# name name2
# 0 Alice Bob
# 1 Alice Carol
# 2 Bob Alice
# 3 Carol Alice

Edges in a graph may be weighted, meaning they have a numerical value associated with them. To create a weighted graph, you must set the weighted parameter to True when creating the Graph object and set the weight property on the edges:

import relationalai as rai
from relationalai.std.graphs import Graph
model = rai.Model("MyModel")
Person = model.Type("Person")
Friendship = model.Type("Friendship")
# Add some Person entities and Friendship relationships to the model.
with model.rule():
alice = Person.add(name="Alice")
bob = Person.add(name="Bob")
carol = Person.add(name="Carol")
Friendship.add(person1=alice, person2=bob, years_known=2)
Friendship.add(person1=alice, person2=carol, years_known=5)
# Create a weighted graph object from the model.
friends_graph = Graph(model, undirected=True, weighted=True)
# Add edges to the graph from the Friendship type. The weight of each edge is
# set to the years_known property of the Friendship relationship.
with model.rule():
friendship = Friendship()
friends_graph.Edge.add(
from_=friendship.person1,
to=friendship.person2,
weight=friendship.years_known
)
# Show the edges and their weights.
with model.query() as select:
edge = friends_graph.Edge()
response = select(edge.from_.name, edge.to.name, edge.weight)
print(response.results)
# name name2 v
# 0 Alice Bob 2
# 1 Alice Carol 5

Both directed and undirected graphs may be weighted. Any edges that are not explicitly given a weight are assigned a default weight of 1.0.

Just like other single-valued properties, you should avoid setting an edges’ weight property multiple times. For instance, the following is invalid:

with model.rule():
friendship = Friendship()
edge = friends_graph.Edge.add(
from_=friendship.person1,
to=friendship.person2,
weight=friendship.years_known
)
# The following does not update the weight of the edge. It creates an edge with
# an indeterminate weight value.
edge.set(weight=friendship.years_known + 1)
# Querying the model will result in an error:
with model.query():
edge = friends_graph.Edge()
response = select(edge.from_.name, edge.to.name, edge.weight)
# --- Integrity constraint violation ---------------------------------------------
#
# The provided weight relation must not contain multiple-weight edges.
#
# 17 | friends_graph = Graph(model, undirected=True, weighted=True)
#
# --------------------------------------------------------------------------------

In some cases, however, you may want to update the edge weights in a graph. To do so, you must create a new graph with the same nodes and edges but with different weights:

# Create a new graph.
new_graph = Graph(model)
# Add nodes from friends_graph to the new graph.
new_graph.Node.extend(friends_graph.Node)
# Add edges from friends_graph to the new graph with updated weights.
with model.rule():
edge = friends_graph.Edge()
new_graph.Edge.add(
from_=edge.from_,
to=edge.to,
weight=edge.weight + 1
)