import time
import logging

from graphit import Graph
from graphit.graph_algorithms import node_neighbors

logging.basicConfig(level=logging.WARN)

Tutorial 3: Working with graph selections

Tutorial 2 demonstrated how to retrieve/query specific nodes and edges from a graph. In graphit these node and edge selections are regarded subgraphs of the parent graph they are selected from. In this tutorial you will take a closer look at these subgraphs and the special properties they have.

Subgraphs are ‘views’ on the main graph

The various node and edge query and selection tools that graphit offers are powerfull in selecting subgraphs based on a wide range of criteria. The subgraphs returned are not a copy but a ‘view’ on the data in the main graph similar to dictionary views in Python. graphit aims in using views as often as possible for graph operations.

Views are convenient because they are fast compared to making a full copies and do not fragment the data. The latter means that data modifications made in a ‘view’ effect the data stored in the source graph only and are synchronized automatically in all other views on the same data. The source or origin Graph representing the full node and edge data set can always be obtained from any selection using the origin attribute.

1.1 Creating subgraphs

Create a subgraph using getnodes or getedges by default returns a Graph object with a view on the respective selection only. In both cases this means a view on the nodes, the edges connecting them and adjacency reflecting the neighbours of the nodes. Adjacency itself is not a view as both keys (source nodes) and the values (list of neighbouring nodes) will need to be adjusted to reflect the node and edge selection.

g = Graph()
g.add_edges([(1, 2), (2, 4), (4, 5), (4, 6), (5, 7), (6, 8), (7, 9)], node_from_edge=True)

# Making a node selection using getnodes
nsub1 = g.getnodes([4,5,6])
print(nsub1.nodes.keys())
print(nsub1.edges.keys())
print(nsub1.adjacency())

# Nodes, edges and adjacency are 'views'
print(nsub1.nodes.is_view, nsub1.edges.is_view)

# Selecting nodes from another selection
nsub2 = nsub1.getnodes([5,8])
print(nsub2.nodes.keys())
print(nsub2.edges.keys())
print(nsub2.adjacency())

# Selecting nodes that do not exist will not work
nsub3 = nsub1.getnodes(8)
# The source graph for the selections remains available
print(nsub1.origin == g, nsub2.origin == g)

# Data modifications in views are made on the parent data object and synchronized
print(g.nodes[5])
nsub1.nodes[5]['key'] = 15
print(g.nodes[5], nsub2.nodes[5])
# Making an edge selection using getedges works in the same way as getnodes
esub1 = g.getedges([(2, 4), (4, 2), (4, 5), (5, 4), (4, 6)])
print(esub1.nodes.keys())
print(esub1.edges.keys())
print(esub1.adjacency())

1.2 Subgraph connectivity

Subgraphs created using getnodes, getedges or other selection and query methods return Graph objects with a view on the given selection. Although they seem to be isolated subgraphs they maintain connectivity with the origin graph by default.

Connectivity behaviour is controlled using the Graph.masked attribute. False by default maintains connectivity with the origin graph while set to False treats the selection as an isolated subgraph.

This is an important functionality as it for instance allows selection of a node while still maintaining information on the environment it is embedded in (like neighbours), masked set to true on the other hand treats the subgraph as an isolated graph even if it is connected to or embedded in a larger graph.

# Get neighbours outside subgraph by default
print(node_neighbors(g, 6))
print(node_neighbors(nsub1, 6))

# Set masked only considers the subgraph nodes as neighbours
nsub1.masked = True
print(node_neighbors(nsub1, 6))

1.3 Everything is a view

graphit aims to use views as often as possible when operating on graph selections originating from the same origin graph. Only when operations will or are likely to return a graph with a distinct topology not found in the input graphs will it return a new independant graph.

1.3 Copying graphs

Moving from a graph with a ‘view’ on the origin graph to an independent graph is handled by the Graph.copy method that returns a (deep)copy.

cp = nsub1.copy()

print(cp)
print(cp.origin != nsub1.origin)
print(cp.nodes.is_view, cp.edges.is_view, cp.adjacency.is_view)
print(cp.nodes.keys())