Graph
Graph( model, *, directed: bool, weighted: bool, aggregator: Optional[str] = None, node_concept: Optional[Concept] = None, edge_concept: Optional[Concept] = None, edge_src_relationship: Optional[Relationship | Chain] = None, edge_dst_relationship: Optional[Relationship | Chain] = None, edge_weight_relationship: Optional[Relationship | Chain] = None)A graph object.
Parameters
Section titled “Parameters”
(modelModel) - The model to use for the graph.
(directedbool) - Whether the graph is directed.
(weightedbool) - Whether the graph is weighted.
(aggregatorstr|None, default:None) - The aggregation function to use for multi-edges.
(node_conceptConcept|None, default:None) - The concept to use for the nodes in the graph.
(edge_conceptConcept|None, default:None) - The concept to use for the edges in the graph.
(edge_src_relationshipRelationship|Chain|None, default:None) - The relationship to use for the source nodes in the graph.
(edge_dst_relationshipRelationship|Chain|None, default:None) - The relationship to use for the destination nodes in the graph.
(edge_weight_relationshipRelationship|Chain|None, default:None) - The relationship to use for the edge weights in the graph.
Attributes
Section titled “Attributes”.EdgeSrc
Section titled “.EdgeSrc”Graph.EdgeSrc: Relationship | ChainThe relationship that determines source nodes for edges in the graph.
Possible Members:
.EdgeDst
Section titled “.EdgeDst”Graph.EdgeDst: Relationship | ChainThe relationship that determines destination nodes for edges in the graph.
Possible Members:
.EdgeWeight
Section titled “.EdgeWeight”Graph.EdgeWeight: Relationship | ChainThe relationship that determines edge weights in the graph.
Possible Members:
Methods
Section titled “Methods”.num_nodes()
Section titled “.num_nodes()”Graph.num_nodes() -> RelationshipReturns a unary relationship containing the number of nodes in the graph.
Returns:
Relationship- A unary relationship containing the number of nodes in the graph.
Relationship Schema:
num_nodes(count)
- count (Integer): The number of nodes in the graph.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up the graph and concepts>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define some nodes>>> node1, node2, node3, node4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(node1, node2, node3, node4)>>>>>> # 3. Define the full set of edges>>> model.define(... Edge.new(src=node1, dst=node2),... Edge.new(src=node2, dst=node3),... Edge.new(src=node3, dst=node3),... Edge.new(src=node2, dst=node4),... )>>>>>> # 4. The relationship contains the number of nodes>>> graph.num_nodes().inspect() num_nodes0 4.num_edges()
Section titled “.num_edges()”Graph.num_edges() -> RelationshipReturns a unary relationship containing the number of edges in the graph.
Returns:
Relationship- A unary relationship containing the number of edges in the graph.
Relationship Schema:
num_edges(count)
- count (Integer): The number of edges in the graph.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up the graph and concepts>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define some nodes>>> node1, node2, node3, node4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(node1, node2, node3, node4)>>>>>> # 3. Define the edges>>> model.define(... Edge.new(src=node1, dst=node2),... Edge.new(src=node2, dst=node3),... Edge.new(src=node3, dst=node3),... Edge.new(src=node2, dst=node4),... )>>>>>> # 4. The relationship contains the number of edges>>> graph.num_edges().inspect() num_edges0 4Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.neighbor()
Section titled “.neighbor()”Graph.neighbor(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing all neighbor pairs in the graph.
For directed graphs, a node’s neighbors include both its in-neighbors and out-neighbors.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the neighbor computation: only neighbors of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and one of its neighbors.
Relationship Schema:
neighbor(node, neighbor_node)
- node (Node): A node in the graph.
- neighbor_node (Node): A neighbor of the node.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... )>>>>>> # 3. Select the IDs from the neighbor relationship and inspect>>> u, v = Node.ref("u"), Node.ref("v")>>> neighbor = graph.neighbor()>>> model.select(u.id, v.id).where(neighbor(u, v)).inspect() id id_20 1 21 2 12 2 33 2 44 3 25 3 36 4 2>>> # 4. Use 'of' parameter to constrain the set of nodes to compute neighbors of>>> # Define a subset containing only nodes 2 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>>>>> # Get neighbors only of nodes in the subset>>> constrained_neighbor = graph.neighbor(of=subset)>>> model.select(u.id, v.id).where(constrained_neighbor(u, v)).inspect() id id_20 2 11 2 32 2 43 3 24 3 3Notes:
The neighbor() method, called with no parameters, computes and caches
the full neighbor relationship, providing efficient reuse across multiple
calls to neighbor(). In contrast, neighbor(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the neighbor relation is needed
across a program, neighbor() is typically more efficient; this is the
typical case. Use neighbor(of=subset) only when small subsets of the
neighbor relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.inneighbor()
Section titled “.inneighbor()”Graph.inneighbor(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship of all nodes and their in-neighbors.
An in-neighbor of a node u is any node v where an edge from v
to u exists. For undirected graphs, this is identical to neighbor.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the inneighbor computation: only in-neighbors of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a destination node and one of its in-neighbors.
Relationship Schema:
inneighbor(node, inneighbor_node)
- node (Node): The destination node.
- inneighbor_node (Node): The in-neighbor of the node (i.e., the source of an incoming edge).
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... )>>>>>> # 3. Select the IDs from the in-neighbor relationship and inspect>>> node, inneighbor_node = Node.ref("node"), Node.ref("inneighbor_node")>>> inneighbor = graph.inneighbor()>>> model.select(... node.id,... inneighbor_node.id... ).where(... inneighbor(node, inneighbor_node)... ).inspect() id id_20 2 11 3 22 3 33 4 2>>> # 4. Use 'of' parameter to constrain the set of nodes to compute in-neighbors of>>> # Define a subset containing only nodes 2 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>>>>> # Get in-neighbors only of nodes in the subset>>> constrained_inneighbor = graph.inneighbor(of=subset)>>> model.select(node.id, inneighbor_node.id).where(constrained_inneighbor(node, inneighbor_node)).inspect() id id_20 2 11 3 22 3 3Notes:
The inneighbor() method, called with no parameters, computes and caches
the full inneighbor relationship, providing efficient reuse across multiple
calls to inneighbor(). In contrast, inneighbor(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the inneighbor relation is needed
across a program, inneighbor() is typically more efficient; this is the
typical case. Use inneighbor(of=subset) only when small subsets of the
inneighbor relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.outneighbor()
Section titled “.outneighbor()”Graph.outneighbor(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship of all nodes and their out-neighbors.
An out-neighbor of a node u is any node v where an edge from u
to v exists. For undirected graphs, this is identical to neighbor.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the outneighbor computation: only out-neighbors of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a source node and one of its out-neighbors.
Relationship Schema:
outneighbor(node, outneighbor_node)
- node (Node): The source node.
- outneighbor_node (Node): The out-neighbor of the node (i.e., the destination of an outgoing edge).
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the IDs from the out-neighbor relationship and inspect>>> node, outneighbor_node = Node.ref("node"), Node.ref("outneighbor_node")>>> outneighbor = graph.outneighbor()>>> model.select(... node.id,... outneighbor_node.id... ).where(... outneighbor(node, outneighbor_node)... ).inspect() id id_20 1 21 2 32 2 43 3 3>>> # 4. Use 'of' parameter to constrain the set of nodes to compute out-neighbors of>>> # Define a subset containing only nodes 2 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>>>>> # Get out-neighbors only of nodes in the subset>>> constrained_outneighbor = graph.outneighbor(of=subset)>>> model.select(node.id, outneighbor_node.id).where(constrained_outneighbor(node, outneighbor_node)).inspect() id id_20 2 31 2 42 3 3Notes:
The outneighbor() method, called with no parameters, computes and caches
the full outneighbor relationship, providing efficient reuse across multiple
calls to outneighbor(). In contrast, outneighbor(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the outneighbor relation is needed
across a program, outneighbor() is typically more efficient; this is the
typical case. Use outneighbor(of=subset) only when small subsets of the
outneighbor relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.common_neighbor()
Section titled “.common_neighbor()”Graph.common_neighbor( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship of common neighbor triplets.
A node w is a common neighbor of a pair of nodes u and v if
w is a neighbor of both u and v.
Parameters:
(fullbool, default:None) - IfTrue, computes common neighbors for all pairs of nodes in the graph. This computation can be expensive for large graphs, as the result can scale quadratically in the number of edges or cubically in the number of nodes. Mutually exclusive with other parameters. Default isNone.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the common neighbor computation: only common neighbors of node pairs where the first node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. Can only be used together with thefrom_parameter. When provided withfrom_, constrains the domain of the common neighbor computation: only common neighbors of node pairs where the first node is infrom_and the second node is intoare computed and returned. Default isNone.
(betweenRelationship, default:None) - A binary relationship containing pairs of nodes. When provided, constrains the domain of the common neighbor computation: only common neighbors for the specific node pairs in this relationship are computed and returned. Mutually exclusive with other parameters. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and one of their common neighbors.
Raises:
TypeError- Iffrom_,to, orbetweenis not aRelationship.ValueError- Iffullis provided with any other parameter. Ifbetweenis provided with any other parameter. Iffrom_is provided with any parameter other thanto. Iftois provided without thefrom_parameter. If none offull,from_, orbetweenis provided. Iffullis notTrueorNone. Iffrom_,to, orbetweenis not attached to the same model as the graph. Iffrom_,to, orbetweendoes not contain the graph’sNodeconcept. Iffrom_ortois not a unary relationship. Ifbetweenis not a binary relationship.
Relationship Schema:
common_neighbor(node_u, node_v, common_neighbor_node)
- node_u (Node): The first node in the pair.
- node_v (Node): The second node in the pair.
- common_neighbor_node (Node): The common neighbor of the pair.
Examples:
>>> from relationalai.semantics import Model, define, select>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Select the IDs from the common_neighbor relationship and inspect>>> u, v, w = Node.ref("u"), Node.ref("v"), Node.ref("w")>>> common_neighbor = graph.common_neighbor(full=True)>>> model.select(... u.id, v.id, w.id... ).where(... common_neighbor(u, v, w)... ).inspect() id id_2 id_30 1 1 21 1 3 22 1 4 23 2 2 14 2 2 35 2 2 46 2 3 37 2 3 48 2 4 39 3 1 210 3 2 311 3 2 412 3 3 213 3 3 314 3 3 415 3 4 216 3 4 317 4 1 218 4 2 319 4 3 220 4 3 321 4 4 222 4 4 3>>> # 4. Use 'from_' parameter to constrain the set of nodes to compute common neighbors for>>> # Define a subset containing only node 1>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 1).define(subset(node))>>>>>> # Get common neighbors only for pairs where first node is in subset>>> constrained_common_neighbor = graph.common_neighbor(from_=subset)>>> model.select(u.id, v.id, w.id).where(constrained_common_neighbor(u, v, w)).inspect() id id_2 id_30 1 1 21 1 3 22 1 4 2>>> # 5. Use both 'from_' and 'to' parameters to constrain the first two positions>>> subset_a = model.Relationship(f"{Node:node} is in subset_a")>>> subset_b = model.Relationship(f"{Node:node} is in subset_b")>>> model.where(node.id == 1).define(subset_a(node))>>> model.where(node.id == 3).define(subset_b(node))>>>>>> # Get common neighbors only where the first node is in subset_a and the second node is in subset_b>>> constrained_common_neighbor = graph.common_neighbor(from_=subset_a, to=subset_b)>>> model.select(u.id, v.id, w.id).where(constrained_common_neighbor(u, v, w)).inspect() id id_2 id_30 1 3 2>>> # 6. Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 1, node_b.id == 3).define(pairs(node_a, node_b))>>> model.where(node_a.id == 2, node_b.id == 4).define(pairs(node_a, node_b))>>>>>> # Get common neighbors only for the specific pairs (1, 3) and (2, 4)>>> constrained_common_neighbor = graph.common_neighbor(between=pairs)>>> model.select(u.id, v.id, w.id).where(constrained_common_neighbor(u, v, w)).inspect() id id_2 id_30 1 3 21 2 4 3Notes:
The common_neighbor(full=True) method computes and caches the full common
neighbor relationship for all pairs of nodes, providing efficient reuse across
multiple calls. This can be expensive as the result can contain O(|E|²) or
O(|V|³) tuples depending on graph density.
Calling common_neighbor() without arguments raises a ValueError,
to ensure awareness and explicit acknowledgement (full=True) of this cost.
In contrast, common_neighbor(from_=subset) constrains the computation to
tuples with the first position in the passed-in subset. The result is
not cached; it is specific to the call site. When a significant fraction of
the common neighbor relation is needed across a program, common_neighbor(full=True)
is typically more efficient. Use common_neighbor(from_=subset) only
when small subsets of the common neighbor relationship are needed
collectively across the program.
The to parameter can be used together with from_ to further
constrain the computation: common_neighbor(from_=subset_a, to=subset_b)
computes common neighbors only for node pairs where the first node is in
subset_a and the second node is in subset_b. (Since common_neighbor
is symmetric in its first two positions, using to without from_ would
be functionally redundant, and is not allowed.)
The between parameter provides another way to constrain the computation:
Unlike from_ and to, which allow you to independently constrain the first
and second positions in common_neighbor tuples to sets of nodes, between
allows you to constrain the first and second positions, jointly, to specific pairs
of nodes.
Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.degree()
Section titled “.degree()”Graph.degree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the degree of each node.
For directed graphs, a node’s degree is the sum of its indegree and outdegree. For undirected graphs, a self-loop contributes +2 to the node’s degree.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the degree computation: only degrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its degree.
Relationship Schema:
degree(node, node_degree)
- node (Node): The node.
- node_degree (Integer): The degree of the node.
Examples:
Undirected Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the degree of each node and inspect>>> node, node_degree = Node.ref("node"), Integer.ref("node_degree")>>> degree = graph.degree()>>> model.select(node.id, node_degree).where(degree(node, node_degree)).inspect() id node_degree0 1 11 2 32 3 33 4 1>>> # 4. Use 'of' parameter to constrain the set of nodes to compute degree of>>> # Define a subset containing only nodes 2 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>>>>> # Get degrees only of nodes in the subset>>> constrained_degree = graph.degree(of=subset)>>> model.select(node.id, node_degree).where(constrained_degree(node, node_degree)).inspect() id node_degree0 2 31 3 3Directed Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define the same nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the degree of each node and inspect>>> node, node_degree = Node.ref("node"), Integer.ref("node_degree")>>> degree = graph.degree()>>> model.select(node.id, node_degree).where(degree(node, node_degree)).inspect() id node_degree0 1 11 2 32 3 33 4 1>>> # 4. Use 'of' parameter to constrain the set of nodes to compute degree of>>> # Define a subset containing only nodes 1 and 4>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 4)).define(subset(node))>>>>>> # Get degrees only of nodes in the subset>>> constrained_degree = graph.degree(of=subset)>>> model.select(node.id, node_degree).where(constrained_degree(node, node_degree)).inspect() id node_degree0 1 11 4 1Notes:
The degree() method, called with no parameters, computes and caches
the full degree relationship, providing efficient reuse across multiple
calls to degree(). In contrast, degree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the degree relation is needed
across a program, degree() is typically more efficient; this is the
typical case. Use degree(of=subset) only when small subsets of the
degree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.indegree()
Section titled “.indegree()”Graph.indegree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the indegree of each node.
A node’s indegree is the number of incoming edges. For undirected graphs, a node’s indegree is identical to its degree, except self-loops contribute only +1 to a node’s indegree.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the indegree computation: only indegrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its indegree.
Relationship Schema:
indegree(node, node_indegree)
- node (Node): The node.
- node_indegree (Integer): The indegree of the node.
Examples:
Undirected Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n3, dst=n4),... )>>> # 3. Select the indegree of each node and inspect>>> node, node_indegree = Node.ref("node"), Integer.ref("node_indegree")>>> indegree = graph.indegree()>>> model.select(node.id, node_indegree).where(indegree(node, node_indegree)).inspect() id node_indegree0 1 11 2 22 3 33 4 1>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute indegree of>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_indegree = graph.indegree(of=subset)>>> model.select(node.id, node_indegree).where(constrained_indegree(node, node_indegree)).inspect() id node_indegree0 2 21 3 3Directed Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define the same nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n3, dst=n4)... )>>>>>> # 3. Select the indegree of each node and inspect>>> node, node_indegree = Node.ref("node"), Integer.ref("node_indegree")>>> indegree = graph.indegree()>>> model.select(node.id, node_indegree).where(indegree(node, node_indegree)).inspect() id node_indegree0 1 01 2 12 3 23 4 1>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute indegree of>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_indegree = graph.indegree(of=subset)>>> model.select(node.id, node_indegree).where(constrained_indegree(node, node_indegree)).inspect() id node_indegree0 2 11 3 2Notes:
The indegree() method, called with no parameters, computes and caches
the full indegree relationship, providing efficient reuse across multiple
calls to indegree(). In contrast, indegree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the indegree relation is needed
across a program, indegree() is typically more efficient; this is the
typical case. Use indegree(of=subset) only when small subsets of the
indegree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.outdegree()
Section titled “.outdegree()”Graph.outdegree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the outdegree of each node.
A node’s outdegree is the number of outgoing edges. For undirected graphs, a node’s outdegree is identical to its degree, except self-loops contribute only +1 to a node’s outdegree.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the outdegree computation: only outdegrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its outdegree.
Relationship Schema:
outdegree(node, node_outdegree)
- node (Node): The node.
- node_outdegree (Integer): The outdegree of the node.
Examples:
Undirected Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the outdegree of each node and inspect>>> node, node_outdegree = Node.ref("node"), Integer.ref("node_outdegree")>>> outdegree = graph.outdegree()>>> model.select(node.id, node_outdegree).where(outdegree(node, node_outdegree)).inspect() id node_outdegree0 1 11 2 32 3 23 4 1>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute outdegree of>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_outdegree = graph.outdegree(of=subset)>>> model.select(node.id, node_outdegree).where(constrained_outdegree(node, node_outdegree)).inspect() id node_outdegree0 2 31 3 2Directed Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define the same nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the outdegree of each node and inspect>>> node, node_outdegree = Node.ref("node"), Integer.ref("node_outdegree")>>> outdegree = graph.outdegree()>>> model.select(node.id, node_outdegree).where(outdegree(node, node_outdegree)).inspect() id node_outdegree0 1 11 2 22 3 13 4 0>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute outdegree of>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_outdegree = graph.outdegree(of=subset)>>> model.select(node.id, node_outdegree).where(constrained_outdegree(node, node_outdegree)).inspect() id node_outdegree0 2 21 3 1Notes:
The outdegree() method, called with no parameters, computes and caches
the full outdegree relationship, providing efficient reuse across multiple
calls to outdegree(). In contrast, outdegree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the outdegree relation is needed
across a program, outdegree() is typically more efficient; this is the
typical case. Use outdegree(of=subset) only when small subsets of the
outdegree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.weighted_degree()
Section titled “.weighted_degree()”Graph.weighted_degree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the weighted degree of each node.
A node’s weighted degree is the sum of the weights of all edges connected to it. For directed graphs, this is the sum of the weights of both incoming and outgoing edges. For undirected graphs, the weights of self-loops contribute twice to the node’s weighted degree. For unweighted graphs, all edge weights are considered to be 1.0.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the weighted degree computation: only weighted degrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its weighted degree.
Relationship Schema:
weighted_degree(node, node_weighted_degree)
- node (Node): The node.
- node_weighted_degree (Float): The weighted degree of the node.
Examples:
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed, weighted graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n2, dst=n1, weight=0.0),... Edge.new(src=n2, dst=n3, weight=1.0),... )>>>>>> # 3. Select the weighted degree of each node and inspect>>> node, node_weighted_degree = Node.ref("node"), Float.ref("node_weighted_degree")>>> weighted_degree = graph.weighted_degree()>>> model.select(... node.id, node_weighted_degree... ).where(... weighted_degree(node, node_weighted_degree)... ).inspect() id node_weighted_degree0 1 1.01 2 2.02 3 1.0>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted degree of>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_weighted_degree = graph.weighted_degree(of=subset)>>> model.select(... node.id, node_weighted_degree... ).where(... constrained_weighted_degree(node, node_weighted_degree)... ).inspect() id node_weighted_degree0 2 2.01 3 1.0Notes:
The weighted_degree() method, called with no parameters, computes and caches
the full weighted degree relationship, providing efficient reuse across multiple
calls to weighted_degree(). In contrast, weighted_degree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the weighted degree relation is needed
across a program, weighted_degree() is typically more efficient; this is the
typical case. Use weighted_degree(of=subset) only when small subsets of the
weighted degree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.weighted_indegree()
Section titled “.weighted_indegree()”Graph.weighted_indegree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the weighted indegree of each node.
A node’s weighted indegree is the sum of the weights of all incoming
edges. For undirected graphs, this is identical to weighted_degree,
except for self-loops. For weighted graphs, we assume edge weights are
non-negative. For unweighted graphs, all edge weights are considered
to be 1.0.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the weighted indegree computation: only weighted indegrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its weighted indegree.
Relationship Schema:
weighted_indegree(node, node_weighted_indegree)
- node (Node): The node.
- node_weighted_indegree (Float): The weighted indegree of the node.
Examples:
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed, weighted graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n2, dst=n1, weight=0.0),... Edge.new(src=n2, dst=n3, weight=1.0),... )>>>>>> # 3. Select the weighted indegree of each node and inspect>>> node, node_weighted_indegree = Node.ref("node"), Float.ref("node_weighted_indegree")>>> weighted_indegree = graph.weighted_indegree()>>> model.select(... node.id, node_weighted_indegree... ).where(... weighted_indegree(node, node_weighted_indegree)... ).inspect() id node_weighted_indegree0 1 0.01 2 1.02 3 1.0>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted indegree of>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>> constrained_weighted_indegree = graph.weighted_indegree(of=subset)>>> model.select(node.id, node_weighted_indegree).where(constrained_weighted_indegree(node, node_weighted_indegree)).inspect() id node_weighted_indegree0 2 1.01 3 1.0Notes:
The weighted_indegree() method, called with no parameters, computes and caches
the full weighted indegree relationship, providing efficient reuse across multiple
calls to weighted_indegree(). In contrast, weighted_indegree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the weighted indegree relation is needed
across a program, weighted_indegree() is typically more efficient; this is the
typical case. Use weighted_indegree(of=subset) only when small subsets of the
weighted indegree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.weighted_outdegree()
Section titled “.weighted_outdegree()”Graph.weighted_outdegree(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the weighted outdegree of each node.
A node’s weighted outdegree is the sum of the weights of all outgoing
edges. For undirected graphs, this is identical to weighted_degree,
except for self-loops. For unweighted graphs, all edge weights are
considered to be 1.0.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the weighted outdegree computation: only weighted outdegrees of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its weighted outdegree.
Relationship Schema:
weighted_outdegree(node, node_weighted_outdegree)
- node (Node): The node.
- node_weighted_outdegree (Float): The weighted outdegree of the node.
Examples:
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed, weighted graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n2, dst=n1, weight=0.0),... Edge.new(src=n2, dst=n3, weight=1.0),... )>>>>>> # 3. Select the weighted outdegree of each node and inspect>>> node, node_weighted_outdegree = Node.ref("node"), Float.ref("node_weighted_outdegree")>>> weighted_outdegree = graph.weighted_outdegree()>>> model.select(... node.id, node_weighted_outdegree... ).where(... weighted_outdegree(node, node_weighted_outdegree)... ).inspect() id node_weighted_outdegree0 1 1.01 2 1.02 3 0.0>>>>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted outdegree of>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 2)).define(subset(node))>>> constrained_weighted_outdegree = graph.weighted_outdegree(of=subset)>>> model.select(... node.id, node_weighted_outdegree... ).where(... constrained_weighted_outdegree(node, node_weighted_outdegree)... ).inspect() id node_weighted_outdegree0 1 1.01 2 1.0Notes:
The weighted_outdegree() method, called with no parameters, computes and caches
the full weighted outdegree relationship, providing efficient reuse across multiple
calls to weighted_outdegree(). In contrast, weighted_outdegree(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the weighted outdegree relation is needed
across a program, weighted_outdegree() is typically more efficient; this is the
typical case. Use weighted_outdegree(of=subset) only when small subsets of the
weighted outdegree relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.degree_centrality()
Section titled “.degree_centrality()”Graph.degree_centrality(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the degree centrality of each node.
Degree centrality is a measure of a node’s importance, defined as its degree (or weighted degree for weighted graphs) divided by the number of other nodes in the graph.
For unweighted graphs without self-loops, this value will be at most 1.0; unweighted graphs with self-loops might have nodes with a degree centrality greater than 1.0. Weighted graphs may have degree centralities greater than 1.0 as well.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the degree centrality computation: only degree centralities of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its degree centrality.
Relationship Schema:
degree_centrality(node, centrality)
- node (Node): The node.
- centrality (Float): The degree centrality of the node.
Examples:
Unweighted Graph Example
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an unweighted graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... )>>>>>> # 3. Select the degree centrality of each node and inspect>>> node, centrality = Node.ref("node"), Float.ref("centrality")>>> degree_centrality = graph.degree_centrality()>>> model.select(node.id, centrality).where(degree_centrality(node, centrality)).inspect() id centrality0 1 0.3333331 2 1.0000002 3 1.0000003 4 0.333333>>> # 4. Use 'of' parameter to constrain the set of nodes to compute degree centrality of>>> # Define a subset containing only nodes 2 and 3>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 2, node.id == 3)).define(subset(node))>>>>>> # Get degree centralities only of nodes in the subset>>> constrained_degree_centrality = graph.degree_centrality(of=subset)>>> model.select(node.id, centrality).where(constrained_degree_centrality(node, centrality)).inspect() id centrality0 2 1.01 3 1.0Weighted Graph Example
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a weighted graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2, weight=2.0),... Edge.new(src=n1, dst=n3, weight=0.5),... Edge.new(src=n2, dst=n3, weight=1.5),... )>>>>>> # 3. Select the degree centrality using weighted degrees>>> node, centrality = Node.ref("node"), Float.ref("centrality")>>> degree_centrality = graph.degree_centrality()>>> model.select(node.id, centrality).where(degree_centrality(node, centrality)).inspect() id centrality0 1 1.251 2 1.752 3 1.00Notes:
The degree_centrality() method, called with no parameters, computes and caches
the full degree centrality relationship, providing efficient reuse across multiple
calls to degree_centrality(). In contrast, degree_centrality(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the degree centrality relation is needed
across a program, degree_centrality() is typically more efficient; this is the
typical case. Use degree_centrality(of=subset) only when small subsets of the
degree centrality relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.eigenvector_centrality()
Section titled “.eigenvector_centrality()”Graph.eigenvector_centrality( tolerance: float = 1e-06, max_iter: int = 200, *, diagnostic_info: bool = False) -> Union[Relationship, Tuple[Relationship, Relationship]]Returns a binary relationship containing the eigenvector centrality of each node.
Parameters:
(tolerancefloat, default:1e-06) - The convergence tolerance for the eigenvector centrality calculation. Must be a non-negative float. Default is 1e-6.
(max_iterint, default:200) - The maximum number of iterations to perform in the power method. Default is 200.
(diagnostic_infobool, default:False) - IfTrue, returns diagnostic information alongside centrality scores. IfFalse(default), returns only centrality scores.
Returns:
-
Relationship or tuple of Relationships- Ifdiagnostic_infoisFalse(default), returns a binary relationship where each tuple represents a node and its eigenvector centrality.If
diagnostic_infoisTrue, returns a tuple of(eigenvector_centrality, diagnostic_info)where:eigenvector_centralityis the binary relationship described abovediagnostic_infois a binary relationship where each tuple contains a diagnostic name string and diagnostic value string.
Raises:
DirectedGraphNotSupported- If the graph is directed.
Relationship Schema:
When diagnostic_info=False (default):
eigenvector_centrality(node, centrality)
- node (Node): The node.
- centrality (Float): The eigenvector centrality of the node.
When diagnostic_info=True, returns two relationships:
eigenvector_centrality(node, centrality)as described above; anddiagnostic_info(diagnostic_name, diagnostic_value)
- diagnostic_name (String): The name of the diagnostic information.
- diagnostic_value (String): The value of the diagnostic information.
Examples:
Unweighted Graph Example
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an unweighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n4)... )>>>>>> # 3. Select the eigenvector centrality for each node and inspect>>> node, centrality = Node.ref("node"), Float.ref("centrality")>>> eigenvector_centrality = graph.eigenvector_centrality()>>> model.select(node.id, centrality).where(eigenvector_centrality(node, centrality)).inspect() id centrality0 1 0.3717481 2 0.6015012 3 0.6015013 4 0.371748Weighted Graph Example
>>> # 1. Set up a weighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2, weight=0.8),... Edge.new(src=n2, dst=n3, weight=0.7),... Edge.new(src=n3, dst=n3, weight=2.0),... Edge.new(src=n2, dst=n4, weight=1.5)... )>>>>>> # 3. Select the eigenvector centrality for each node and inspect>>> node, centrality = Node.ref("node"), Float.ref("centrality")>>> eigenvector_centrality = graph.eigenvector_centrality()>>> model.select(node.id, centrality).where(eigenvector_centrality(node, centrality)).inspect() id centrality0 1 0.1573271 2 0.4732502 3 0.8150243 4 0.294988Diagnostic Information
>>> # Repeat steps 1. and 2. from the previous example.>>> # 3. Get eigenvector centrality along with diagnostic information>>> eigenvector_centrality, diag_info = graph.eigenvector_centrality(diagnostic_info=True)>>> model.select(node.id, centrality).where(eigenvector_centrality(node, centrality)).inspect() id centrality0 1 0.1573271 2 0.4732502 3 0.8150243 4 0.294988>>> model.select(diag_info[0], diag_info[1]).where(diag_info[0] == "Termination status").inspect() diagnostic_name diagnostic_value0 Termination status ConvergedNotes:
Eigenvector centrality is a measure of the centrality or importance of a node in a graph based on finding the eigenvector associated with the top eigenvalue of the adjacency matrix. We use the power method to compute the eigenvector in our implementation. Note that the power method requires the adjacency matrix to be diagonalizable and will only converge if the absolute value of the top 2 eigenvalues is distinct. Thus, if the graph you are using has an adjacency matrix that is not diagonalizable or the top two eigenvalues are not distinct, this method will not converge. Specifically, bipartite graphs have symmetric spectra, so this computation will not converge. In cases where eigenvector centrality does not converge, consider PageRank.
In the case of weighted graphs, weights are assumed to be non-negative.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.betweenness_centrality()
Section titled “.betweenness_centrality()”Graph.betweenness_centrality(num_pivots: int = 100) -> RelationshipReturns a binary relationship containing the betweenness centrality of each node.
Betweenness centrality measures how important a node is based on how many times that node appears in a shortest path between any two nodes in the graph. Nodes with high betweenness centrality represent bridges between different parts of the graph. For example, in a network representing airports and flights between them, nodes with high betweenness centrality may identify “hub” airports that connect flights to different regions.
Parameters:
(num_pivotsint, default:100) - The number of pivot nodes to sample as BFS/SSSP sources for approximating betweenness centrality. A larger value yields a more accurate approximation at the cost of increased runtime. When set to |V|, the algorithm reduces to the exact Brandes algorithm. Default is 100.
Returns:
Relationship- A binary relationship where each tuple represents a node and its betweenness centrality.
Raises:
WeightedGraphNotSupported- If the graph is weighted.
Relationship Schema:
betweenness_centrality(node, centrality)
- node (Node): The node.
- centrality (Float): The betweenness centrality of the node.
Examples:
>>> from relationalai.semantics import Model, define, select, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Select the betweenness centrality for each node and inspect>>> node, centrality = Node.ref("node"), Float.ref("centrality")>>> betweenness_centrality = graph.betweenness_centrality()>>> model.select(node.id, centrality).where(betweenness_centrality(node, centrality)).inspect() id centrality0 1 0.01 2 3.02 3 0.03 4 0.0Notes:
Calculating betweenness centrality involves computing all of the shortest paths between
every pair of nodes in a graph and can be expensive to calculate exactly. The
betweenness_centrality relation 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 supports sampling-based approximation via the num_pivots parameter,
which controls the number of pivot nodes used in the computation. The default value of 100 pivots
yields a time complexity of O(100 * (|V|+|E|)). When the number of pivots equals or exceeds the
graph size, the algorithm reduces to the exact
Brandes algorithm,
with time complexity O(|V|(|V|+|E|)) for unweighted graphs.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.pagerank()
Section titled “.pagerank()”Graph.pagerank( damping_factor: float = 0.85, tolerance: float = 1e-06, max_iter: int = 200, *, diagnostic_info: bool = False) -> Union[Relationship, Tuple[Relationship, Relationship]]Returns a binary relationship containing the PageRank score of each node.
Parameters:
(damping_factorfloat, default:0.85) - The damping factor for the PageRank calculation. Must be in the range [0, 1). Default is 0.85.
(tolerancefloat, default:1e-06) - The convergence tolerance for the PageRank calculation. Must be a non-negative float. Default is 1e-6.
(max_iterint, default:200) - The maximum number of iterations for PageRank to run. Must be a positive integer. Default is 200.
(diagnostic_infobool, default:False) - IfTrue, returns diagnostic information alongside PageRank scores. IfFalse(default), returns only PageRank scores.
Returns:
-
Relationship or tuple of Relationships- Ifdiagnostic_infoisFalse(default), returns a binary relationship where each tuple represents a node and its PageRank score.If
diagnostic_infoisTrue, returns a tuple of(pagerank, diagnostic_info)where:pagerankis the binary relationship described abovediagnostic_infois a binary relationship where each tuple contains a diagnostic name string and diagnostic value string.
Relationship Schema:
When diagnostic_info=False (default):
pagerank(node, score)
- node (Node): The node.
- score (Float): The PageRank score of the node.
When diagnostic_info=True, returns two relationships:
pagerank(node, score)as described above; anddiagnostic_info(diagnostic_name, diagnostic_value)
- diagnostic_name (String): The name of the diagnostic information.
- diagnostic_value (String): The value of the diagnostic information.
Examples:
Unweighted Graph Example
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an unweighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4)... )>>>>>> # 3. Compute PageRank with default parameters and inspect>>> node, score = Node.ref("node"), Float.ref("score")>>> pagerank = graph.pagerank()>>> model.select(node.id, score).where(pagerank(node, score)).inspect() id score0 1 0.1557881 2 0.4174882 3 0.2709363 4 0.155788Weighted Graph Example with Configuration
>>> # 1. Set up a weighted, directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n1, dst=n3, weight=2.0),... Edge.new(src=n3, dst=n4, weight=3.0)... )>>>>>> # 3. Compute PageRank with custom parameters and inspect>>> node, score = Node.ref("node"), Float.ref("score")>>> pagerank = graph.pagerank()>>> model.select(node.id, score).where(pagerank(node, score)).inspect() id score0 1 0.2649041 2 0.1125562 3 0.3874443 4 0.235096Directed Example with Diagnostics
>>> # 1. Set up graph as above>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n1, dst=n3, weight=2.0),... Edge.new(src=n3, dst=n4, weight=3.0)... )>>>>>> # 2. Compute PageRank and retrieve results and diagnostic info>>> node, score = Node.ref("node"), Float.ref("score")>>> pagerank, diagnostic_info = graph.pagerank(diagnostic_info=True)>>>>>> # 3. Select the results>>> model.select(node.id, score).where(pagerank(node, score)).inspect() id score0 1 0.1617691 2 0.2076032 3 0.2534383 4 0.377190>>>>>> # 4. Select termination status from diagnostic info>>> key, value = String.ref("diagnostic_name"), String.ref("diagnostic_value")>>> model.select(key, value).where(diagnostic_info(key, value), key == "Termination status").inspect() diagnostic_name diagnostic_value0 Termination status Converged>>>>>> # 5. Select number of iterations from diagnostic info>>> model.select(key, value).where(diagnostic_info(key, value), key == "Iterations").inspect() diagnostic_name diagnostic_value0 Iterations 12>>>Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.triangle()
Section titled “.triangle()”Graph.triangle(*, full: Optional[bool] = None) -> RelationshipReturns a ternary relationship containing all triangles in the graph.
Unlike unique_triangle, this relationship contains all permutations
of the nodes for each triangle found.
Parameters:
(fullbool, default:None) - IfTrue, computes triangles for all triplets of nodes in the graph. This computation can be expensive for large graphs. Must be set toTrueto compute the full triangle relationship. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a triangle.
Raises:
ValueError- Iffullis not provided. Iffullis notTrue.
Relationship Schema:
triangle(node_a, node_b, node_c)
- node_a (Node): The first node in the triangle.
- node_b (Node): The second node in the triangle.
- node_c (Node): The third node in the triangle.
Examples:
Directed Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph with a 3-cycle>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n1),... )>>>>>> # 3. Select all triangles and inspect>>> a,b,c = Node.ref("a"), Node.ref("b"), Node.ref("c")>>> triangle = graph.triangle(full=True)>>> model.select(a.id, b.id, c.id).where(triangle(a, b, c)).inspect() id id_2 id_30 1 2 31 2 3 12 3 1 2Undirected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph with a triangle>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define the same nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n1),... )>>>>>> # 3. Select all triangles and inspect>>> a,b,c = Node.ref("a"), Node.ref("b"), Node.ref("c")>>> triangle = graph.triangle(full=True)>>> model.select(a.id, b.id, c.id).where(triangle(a, b, c)).inspect() id id_2 id_30 1 2 31 1 3 22 2 1 33 2 3 14 3 1 25 3 2 1Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.unique_triangle()
Section titled “.unique_triangle()”Graph.unique_triangle(*, full: Optional[bool] = None) -> RelationshipReturns a ternary relationship containing all unique triangles in the graph.
Parameters:
(fullbool, default:None) - IfTrue, computes unique triangles for all triplets of nodes in the graph. This computation can be expensive for large graphs. Must be set toTrueto compute the full unique_triangle relationship. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a unique triangle.
Raises:
ValueError- Iffullis not provided. Iffullis notTrue.
Relationship Schema:
unique_triangle(node_a, node_b, node_c)
- node_a (Node): The first node in the triangle.
- node_b (Node): The second node in the triangle.
- node_c (Node): The third node in the triangle.
Examples:
Directed Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n3),... Edge.new(src=n2, dst=n1),... Edge.new(src=n3, dst=n2),... Edge.new(src=n3, dst=n4),... Edge.new(src=n2, dst=n4),... )>>>>>> # 3. Select the unique triangles and inspect>>> a,b,c = Node.ref("a"), Node.ref("b"), Node.ref("c")>>> unique_triangle = graph.unique_triangle(full=True)>>> model.select(a.id, b.id, c.id).where(unique_triangle(a, b, c)).inspect() id id_2 id_30 3 2 1Undirected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges forming two triangles>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n1), # Forms triangle (1,2,3)... Edge.new(src=n2, dst=n4),... Edge.new(src=n3, dst=n4), # Forms triangle (2,3,4)... )>>>>>> # 3. Select the unique triangles and inspect>>> a,b,c = Node.ref("a"), Node.ref("b"), Node.ref("c")>>> unique_triangle = graph.unique_triangle(full=True)>>> model.select(a.id, b.id, c.id).where(unique_triangle(a, b, c)).inspect() id id_2 id_30 3 1 21 4 3 2Notes:
This relationship contains triples of nodes (a, b, c) representing
unique triangles.
For undirected graphs, uniqueness of each triangle is guaranteed
because the relationship only contains triples where a < b < c.
For directed graphs, the triple (a, b, c) represents a triangle
with directed edges (a, b), (b, c), and (c, a), and is unique up
to ordering of the nodes and direction of the edges. unique_triangle
only contains triples such that a < b, a < c, and b != c. For
example, the triples (1, 2, 3) and (1, 3, 2) represent two unique
directed triangles.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.num_triangles()
Section titled “.num_triangles()”Graph.num_triangles() -> RelationshipReturns a unary relationship containing the total number of unique triangles in the graph.
This method counts the number of unique triangles as defined by the
unique_triangle relationship, which has different uniqueness
constraints for directed and undirected graphs.
Returns:
Relationship- A unary relationship containing the total number of unique triangles in the graph.
Relationship Schema:
num_triangles(count)
- count (Integer): The total number of unique triangles in the graph.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n3),... Edge.new(src=n2, dst=n1),... Edge.new(src=n3, dst=n2),... Edge.new(src=n3, dst=n4),... Edge.new(src=n1, dst=n4),... )>>>>>> # 3. Inspect the number of unique triangles>>> graph.num_triangles().inspect() num_triangles0 1Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.triangle_count()
Section titled “.triangle_count()”Graph.triangle_count(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the number of unique triangles each node belongs to.
A triangle is a set of three nodes where each node has a directed or undirected edge to the other two nodes, forming a 3-cycle.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the triangle count computation: only triangle counts of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and the number of unique triangles it is a part of.
Relationship Schema:
triangle_count(node, count)
- node (Node): The node.
- count (Integer): The number of unique triangles the node belongs to.
Examples:
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5 = [Node.new(id=i) for i in range(1, 6)]>>> model.define(n1, n2, n3, n4, n5)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n3, dst=n1),... Edge.new(src=n3, dst=n4),... Edge.new(src=n5, dst=n1),... )>>>>>> # 3. Select the triangle count for each node and inspect>>> node, count = Node.ref("node"), Integer.ref("count")>>> triangle_count = graph.triangle_count()>>> model.select(node.id, count).where(triangle_count(node, count)).inspect() id count0 1 11 2 12 3 13 4 04 5 0>>> # 4. Use 'of' parameter to constrain the set of nodes to compute triangle counts of>>> # Define a subset containing only nodes 1 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 3)).define(subset(node))>>>>>> # Get triangle counts only of nodes in the subset>>> constrained_triangle_count = graph.triangle_count(of=subset)>>> model.select(node.id, count).where(constrained_triangle_count(node, count)).inspect() id count0 1 11 3 1Notes:
The triangle_count() method, called with no parameters, computes and caches
the full triangle count relationship, providing efficient reuse across multiple
calls to triangle_count(). In contrast, triangle_count(of=subset) computes a
constrained relationship specific to the passed-in subset and that
call site. When a significant fraction of the triangle count relation is needed
across a program, triangle_count() is typically more efficient; this is the
typical case. Use triangle_count(of=subset) only when small subsets of the
triangle count relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.local_clustering_coefficient()
Section titled “.local_clustering_coefficient()”Graph.local_clustering_coefficient(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship containing the local clustering coefficient of each node.
The local clustering coefficient quantifies how close a node’s neighbors are to forming a clique (a complete subgraph). The coefficient ranges from 0.0 to 1.0, where 0.0 indicates none of the neighbors have edges directly connecting them, and 1.0 indicates all neighbors have edges directly connecting them.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the local clustering coefficient computation: only coefficients of nodes in this relationship are computed and returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and its local clustering coefficient.
Raises:
NotImplementedError- If the graph is directed.
Relationship Schema:
local_clustering_coefficient(node, coefficient)
- node (Node): The node.
- coefficient (Float): The local clustering coefficient of the node.
Examples:
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5 = [Node.new(id=i) for i in range(1, 6)]>>> model.define(n1, n2, n3, n4, n5)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n1, dst=n3),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n4),... Edge.new(src=n4, dst=n5),... Edge.new(src=n2, dst=n4),... Edge.new(src=n3, dst=n5),... )>>>>>> # 3. Select the local clustering coefficient for each node>>> node, coeff = Node.ref("node"), Float.ref("coeff")>>> lcc = graph.local_clustering_coefficient()>>> model.select(node.id, coeff).where(lcc(node, coeff)).inspect() id coeff0 1 1.0000001 2 0.6666672 3 0.5000003 4 0.6666674 5 1.000000>>> # 4. Use 'of' parameter to constrain the set of nodes to compute local clustering coefficients of>>> # Define a subset containing only nodes 1 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 3)).define(subset(node))>>>>>> # Get local clustering coefficients only of nodes in the subset>>> constrained_lcc = graph.local_clustering_coefficient(of=subset)>>> model.select(node.id, coeff).where(constrained_lcc(node, coeff)).inspect() id coeff0 1 1.01 3 0.5Notes:
The local clustering coefficient for node v is::
(2 * num_neighbor_edges(v)) / (ext_degree(v) * (ext_degree(v) - 1))
where num_neighbor_edges(v) is the number of edges between
the neighbors of node v, and ext_degree(v) is the degree of the
node excluding self-loops. If ext_degree(v) is less than 2,
the local clustering coefficient is 0.0.
The local_clustering_coefficient() method, called with no parameters, computes
and caches the full local clustering coefficient relationship, providing efficient
reuse across multiple calls to local_clustering_coefficient(). In contrast,
local_clustering_coefficient(of=subset) computes a constrained relationship
specific to the passed-in subset and that call site. When a significant fraction
of the local clustering coefficient relation is needed across a program,
local_clustering_coefficient() is typically more efficient; this is the typical
case. Use local_clustering_coefficient(of=subset) only when small subsets of the
local clustering coefficient relationship are needed collectively across the program.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.average_clustering_coefficient()
Section titled “.average_clustering_coefficient()”Graph.average_clustering_coefficient() -> RelationshipReturns a unary relationship containing the average clustering coefficient of the graph.
The average clustering coefficient is the average of the local clustering coefficients of the nodes in a graph.
Returns:
Relationship- A unary relationship containing the average clustering coefficient of the graph.
Raises:
NotImplementedError- If the graph is directed.
Relationship Schema:
average_clustering_coefficient(coefficient)
- coefficient (Float): The average clustering coefficient of the graph.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5 = [Node.new(id=i) for i in range(1, 6)]>>> model.define(n1, n2, n3, n4, n5)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n1, dst=n3),... Edge.new(src=n1, dst=n4),... Edge.new(src=n1, dst=n5),... Edge.new(src=n2, dst=n3),... )>>>>>> # 3. Inspect the average clustering coefficient>>> graph.average_clustering_coefficient().inspect() coefficient0 0.433333Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.reachable()
Section titled “.reachable()”Graph.reachable( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a binary relationship of pairs of nodes (u, v) where v is reachable from u.
A node v is considered reachable from a node u if there is a path
of edges from u to v.
Parameters:
(fullbool, default:None) - IfTrue, computes reachability for all pairs of nodes in the graph. This computation can be expensive for large graphs. Must be set toTrueto compute the full reachable relationship. Cannot be used with other parameters.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the first position of the reachable computation: only reachability from nodes in this relationship are returned. Cannot be used with other parameters.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the second position of the reachable computation: only reachability to nodes in this relationship are returned. Cannot be used with other parameters.
(betweenRelationship, default:None) - Not yet supported for reachable. If provided, raises an error. If you need this capability, please reach out.
Returns:
Relationship- A binary relationship where each tuple represents a node and a node that is reachable from it.
Relationship Schema:
reachable(from_node, to_node)
- from_node (Node): The node from which the path originates.
- to_node (Node): The node that is reachable from the first node.
Examples:
Directed Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... )>>>>>> # 3. Select all reachable pairs and inspect>>> from_node, to_node = Node.ref("start"), Node.ref("end")>>> reachable = graph.reachable(full=True)>>> model.select(from_node.id, to_node.id).where(reachable(from_node, to_node)).inspect() id id_20 1 11 1 22 2 23 3 24 3 3>>> # 4. Use 'from_' parameter to get reachability from specific nodes>>> # Define a subset containing nodes 1 and 3>>> subset = model.Relationship(f"{Node:node} is in from subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 3)).define(subset(node))>>>>>> # Get reachability from nodes in the subset to all other nodes>>> reachable_from = graph.reachable(from_=subset)>>> model.select(from_node.id, to_node.id).where(... reachable_from(from_node, to_node)... ).inspect() id id_20 1 11 1 22 3 23 3 3>>> # 5. Use 'to' parameter to get reachability to specific nodes>>> # Define a different subset containing node 2>>> to_subset = model.Relationship(f"{Node:node} is in to subset")>>> node = Node.ref()>>> model.where(node.id == 2).define(to_subset(node))>>>>>> # Get reachability from all nodes to node 2>>> reachable_to = graph.reachable(to=to_subset)>>> model.select(from_node.id, to_node.id).where(... reachable_to(from_node, to_node)... ).inspect() id id_20 1 21 2 22 3 2Undirected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define the same nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... )>>>>>> # 3. Select all reachable pairs and inspect>>> from_node, to_node = Node.ref("start"), Node.ref("end")>>> reachable = graph.reachable(full=True)>>> model.select(from_node.id, to_node.id).where(reachable(from_node, to_node)).inspect() id id_20 1 11 1 22 1 33 2 14 2 25 2 36 3 17 3 28 3 3Notes:
The reachable(full=True) method computes and caches the full
reachable relationship, providing efficient reuse across multiple calls. In
contrast, reachable(from_=subset) or reachable(to=subset)
compute constrained relationships specific to the passed-in subset and call
site. When a significant fraction of the reachable relation is needed,
reachable(full=True) is typically more efficient. Use constrained
variants only when small subsets are needed.
In directed graphs, the reachable relationship is asymmetric: node B
may be reachable from node A without node A being reachable from node B. This
asymmetry means that from_ and to parameters have distinct,
non-interchangeable meanings.
Important: Simultaneous use of from_ and to parameters is not
yet supported. The between parameter is also not yet supported. If
you need these capabilities, please reach out.
There is a slight difference between transitive closure and
reachable. The transitive closure of a binary relation E is the
smallest relation that contains E and is transitive. When E is the
edge set of a graph, the transitive closure of E does not include
(u, u) if u is isolated. reachable is a different binary
relation in which any node u is always reachable from u. In
particular, transitive closure is a more general concept than
reachable.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.distance()
Section titled “.distance()”Graph.distance( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship containing the shortest path length between pairs of nodes.
This method computes the shortest path length between all pairs of reachable nodes. The calculation depends on whether the graph is weighted:
- For unweighted graphs, the length is the number of edges in the shortest path.
- For weighted graphs, the length is the sum of edge weights along the shortest path. Edge weights are assumed to be non-negative.
Parameters:
(fullbool, default:None) - IfTrue, computes distances for all pairs of nodes in the graph. This computation can be expensive for large graphs. Must be set toTrueto compute the full distance relationship. Cannot be used with other parameters.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the first position (start_node) of the distance computation: only distances from nodes in this relationship are returned. Cannot be used with other parameters.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the second position (end_node) of the distance computation: only distances to nodes in this relationship are returned. Cannot be used with other parameters.
(betweenRelationship, default:None) - Not yet supported for distance. If provided, raises an error. If you need this capability, please reach out.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and the shortest path length between them.
Relationship Schema:
distance(start_node, end_node, length)
- start_node (Node): The start node of the path.
- end_node (Node): The end node of the path.
- length (Integer or Float): The shortest path length, returned as an Integer for unweighted graphs and a Float for weighted graphs.
Examples:
Unweighted Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an unweighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... )>>>>>> # 3. Select the shortest path length between all pairs of nodes>>> start, end = Node.ref("start"), Node.ref("end")>>> length = Integer.ref("length")>>> dist = graph.distance(full=True)>>> model.select(start.id, end.id, length).where(dist(start, end, length)).inspect() id id_2 length0 1 1 01 1 2 12 1 3 23 1 4 24 2 1 15 2 2 06 2 3 17 2 4 18 3 1 29 3 2 110 3 3 011 3 4 212 4 1 213 4 2 114 4 3 215 4 4 0>>> # 4. Use 'from_' parameter to get distances from specific nodes>>> # Define a subset containing nodes 1 and 3>>> subset = model.Relationship(f"{Node:node} is in from subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 3)).define(subset(node))>>>>>> # Get distances from nodes in the subset to all other nodes>>> dist_from = graph.distance(from_=subset)>>> model.select(start.id, end.id, length).where(... dist_from(start, end, length)... ).inspect() id id_2 length0 1 1 01 1 2 12 1 3 23 1 4 24 3 1 25 3 2 16 3 3 07 3 4 2>>> # 5. Use 'to' parameter to get distances to specific nodes>>> # Define a different subset containing node 4>>> to_subset = model.Relationship(f"{Node:node} is in to subset")>>> node = Node.ref()>>> model.where(node.id == 4).define(to_subset(node))>>>>>> # Get distances from all nodes to node 4>>> dist_to = graph.distance(to=to_subset)>>> model.select(start.id, end.id, length).where(... dist_to(start, end, length)... ).inspect() id id_2 length0 1 4 21 2 4 12 3 4 23 4 4 0Weighted Graph Example
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a weighted, directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2, weight=2.0),... Edge.new(src=n1, dst=n3, weight=0.5),... Edge.new(src=n2, dst=n1, weight=1.5),... )>>>>>> # 3. Select the shortest path length between all pairs of nodes>>> start, end = Node.ref("start"), Node.ref("end")>>> length = Float.ref("length")>>> dist = graph.distance(full=True)>>> model.select(start.id, end.id, length).where(dist(start, end, length)).inspect() id id_2 length0 1 1 0.01 1 2 2.02 1 3 0.53 2 1 1.54 2 2 0.05 2 3 2.06 3 3 0.0Notes:
The distance(full=True) method computes and caches the full distance
relationship, providing efficient reuse across multiple calls. In contrast,
distance(from_=subset) or distance(to=subset) compute constrained
relationships specific to the passed-in subset and call site. When a
significant fraction of the distance relation is needed, distance(full=True)
is typically more efficient. Use constrained variants only when small
subsets are needed.
In directed graphs, the distance relationship is asymmetric: the
distance from node A to node B may differ from the distance from node B
to node A. This asymmetry means that from_ and to parameters
have distinct, non-interchangeable meanings.
Important: Simultaneous use of from_ and to parameters is not
yet supported. The between parameter is also not yet supported. If
you need these capabilities, please reach out.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.weakly_connected_component()
Section titled “.weakly_connected_component()”Graph.weakly_connected_component(*, of: Optional[Relationship] = None) -> RelationshipReturns a binary relationship that maps each node to its weakly connected component.
A weakly connected component is a subgraph where for every pair of nodes, there is a path between them in the underlying undirected graph. For undirected graphs, this is equivalent to finding the connected components.
Parameters:
(ofRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the weakly connected component computation: only component memberships of nodes in this relationship are returned.
Returns:
Relationship- A binary relationship where each tuple represents a node and the identifier of the component it belongs to.
Relationship Schema:
weakly_connected_component(node, component_id)
- node (Node): The node.
- component_id (Node): The identifier for the component.
Examples:
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3 = [Node.new(id=i) for i in range(1, 4)]>>> model.define(n1, n2, n3)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... )>>>>>> # 3. Select the component ID for each node and inspect>>> node, component_id = Node.ref("node"), Node.ref("component_id")>>> wcc = graph.weakly_connected_component()>>> model.select(node.id, component_id.id).where(wcc(node, component_id)).inspect() id id_20 1 31 2 32 3 3>>> # 4. Use 'of' parameter to constrain computation to subset of nodes>>> # Define a subset containing only nodes 1 and 3>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(model.union(node.id == 1, node.id == 3)).define(subset(node))>>>>>> # Get component membership only for nodes in the subset>>> constrained_wcc = graph.weakly_connected_component(of=subset)>>> model.select(node.id, component_id.id).where(... constrained_wcc(node, component_id)... ).inspect() id id_20 1 31 3 3Notes:
The component_id is the node with the minimum ID within each
component.
The weakly_connected_component() method, called with no parameters,
computes and caches the full weakly connected component relationship,
providing efficient reuse across multiple calls to
weakly_connected_component().
In contrast, weakly_connected_component(of=subset) computes a constrained
relationship specific to the passed-in subset and that call site.
Note that the constrained computation requires working over all nodes
in the components containing the nodes in subset. When that set
of nodes constitutes an appreciable fraction of the graph, the constrained
computation may be less efficient than computing the full relationship.
Use weakly_connected_component(of=subset) only when small subsets of
the weakly connected component relationship are needed collectively
across the program, and the associated components cover only a small
part of the graph.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.diameter_range()
Section titled “.diameter_range()”Graph.diameter_range() -> Tuple[Relationship, Relationship]Estimates the graph diameter and returns it as a minimum and maximum bound.
Returns:
-
(Relationship, Relationship)- A tuple containing two unaryRelationshipobjects: (min_bound,max_bound).min_bound: A relationship of the formmin_bound(value)wherevalue(Integer) is the lower bound of the estimated diameter.max_bound: A relationship of the formmax_bound(value)wherevalue(Integer) is the upper bound of the estimated diameter.
Relationship Schema:
min_bound(value)
- value (Integer): The lower bound of the estimated diameter.
max_bound(value)
- value (Integer): The upper bound of the estimated diameter.
Examples:
Connected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a connected, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Get the diameter range and inspect each bound>>> min_bound, max_bound = graph.diameter_range()>>> min_bound.inspect() value0 3>>> max_bound.inspect() value0 4Disconnected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a disconnected, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5 = [Node.new(id=i) for i in range(1, 6)]>>> model.define(n1, n2, n3, n4, n5)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n4),... Edge.new(src=n4, dst=n5),... )>>>>>> # 3. The range reflects the largest component {3, 4, 5}>>> min_bound, max_bound = graph.diameter_range()>>> min_bound.inspect() value0 2>>> max_bound.inspect() value0 2Notes:
This method is used to determine the range of possible diameter values in the graph. This is done by selecting a number of random nodes in the graph and taking the maximum of all shortest paths lengths to the rest of the graph. This gives a range per node. Then, the intersection of these ranges is taken and the final range is returned.
For disconnected graphs, diameter_range returns the diameter range
of the largest (weakly) connected component. This behavior deviates
from many graph theory resources, which typically define the diameter
of a disconnected graph as infinity.
Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.is_connected()
Section titled “.is_connected()”Graph.is_connected() -> RelationshipReturns a unary relationship containing whether the graph is connected.
A graph is considered connected if every node is reachable from every other node in the underlying undirected graph.
Returns:
Relationship- A unary relationship containing a boolean indicator of whether the graph is connected.
Relationship Schema:
is_connected(connected)
- connected (Boolean): Whether the graph is connected.
Examples:
Connected Graph Example
>>> from relationalai.semantics import Model>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a connected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Select and inspect the relation>>> model.select(graph.is_connected()).inspect() is_connected0 TrueDisconnected Graph Example
>>> from relationalai.semantics import Model, define, select>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a disconnected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5 = [Node.new(id=i) for i in range(1, 6)]>>> model.define(n1, n2, n3, n4, n5)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n3, dst=n2),... Edge.new(src=n4, dst=n5), # This edge creates a separate component... )>>>>>> # 3. Select and inspect the relation>>> model.select(graph.is_connected()).inspect() is_connected0 FalseNotes:
An empty graph is considered connected.
Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.jaccard_similarity()
Section titled “.jaccard_similarity()”Graph.jaccard_similarity( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship containing the Jaccard similarity for pairs of nodes.
The Jaccard similarity is a measure between two nodes that ranges from 0.0 to 1.0, where higher values indicate greater similarity.
Parameters:
(fullbool, default:None) - IfTrue, computes the Jaccard similarity for all pairs of nodes in the graph. This computation can be expensive for large graphs, as the result can scale quadratically in the number of nodes. Mutually exclusive with other parameters. Default isNone.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the Jaccard similarity computation: only Jaccard similarity scores for node pairs where the first node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. Can only be used together with thefrom_parameter. When provided withfrom_, constrains the domain of the Jaccard similarity computation: only Jaccard similarity scores for node pairs where the first node is infrom_and the second node is intoare computed and returned. Default isNone.
(betweenRelationship, default:None) - A binary relationship containing pairs of nodes. When provided, constrains the domain of the Jaccard similarity computation: only Jaccard similarity scores for the specific node pairs in this relationship are computed and returned. Mutually exclusive with other parameters. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and their Jaccard similarity.
Raises:
DirectedGraphNotSupported- If the graph is directed.TypeError- Iffrom_,to, orbetweenis not aRelationship.ValueError- Iffullis provided with any other parameter. Ifbetweenis provided with any other parameter. Iffrom_is provided with any parameter other thanto. Iftois provided without thefrom_parameter. If none offull,from_, orbetweenis provided. Iffullis notTrueorNone. Iffrom_,to, orbetweenis not attached to the same model as the graph. Iffrom_,to, orbetweendoes not contain the graph’sNodeconcept. Iffrom_ortois not a unary relationship. Ifbetweenis not a binary relationship.
Relationship Schema:
jaccard_similarity(node_u, node_v, score)
- node_u (Node): The first node in the pair.
- node_v (Node): The second node in the pair.
- score (Float): The Jaccard similarity of the pair.
Examples:
Unweighted Graph Examples
Undirected Graph
>>> from relationalai.semantics import Model, define, select, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>> u, v, score = Node.ref("u"), Node.ref("v"), Float.ref("score")>>> jaccard_similarity = graph.jaccard_similarity(full=True)>>> model.select(score).where(jaccard_similarity(u, v, score), u.id == 2, v.id == 4).inspect() score0 0.25Weighted Graph Example
>>> from relationalai.semantics import Model, define, select, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a weighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges with weights>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.6),... Edge.new(src=n1, dst=n3, weight=1.4),... Edge.new(src=n2, dst=n3, weight=0.46),... Edge.new(src=n3, dst=n4, weight=2.5),... )>>>>>> # 3. Select the weighted Jaccard similarity for the pair (1, 2)>>> u, v, score = Node.ref("u"), Node.ref("v"), Float.ref("score")>>> jaccard_similarity = graph.jaccard_similarity(full=True)>>> model.select(score).where(jaccard_similarity(u, v, score), u.id == 1, v.id == 2).inspect() score0 0.1Domain Constraint Examples
>>> from relationalai.semantics import where>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>> u, v, score = Node.ref("u"), Node.ref("v"), Float.ref("score")>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 2).define(subset(node))>>>>>> # Get Jaccard similarity scores only for pairs where first node is in subset>>> # Use 'from_' parameter to constrain the set of nodes for the first position>>> # Using the same undirected unweighted graph from above>>> constrained_jaccard_similarity = graph.jaccard_similarity(from_=subset)>>> model.select(u.id, v.id, score).where(constrained_jaccard_similarity(u, v, score)).inspect() id id_2 score0 2 2 1.001 2 3 0.502 2 4 0.25>>> # Use both 'from_' and 'to' parameters to constrain both positions>>> from_subset = model.Relationship(f"{Node:node} is in from_subset")>>> to_subset = model.Relationship(f"{Node:node} is in to_subset")>>> model.where(node.id == 2).define(from_subset(node))>>> model.where(node.id == 4).define(to_subset(node))>>>>>> # Get Jaccard similarity scores only where first node is in from_subset and second node is in to_subset>>> constrained_jaccard_similarity = graph.jaccard_similarity(from_=from_subset, to=to_subset)>>> model.select(u.id, v.id, score).where(constrained_jaccard_similarity(u, v, score)).inspect() id id_2 score0 2 4 0.25>>> # Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 2, node_b.id == 4).define(pairs(node_a, node_b))>>> model.where(node_a.id == 3, node_b.id == 4).define(pairs(node_a, node_b))>>>>>> # Get Jaccard similarity scores only for the specific pairs (2, 4) and (3, 4)>>> constrained_jaccard_similarity = graph.jaccard_similarity(between=pairs)>>> model.select(u.id, v.id, score).where(constrained_jaccard_similarity(u, v, score)).inspect() id id_2 score0 2 4 0.2500001 3 4 0.666667Notes:
The unweighted Jaccard similarity between two nodes is the ratio of the size of the intersection to the size of the union of their neighbors.
The weighted Jaccard similarity considers the weights of the edges. The definition used here is taken from the reference noted below. It is the ratio between two quantities:
- Numerator: For every other node
win the graph, find the minimum of the edge weights(u, w)and(v, w), and sum these minimums. - Denominator: For every other node
win the graph, find the maximum of the edge weights(u, w)and(v, w), and sum these maximums.
If an edge does not exist, its weight is considered 0.0. This can be better understood via the following calculation for the weighted example below.
| node id | edge weights to node 1 | edge weights to node 2 | min | max |
|---|---|---|---|---|
| 1 | 0.0 | 1.6 | 0.0 | 1.6 |
| 2 | 1.6 | 0.0 | 0.0 | 1.6 |
| 3 | 1.4 | 0.46 | 0.46 | 1.4 |
| 4 | 0.0 | 0.0 | 0.0 | 0.0 |
The weighted Jaccard similarity between node 1 and 2 is then:
0.46 / (1.6 + 1.6 + 1.4) = 0.1.
Edge weights are assumed to be non-negative, so the neighborhood vectors contain only non-negative elements. Therefore, the Jaccard similarity score is always between 0.0 and 1.0, inclusive.
The jaccard_similarity(full=True) method computes and caches
the full Jaccard similarity relationship for all pairs of nodes,
providing efficient reuse across multiple calls. This can be expensive
as the result can contain O(|V|²) tuples.
Calling jaccard_similarity() without arguments raises a ValueError,
to ensure awareness and explicit acknowledgement (full=True) of this cost.
In contrast, jaccard_similarity(from_=subset) constrains the computation to
tuples with the first position in the passed-in subset. The result is
not cached; it is specific to the call site. When a significant fraction of
the Jaccard similarity relation is needed across a program,
jaccard_similarity(full=True) is typically more efficient. Use
jaccard_similarity(from_=subset) only when small subsets of
the Jaccard similarity relationship are needed
collectively across the program.
The to parameter can be used together with from_ to further
constrain the computation: jaccard_similarity(from_=subset_a, to=subset_b)
computes Jaccard similarity scores only for node pairs where the first node is in
subset_a and the second node is in subset_b. (Since jaccard_similarity
is symmetric in its first two positions, using to without from_ would
be functionally redundant, and is not allowed.)
The between parameter provides another way to constrain the computation.
Unlike from_ and to, which allow you to independently constrain the first
and second positions in jaccard_similarity tuples to sets of nodes, between
allows you constrain the first and second positions, jointly, to specific pairs
of nodes.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.cosine_similarity()
Section titled “.cosine_similarity()”Graph.cosine_similarity( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship containing the cosine similarity for pairs of nodes.
The cosine similarity measures the similarity between two nodes based on the angle between their neighborhood vectors. The score ranges from 0.0 to 1.0, inclusive, where 1.0 indicates identical sets of neighbors.
Parameters:
(fullbool, default:None) - IfTrue, computes the cosine similarity for all pairs of nodes in the graph. This computation can be expensive for large graphs, as the result can scale quadratically in the number of nodes. Mutually exclusive with other parameters. Default isNone.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the cosine similarity computation: only cosine similarity scores for node pairs where the first node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. Can only be used together with thefrom_parameter. When provided withfrom_, constrains the domain of the cosine similarity computation: only cosine similarity scores for node pairs where the first node is infrom_and the second node is intoare computed and returned. Default isNone.
(betweenRelationship, default:None) - A binary relationship containing pairs of nodes. When provided, constrains the domain of the cosine similarity computation: only cosine similarity scores for the specific node pairs in this relationship are computed and returned. Mutually exclusive with other parameters. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and their cosine similarity.
Raises:
DirectedGraphNotSupported- If the graph is directed.TypeError- Iffrom_,to, orbetweenis not aRelationship.ValueError- Iffullis provided with any other parameter. Ifbetweenis provided with any other parameter. Iffrom_is provided with any parameter other thanto. Iftois provided without thefrom_parameter. If none offull,from_, orbetweenis provided. Iffullis notTrueorNone. Iffrom_,to, orbetweenis not attached to the same model as the graph. Iffrom_,to, orbetweendoes not contain the graph’sNodeconcept. Iffrom_ortois not a unary relationship. Ifbetweenis not a binary relationship.
Relationship Schema:
cosine_similarity(node_u, node_v, score)
- node_u (Node): The first node in the pair.
- node_v (Node): The second node in the pair.
- score (Float): The cosine similarity of the pair.
Examples:
Unweighted Graph Examples
Undirected Graph
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>> u, v, score = Node.ref("u"), Node.ref("v"), Float.ref("score")>>> cosine_similarity = graph.cosine_similarity(full=True)>>> model.select(score).where(cosine_similarity(u, v, score), u.id == 2, v.id == 4).inspect() score0 0.408248Weighted Graph Examples
Undirected Graph
>>> from relationalai.semantics import Model, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>> n1, n2, n3, n4, n13, n14 = [Node.new(id=i) for i in [1, 2, 3, 4, 13, 14]]>>> model.define(n1, n2, n3, n4, n13, n14)>>> model.define(... Edge.new(src=n1, dst=n2, weight=1.6),... Edge.new(src=n1, dst=n3, weight=1.4),... Edge.new(src=n2, dst=n3, weight=1.2),... Edge.new(src=n3, dst=n4, weight=2.5),... Edge.new(src=n14, dst=n13, weight=1.0),... )>>> u, v, score = Node.ref("u"), Node.ref("v"), Float.ref("score")>>> cosine_similarity = graph.cosine_similarity(full=True)>>> model.select(score).where(cosine_similarity(u, v, score), u.id == 1, v.id == 2).inspect() score0 0.395103Domain Constraint Examples
>>> # Use 'from_' parameter to constrain the set of nodes for the first position>>> # Using the same undirected weighted graph from above>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 2).define(subset(node))>>>>>> # Get cosine similarity scores only for pairs where first node is in subset>>> constrained_cosine_similarity = graph.cosine_similarity(from_=subset)>>> model.select(u.id, v.id, score).where(constrained_cosine_similarity(u, v, score)).inspect() id id_2 score0 2 1 0.3951031 2 2 1.0000002 2 3 0.3605413 2 4 0.600000>>> # Use both 'from_' and 'to' parameters to constrain both positions>>> from_subset = model.Relationship(f"{Node:node} is in from_subset")>>> to_subset = model.Relationship(f"{Node:node} is in to_subset")>>> model.where(node.id == 2).define(from_subset(node))>>> model.where(node.id == 1).define(to_subset(node))>>>>>> # Get cosine similarity scores only where first node is in from_subset and second node is in to_subset>>> constrained_cosine_similarity = graph.cosine_similarity(from_=from_subset, to=to_subset)>>> model.select(u.id, v.id, score).where(constrained_cosine_similarity(u, v, score)).inspect() id id_2 score0 2 1 0.395103>>> # Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 1, node_b.id == 2).define(pairs(node_a, node_b))>>> model.where(node_a.id == 1, node_b.id == 1).define(pairs(node_a, node_b))>>>>>> # Get cosine similarity scores only for the specific pairs (2, 4) and (3, 4)>>> constrained_cosine_similarity = graph.cosine_similarity(between=pairs)>>> model.select(u.id, v.id, score).where(constrained_cosine_similarity(u, v, score)).inspect() id id_2 score0 1 1 1.0000001 1 2 0.395103Notes:
The cosine similarity is defined as the normalized inner product of
two vectors representing the neighborhoods of the nodes u and v.
- For unweighted graphs, the vector for a node
ucontains a 1 for each neighbor and a 0 for each non-neighbor. - For weighted graphs, the vector for a node
ucontains the edge weight for each neighbor and a 0 for each non-neighbor.
Edge weights are assumed to be non-negative, so the neighborhood vectors contain only non-negative elements. Therefore, the cosine similarity score is always between 0.0 and 1.0, inclusive.
The cosine_similarity(full=True) method computes and caches
the full cosine similarity relationship for all pairs of nodes,
providing efficient reuse across multiple calls. This can be expensive
as the result can contain O(|V|²) tuples.
Calling cosine_similarity() without arguments raises a ValueError,
to ensure awareness and explicit acknowledgement (full=True) of this cost.
In contrast, cosine_similarity(from_=subset) constrains the computation to
tuples with the first position in the passed-in subset. The result is
not cached; it is specific to the call site. When a significant fraction of
the cosine similarity relation is needed across a program,
cosine_similarity(full=True) is typically more efficient. Use
cosine_similarity(from_=subset) only when small subsets of
the cosine similarity relationship are needed
collectively across the program.
The to parameter can be used together with from_ to further
constrain the computation: cosine_similarity(from_=subset_a, to=subset_b)
computes cosine similarity scores only for node pairs where the first node is in
subset_a and the second node is in subset_b. (Since cosine_similarity
is symmetric in its first two positions, using to without from_ would
be functionally redundant, and is not allowed.)
The between parameter provides another way to constrain the computation.
Unlike from_ and to, which allow you to independently constrain the first
and second positions in cosine_similarity tuples to sets of nodes, between
allows you constrain the first and second positions, jointly, to specific pairs
of nodes.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.adamic_adar()
Section titled “.adamic_adar()”Graph.adamic_adar( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship containing the Adamic-Adar index for pairs of nodes.
The Adamic-Adar index is a similarity measure between two nodes based on the amount of shared neighbors between them, giving more weight to common neighbors that are less connected.
Parameters:
(fullbool, default:None) - IfTrue, computes the Adamic-Adar index for all pairs of nodes in the graph. This computation can be expensive for large graphs, as dependencies can scale quadratically in the number of edges or cubically in the number of nodes. Mutually exclusive with other parameters. Default isNone.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the Adamic-Adar computation: only Adamic-Adar indices for node pairs where the first node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. Can only be used together with thefrom_parameter. When provided withfrom_, constrains the domain of the Adamic-Adar computation: only Adamic-Adar indices for node pairs where the first node is infrom_and the second node is intoare computed and returned. Default isNone.
(betweenRelationship, default:None) - A binary relationship containing pairs of nodes. When provided, constrains the domain of the Adamic-Adar computation: only Adamic-Adar indices for the specific node pairs in this relationship are computed and returned. Mutually exclusive with other parameters. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and their Adamic-Adar index.
Raises:
DirectedGraphNotSupported- If the graph is directed.TypeError- Iffrom_,to, orbetweenis not aRelationship.ValueError- Iffullis provided with any other parameter. Ifbetweenis provided with any other parameter. Iffrom_is provided with any parameter other thanto. Iftois provided without thefrom_parameter. If none offull,from_, orbetweenis provided. Iffullis notTrueorNone. Iffrom_,to, orbetweenis not attached to the same model as the graph. Iffrom_,to, orbetweendoes not contain the graph’sNodeconcept. Iffrom_ortois not a unary relationship. Ifbetweenis not a binary relationship.WeightedGraphNotSupported- If the graph is weighted.
Relationship Schema:
adamic_adar(node_u, node_v, score)
- node_u (Node): The first node in the pair.
- node_v (Node): The second node in the pair.
- score (Float): The Adamic-Adar index of the pair.
Examples:
>>> from relationalai.semantics import Model, define, select, where, Float>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Select the Adamic-Adar indices from the full relationship>>> u, v = Node.ref("u"), Node.ref("v")>>> score = Float.ref("score")>>> adamic_adar = graph.adamic_adar(full=True)>>> model.select(... u.id, v.id, score,... ).where(... adamic_adar(u, v, score),... u.id == 2,... v.id == 4,... ).inspect() id id_2 score0 2 4 0.910239>>> # 4. Use 'from_' parameter to constrain the set of nodes for the first position>>> # Define a subset containing only node 1>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 1).define(subset(node))>>>>>> # Get Adamic-Adar indices only for pairs where first node is in subset>>> constrained_adamic_adar = graph.adamic_adar(from_=subset)>>> model.select(u.id, v.id, score).where(constrained_adamic_adar(u, v, score)).inspect() id id_2 score0 1 1 0.9102391 1 3 0.9102392 1 4 0.910239>>> # 5. Use both 'from_' and 'to' parameters to constrain both positions>>> subset_a = model.Relationship(f"{Node:node} is in subset_a")>>> subset_b = model.Relationship(f"{Node:node} is in subset_b")>>> model.where(node.id == 1).define(subset_a(node))>>> model.where(node.id == 4).define(subset_b(node))>>>>>> # Get Adamic-Adar indices only where first node is in subset_a and second node is in subset_b>>> constrained_adamic_adar = graph.adamic_adar(from_=subset_a, to=subset_b)>>> model.select(u.id, v.id, score).where(constrained_adamic_adar(u, v, score)).inspect() id id_2 score0 1 4 0.910239>>> # 6. Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 1, node_b.id == 4).define(pairs(node_a, node_b))>>> model.where(node_a.id == 2, node_b.id == 3).define(pairs(node_a, node_b))>>>>>> # Get Adamic-Adar indices only for the specific pairs (1, 4) and (2, 3)>>> constrained_adamic_adar = graph.adamic_adar(between=pairs)>>> model.select(u.id, v.id, score).where(constrained_adamic_adar(u, v, score)).inspect() id id_2 score0 1 4 0.9102391 2 3 2.352934Notes:
The Adamic-Adar index for nodes u and v is defined as the sum of
the inverse logarithmic degree of their common neighbors w::
AA(u,v) = Σ (1 / log(degree(w)))
The adamic_adar(full=True) method computes and caches the full Adamic-Adar
relationship for all pairs of nodes, providing efficient reuse across
multiple calls. This can be expensive as dependencies can contain O(|E|²) or
O(|V|³) tuples depending on graph density.
Calling adamic_adar() without arguments raises a ValueError,
to ensure awareness and explicit acknowledgement (full=True) of this cost.
In contrast, adamic_adar(from_=subset) constrains the computation to
tuples with the first position in the passed-in subset. The result is
not cached; it is specific to the call site. When a significant fraction of
the Adamic-Adar relation is needed across a program, adamic_adar(full=True)
is typically more efficient. Use adamic_adar(from_=subset) only
when small subsets of the Adamic-Adar relationship are needed
collectively across the program.
The to parameter can be used together with from_ to further
constrain the computation: adamic_adar(from_=subset_a, to=subset_b)
computes Adamic-Adar indices only for node pairs where the first node is in
subset_a and the second node is in subset_b. (Since adamic_adar
is symmetric in its first two positions, using to without from_ would
be functionally redundant, and is not allowed.)
The between parameter provides another way to constrain the computation.
Unlike from_ and to, which allow you to independently constrain the first
and second positions in adamic_adar tuples to sets of nodes, between
allows you constrain the first and second positions, jointly, to specific pairs
of nodes.
Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.preferential_attachment()
Section titled “.preferential_attachment()”Graph.preferential_attachment( *, full: Optional[bool] = None, from_: Optional[Relationship] = None, to: Optional[Relationship] = None, between: Optional[Relationship] = None) -> RelationshipReturns a ternary relationship containing the preferential attachment score for pairs of nodes.
The preferential attachment score between two nodes u and v is the
product of the outdegree of u and the indegree of v for directed graphs,
and the product of the degrees of u and v for undirected graphs.
Parameters:
(fullbool, default:None) - IfTrue, computes the preferential attachment score for all pairs of nodes in the graph. This computation can be expensive for large graphs, as the result can scale quadratically in the number of nodes. Mutually exclusive with other parameters. Default isNone.
(from_Relationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the preferential attachment computation: only preferential attachment scores for node pairs where the first node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(toRelationship, default:None) - A unary relationship containing a subset of the graph’s nodes. When provided, constrains the domain of the preferential attachment computation: only preferential attachment scores for node pairs where the second node is in this relationship are computed and returned. Mutually exclusive withfullandbetween. Default isNone.
(betweenRelationship, default:None) - A binary relationship containing pairs of nodes. When provided, constrains the domain of the preferential attachment computation: only preferential attachment scores for the specific node pairs in this relationship are computed and returned. Mutually exclusive with other parameters. Default isNone.
Returns:
Relationship- A ternary relationship where each tuple represents a pair of nodes and their preferential attachment score.
Raises:
TypeError- Iffrom_,to, orbetweenis not aRelationship.ValueError- Iffullis provided with any other parameter. Ifbetweenis provided with any other parameter. Iffrom_is provided with any parameter other thanto. Iftois provided with any parameter other thanfrom_. If none offull,from_,to, orbetweenis provided. Iffullis notTrueorNone. Iffrom_,to, orbetweenis not attached to the same model as the graph. Iffrom_,to, orbetweendoes not contain the graph’sNodeconcept. Iffrom_ortois not a unary relationship. Ifbetweenis not a binary relationship.WeightedGraphNotSupported- If the graph is weighted.
Relationship Schema:
preferential_attachment(node_u, node_v, score)
- node_u (Node): The first node in the pair.
- node_v (Node): The second node in the pair.
- score (Integer): The preferential attachment score of the pair.
Examples:
Undirected Graph Example
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Select the preferential attachment scores from the full relationship>>> u, v = Node.ref("u"), Node.ref("v")>>> score = Integer.ref("score")>>> preferential_attachment = graph.preferential_attachment(full=True)>>> model.select(... u.id, v.id, score,... ).where(... preferential_attachment(u, v, score),... u.id == 1,... model.union(v.id == 2, v.id == 3),... ).inspect() id id_2 score0 1 2 31 1 3 4>>> # 4. Use 'from_' parameter to constrain the set of nodes for the first position>>> # Define a subset containing only node 1>>> from relationalai.semantics import where>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 1).define(subset(node))>>>>>> # Get preferential attachment scores only for pairs where first node is in subset>>> constrained_preferential_attachment = graph.preferential_attachment(from_=subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 1 11 1 2 32 1 3 43 1 4 2>>> # 5. Use 'to' parameter to constrain the set of nodes for the second position>>> # Get preferential attachment scores only for pairs where second node is in subset>>> constrained_preferential_attachment = graph.preferential_attachment(to=subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 1 11 2 1 32 3 1 43 4 1 2>>> # 6. Use both 'from_' and 'to' parameters to constrain both positions>>> from_subset = model.Relationship(f"{Node:node} is in from_subset")>>> to_subset = model.Relationship(f"{Node:node} is in to_subset")>>> model.where(node.id == 1).define(from_subset(node))>>> model.where(node.id == 3).define(to_subset(node))>>>>>> # Get preferential attachment scores only where first node is in from_subset and second node is in to_subset>>> constrained_preferential_attachment = graph.preferential_attachment(from_=from_subset, to=to_subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 3 4>>> # 7. Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 1, node_b.id == 3).define(pairs(node_a, node_b))>>> model.where(node_a.id == 2, node_b.id == 4).define(pairs(node_a, node_b))>>>>>> # Get preferential attachment scores only for the specific pairs (1, 3) and (2, 4)>>> constrained_preferential_attachment = graph.preferential_attachment(between=pairs)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 3 41 2 4 6Directed Graph Example
>>> from relationalai.semantics import Model, Integer, union>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up a directed graph>>> model = Model("test_model")>>> graph = Graph(model, directed=True, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4 = [Node.new(id=i) for i in range(1, 5)]>>> model.define(n1, n2, n3, n4)>>> model.define(... Edge.new(src=n1, dst=n2),... Edge.new(src=n2, dst=n3),... Edge.new(src=n3, dst=n3),... Edge.new(src=n2, dst=n4),... Edge.new(src=n4, dst=n3),... )>>>>>> # 3. Select the preferential attachment scores from the full relationship>>> u, v = Node.ref("u"), Node.ref("v")>>> score = Integer.ref("score")>>> preferential_attachment = graph.preferential_attachment(full=True)>>> model.select(... u.id, v.id, score,... ).where(... preferential_attachment(u, v, score),... u.id == 1,... model.union(v.id == 2, v.id == 3),... ).inspect() id id_2 score0 1 2 11 1 3 3>>> # 4. Use 'from_' parameter to constrain the set of nodes for the first position>>> # Define a subset containing only node 1>>> subset = model.Relationship(f"{Node:node} is in subset")>>> node = Node.ref()>>> model.where(node.id == 1).define(subset(node))>>>>>> # Get preferential attachment scores only for pairs where first node is in subset>>> constrained_preferential_attachment = graph.preferential_attachment(from_=subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 1 01 1 2 12 1 3 33 1 4 1>>> # 5. Use 'to' parameter to constrain the set of nodes for the second position>>> # Get preferential attachment scores only for pairs where second node is in subset>>> constrained_preferential_attachment = graph.preferential_attachment(to=subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 1 01 2 1 02 3 1 03 4 1 0>>> # 6. Use both 'from_' and 'to' parameters to constrain both positions>>> from_subset = model.Relationship(f"{Node:node} is in from_subset")>>> to_subset = model.Relationship(f"{Node:node} is in to_subset")>>> model.where(node.id == 1).define(from_subset(node))>>> model.where(node.id == 3).define(to_subset(node))>>>>>> # Get preferential attachment scores only where first node is in from_subset and second node is in to_subset>>> constrained_preferential_attachment = graph.preferential_attachment(from_=from_subset, to=to_subset)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 3 3>>> # 7. Use 'between' parameter to constrain to specific pairs of nodes>>> pairs = model.Relationship(f"{Node:node_a} and {Node:node_b} are a pair")>>> node_a, node_b = Node.ref(), Node.ref()>>> model.where(node_a.id == 1, node_b.id == 3).define(pairs(node_a, node_b))>>> model.where(node_a.id == 2, node_b.id == 4).define(pairs(node_a, node_b))>>>>>> # Get preferential attachment scores only for the specific pairs (1, 3) and (2, 4)>>> constrained_preferential_attachment = graph.preferential_attachment(between=pairs)>>> model.select(u.id, v.id, score).where(constrained_preferential_attachment(u, v, score)).inspect() id id_2 score0 1 3 31 2 4 2Notes:
The preferential_attachment(full=True) method computes and caches
the full preferential attachment relationship for all pairs of nodes,
providing efficient reuse across multiple calls. This can be expensive
as the result contains O(|V|²) tuples.
Calling preferential_attachment() without arguments raises a ValueError,
to ensure awareness and explicit acknowledgement (full=True) of this cost.
In contrast, parameters from_, to, and between allow for constraining
the computation to specific subsets of the graph. The result is
not cached; it is specific to the call site. When a significant fraction of
the preferential attachment relation is needed across a program,
preferential_attachment(full=True) is typically more efficient. Use
parameters from_, to, and between only when small subsets of
the preferential attachment relationship are needed
collectively across the program.
preferential_attachment(from_=subset) constrains the computation to
tuples with the first position in the passed-in subset.
preferential_attachment(to=subset) constrains the computation to
tuples with the second position in the passed-in subset.
In directed graphs, the preferential_attachment relationship is asymmetric:
the computation considers out-edges from node_u and in-edges to node_v.
This asymmetry means that from_ and to parameters have distinct,
non-interchangeable meanings.
The to parameter can be used together with from_ to further
constrain the computation: preferential_attachment(from_=subset_a, to=subset_b)
computes preferential attachment scores only for node pairs where the first node is in
subset_a and the second node is in subset_b.
The between parameter provides another way to constrain the computation.
Unlike from_ and to, which allow you to independently constrain the first
and second positions in preferential_attachment tuples to sets of nodes, between
allows you constrain the first and second positions, jointly, to specific pairs
of nodes.
Referenced By:
RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview
.infomap()
Section titled “.infomap()”Graph.infomap( 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 = 8675309, *, diagnostic_info: bool = False) -> Union[Relationship, Tuple[Relationship, Relationship]]Partitions nodes into communities using a variant of the Infomap algorithm.
This method maps nodes to community assignments based on the flow of information on the graph.
Parameters:
(max_levelsint, default:1) - The maximum number of levels at which to optimize. Must be a positive integer. Default is 1.
(max_sweepsint, default:20) - The maximum number of sweeps within each level. Must be a non-negative integer. Default is 20.
(level_tolerancefloat, default:0.01) - Map equation progress threshold to continue to the next level. Must be a non-negative float. Default is 0.01.
(sweep_tolerancefloat, default:0.0001) - Map equation progress threshold to continue to the next sweep. Must be a non-negative float. Default is 0.0001.
(teleportation_ratefloat, default:0.15) - Teleportation rate for ergodic node visit rate calculation. Must be a float in [1e-4, 1). Default is 0.15.
(visit_rate_tolerancefloat, default:1e-15) - Convergence tolerance for ergodic node visit rate calculation. Must be a positive float. Default is 1e-15.
(randomization_seedint, default:8675309) - The random number generator seed for the run. Must be a non-negative integer. Default is 8675309.
(diagnostic_infobool, default:False) - IfTrue, returns diagnostic information alongside community assignments. IfFalse(default), returns only community assignments.
Returns:
-
Relationship or tuple of Relationships- Ifdiagnostic_infoisFalse(default), returns a binary relationship where each tuple represents a node and its community assignment.If
diagnostic_infoisTrue, returns a tuple of(community_assignments, diagnostic_info)where:community_assignmentsis the binary relationship described abovediagnostic_infois a binary relationship containing one tuple, whose second component is a diagnostic string describing the algorithm’s convergence and termination behavior
Relationship Schema:
When diagnostic_info=False (default):
infomap(node, community_label)
- node (Node): The node.
- community_label (Integer): The label of the community the node belongs to.
When diagnostic_info=True, returns two relationships:
infomap(node, community_label)as described above; anddiagnostic_info(diagnostic_name, diagnostic_value)
- diagnostic_name (String): The name of the diagnostic information.
- diagnostic_value (String): A diagnostic string describing the algorithm’s convergence and termination behavior.
Examples:
Unweighted Graph Example
Compute community assignments for each node in an undirected graph. Here, an undirected dumbbell graph resolves into two communities, namely its two constituent three-cliques.
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges for a dumbbell graph>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # The first three-clique.... Edge.new(src=n1, dst=n2), Edge.new(src=n1, dst=n3), Edge.new(src=n2, dst=n3),... # The second three-clique.... Edge.new(src=n4, dst=n5), Edge.new(src=n4, dst=n6), Edge.new(src=n5, dst=n6),... # The connection between the three-cliques.... Edge.new(src=n1, dst=n4)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> infomap = graph.infomap()>>> model.select(node.id, label).where(infomap(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Weighted Graph Example
Compute community assignments for each node in an undirected weighted graph. Here, a six-clique has the edges forming a dumbbell graph within the six-clique strongly weighted, and the remaining edges weakly weighted. The graph resolves into two communities, namely the two three-cliques constituent of the dumbbell embedded in the six-clique.
>>> # 1. Set up a weighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # First embedded three-clique.... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n1, dst=n3, weight=1.0),... Edge.new(src=n2, dst=n3, weight=1.0),... # Second embedded three-clique.... Edge.new(src=n4, dst=n5, weight=1.0),... Edge.new(src=n4, dst=n6, weight=1.0),... Edge.new(src=n5, dst=n6, weight=1.0),... # Slightly weaker connection between the embedded three-cliques.... Edge.new(src=n1, dst=n4, weight=0.5),... # Weaker edges connecting the six-clique in full.... Edge.new(src=n1, dst=n5, weight=0.1), Edge.new(src=n1, dst=n6, weight=0.1),... Edge.new(src=n2, dst=n4, weight=0.1), Edge.new(src=n2, dst=n5, weight=0.1),... Edge.new(src=n2, dst=n6, weight=0.1), Edge.new(src=n3, dst=n4, weight=0.1),... Edge.new(src=n3, dst=n5, weight=0.1), Edge.new(src=n3, dst=n6, weight=0.1)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> infomap = graph.infomap()>>> model.select(node.id, label).where(infomap(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Notes:
This implementation of Infomap minimizes the map equation via a Louvain-like optimization heuristic; this is often referred to as “core” Infomap in the literature. Computation of the ergodic node visit frequencies is done via regularized power iteration, with regularization via a uniform teleportation probability of 0.15, matching the nominal selection in the literature.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.louvain()
Section titled “.louvain()”Graph.louvain( max_levels: int = 1, max_sweeps: int = 20, level_tolerance: float = 0.01, sweep_tolerance: float = 0.0001, randomization_seed: int = 8675309, *, diagnostic_info: bool = False) -> Union[Relationship, Tuple[Relationship, Relationship]]Partitions nodes into communities using the Louvain algorithm.
This method detects communities by maximizing a modularity score. It is only applicable to undirected graphs.
Parameters:
(max_levelsint, default:1) - The maximum number of levels at which to optimize. Must be a positive integer. Default is 1.
(max_sweepsint, default:20) - The maximum number of sweeps within each level. Must be a non-negative integer. Default is 20.
(level_tolerancefloat, default:0.01) - Modularity progress threshold to continue to the next level. Must be a non-negative float. Default is 0.01.
(sweep_tolerancefloat, default:0.0001) - Modularity progress threshold to continue to the next sweep. Must be a non-negative float. Default is 0.0001.
(randomization_seedint, default:8675309) - The random number generator seed for the run. Must be a non-negative integer. Default is 8675309.
(diagnostic_infobool, default:False) - IfTrue, returns diagnostic information alongside community assignments. IfFalse(default), returns only community assignments.
Returns:
-
Relationship or tuple of Relationships- Ifdiagnostic_infoisFalse(default), returns a binary relationship where each tuple represents a node and its community assignment.If
diagnostic_infoisTrue, returns a tuple of(community_assignments, diagnostic_info)where:community_assignmentsis the binary relationship described abovediagnostic_infois a binary relationship containing one tuple, whose second component is a diagnostic string describing the algorithm’s convergence and termination behavior
Raises:
DirectedGraphNotSupported- If the graph is directed.
Relationship Schema:
When diagnostic_info=False (default):
louvain(node, community_label)
- node (Node): The node.
- community_label (Integer): The label of the community the node belongs to.
When diagnostic_info=True, returns two relationships:
louvain(node, community_label)as described above; anddiagnostic_info(diagnostic_name, diagnostic_value)
- diagnostic_name (String): The name of the diagnostic information.
- diagnostic_value (String): A diagnostic string describing the algorithm’s convergence and termination behavior.
Examples:
Unweighted Graph Example
Compute community assignments for each node in an undirected graph. Here, an undirected dumbbell graph resolves into two communities, namely its two constituent three-cliques.
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges for a dumbbell graph>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # The first three-clique.... Edge.new(src=n1, dst=n2), Edge.new(src=n1, dst=n3), Edge.new(src=n2, dst=n3),... # The second three-clique.... Edge.new(src=n4, dst=n5), Edge.new(src=n4, dst=n6), Edge.new(src=n5, dst=n6),... # The connection between the three-cliques.... Edge.new(src=n1, dst=n4)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> louvain = graph.louvain()>>> model.select(node.id, label).where(louvain(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Weighted Graph Example
Compute community assignments for each node in an undirected weighted graph. Here, a six-clique has the edges forming a dumbbell graph within the six-clique strongly weighted, and the remaining edges weakly weighted. The graph resolves into two communities, namely the two three-cliques constituent of the dumbbell embedded in the six-clique.
>>> # 1. Set up a weighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # First embedded three-clique.... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n1, dst=n3, weight=1.0),... Edge.new(src=n2, dst=n3, weight=1.0),... # Second embedded three-clique.... Edge.new(src=n4, dst=n5, weight=1.0),... Edge.new(src=n4, dst=n6, weight=1.0),... Edge.new(src=n5, dst=n6, weight=1.0),... # Connection between the embedded three-cliques.... Edge.new(src=n1, dst=n4, weight=1.0),... # Weaker edges connecting the six-clique in full.... Edge.new(src=n1, dst=n5, weight=0.2), Edge.new(src=n1, dst=n6, weight=0.2),... Edge.new(src=n2, dst=n4, weight=0.2), Edge.new(src=n2, dst=n5, weight=0.2),... Edge.new(src=n2, dst=n6, weight=0.2), Edge.new(src=n3, dst=n4, weight=0.2),... Edge.new(src=n3, dst=n5, weight=0.2), Edge.new(src=n3, dst=n6, weight=0.2)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> louvain = graph.louvain()>>> model.select(node.id, label).where(louvain(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Notes:
This implementation of the Louvain algorithm is consistent with the modularity definition (Eq. 1) in “Fast unfolding of communities in large networks”, Blondel et al J. Stat. Mech. (2008) P10008.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
.label_propagation()
Section titled “.label_propagation()”Graph.label_propagation( max_sweeps: int = 20, randomization_seed: int = 8675309, *, diagnostic_info: bool = False) -> Union[Relationship, Tuple[Relationship, Relationship]]Partitions nodes into communities using the Label Propagation algorithm.
This method maps nodes to community assignments via asynchronous label propagation.
Parameters:
(max_sweepsint, default:20) - The maximum number of sweeps for label propagation to perform. Must be a non-negative integer. Default is 20.
(randomization_seedint, default:8675309) - The random number generator seed for the run. Must be a non-negative integer. Default is 8675309.
(diagnostic_infobool, default:False) - IfTrue, returns diagnostic information alongside community assignments. IfFalse(default), returns only community assignments.
Returns:
-
Relationship or tuple of Relationships- Ifdiagnostic_infoisFalse(default), returns a binary relationship where each tuple represents a node and its community assignment.If
diagnostic_infoisTrue, returns a tuple of(community_assignments, diagnostic_info)where:community_assignmentsis the binary relationship described abovediagnostic_infois a binary relationship containing one tuple, whose second component is a diagnostic string describing the algorithm’s convergence and termination behavior
Relationship Schema:
When diagnostic_info=False (default):
label_propagation(node, community_label)
- node (Node): The node.
- community_label (Integer): The label of the community the node belongs to.
When diagnostic_info=True, returns two relationships:
label_propagation(node, community_label)as described above; anddiagnostic_info(diagnostic_name, diagnostic_value)
- diagnostic_name (String): The name of the diagnostic information.
- diagnostic_value (String): A diagnostic string describing the algorithm’s convergence and termination behavior.
Examples:
Unweighted Graph Example
Compute community assignments for each node in an undirected graph. Here, an undirected dumbbell graph resolves into two communities, namely its two constituent three-cliques.
>>> from relationalai.semantics import Model, Integer>>> from relationalai.semantics.reasoners.graph import Graph>>>>>> # 1. Set up an undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=False)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges for a dumbbell graph>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # The first three-clique.... Edge.new(src=n1, dst=n2), Edge.new(src=n1, dst=n3), Edge.new(src=n2, dst=n3),... # The second three-clique.... Edge.new(src=n4, dst=n5), Edge.new(src=n4, dst=n6), Edge.new(src=n5, dst=n6),... # The connection between the three-cliques.... Edge.new(src=n1, dst=n4)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> label_propagation = graph.label_propagation()>>> model.select(node.id, label).where(label_propagation(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Weighted Graph Example
Compute community assignments for each node in an undirected weighted graph. Here, a six-clique has the edges forming a dumbbell graph within the six-clique strongly weighted, and the remaining edges weakly weighted. The graph resolves into two communities, namely the two three-cliques constituent of the dumbbell embedded in the six-clique.
>>> # 1. Set up a weighted, undirected graph>>> model = Model("test_model")>>> graph = Graph(model, directed=False, weighted=True)>>> Node, Edge = graph.Node, graph.Edge>>>>>> # 2. Define nodes and edges>>> n1, n2, n3, n4, n5, n6 = [Node.new(id=i) for i in range(1, 7)]>>> model.define(n1, n2, n3, n4, n5, n6)>>> model.define(... # First embedded three-clique.... Edge.new(src=n1, dst=n2, weight=1.0),... Edge.new(src=n1, dst=n3, weight=1.0),... Edge.new(src=n2, dst=n3, weight=1.0),... # Second embedded three-clique.... Edge.new(src=n4, dst=n5, weight=1.0),... Edge.new(src=n4, dst=n6, weight=1.0),... Edge.new(src=n5, dst=n6, weight=1.0),... # Slightly weaker connection between the embedded three-cliques.... Edge.new(src=n1, dst=n4, weight=0.5),... # Weaker edges connecting the six-clique in full.... Edge.new(src=n1, dst=n5, weight=0.1), Edge.new(src=n1, dst=n6, weight=0.1),... Edge.new(src=n2, dst=n4, weight=0.1), Edge.new(src=n2, dst=n5, weight=0.1),... Edge.new(src=n2, dst=n6, weight=0.1), Edge.new(src=n3, dst=n4, weight=0.1),... Edge.new(src=n3, dst=n5, weight=0.1), Edge.new(src=n3, dst=n6, weight=0.1)... )>>>>>> # 3. Compute community assignments and inspect>>> node, label = Node.ref("node"), Integer.ref("label")>>> label_propagation = graph.label_propagation()>>> model.select(node.id, label).where(label_propagation(node, label)).inspect() id label0 1 21 2 22 3 23 4 14 5 15 6 1Notes:
This implementation of asynchronous label propagation breaks ties between neighboring labels with equal cumulative edge weight (and frequency in the unweighted case) uniformly at random, but with a static seed.
Edges with zero weight still influence the result, for example if a node has only zero-weight edges.
Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Use advanced reasoning > Graph reasoning └── Run a Graph Algorithm └── Available Algorithms
Referenced By
Section titled “Referenced By”RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Use advanced reasoning > Graph reasoning │ ├── Create a graph │ │ ├── Understand graph basics │ │ └── Choose a graph type │ └── Run a Graph Algorithm │ └── Available Algorithms └── Release Notes └── Preview Features └── Features In Preview