<jupyter_start><jupyter_text><jupyter_code># !pip install dgl
# !pip install networkx
import torch<jupyter_output><empty_output><jupyter_text>Let's start out of the box and draw a graph<jupyter_code>import networkx as nx
import dgl
import dgl.function as fn

g_nx = nx.petersen_graph()
g_dgl = dgl.DGLGraph(g_nx)

import matplotlib.pyplot as plt

plt.rcParams["figure.figsize"] = (12,6)

plt.subplot(121)
nx.draw(g_nx, with_labels=True)
plt.subplot(122)
nx.draw(g_dgl.to_networkx(), with_labels=True)

plt.show()<jupyter_output>Using backend: pytorch
/Users/sanmay/anaconda3/lib/python3.8/site-packages/dgl/base.py:45: DGLWarning: Recommend creating graphs by `dgl.graph(data)` instead of `dgl.DGLGraph(data)`.
  return warnings.warn(message, category=category, stacklevel=1)<jupyter_text>Building a graph from scratch<jupyter_code>import torch as th

plt.rcParams["figure.figsize"] = (6,6)
g = dgl.DGLGraph()
g.add_nodes(10)
# # a couple edges one-by-one
# for i in range(1, 4):
#     g.add_edge(i, 0)
# # a few more with a paired list
# src = list(range(5, 8)); dst = [0]*3
# g.add_edges(src, dst)
# # finish with a pair of tensors
# src = th.tensor([8, 9]); dst = th.tensor([0, 0])
# g.add_edges(src, dst)

# # edge broadcasting will do star graph in one go!
# g.clear(); g.add_nodes(10)
# src = th.tensor(list(range(1, 10)));
# g.add_edges(src, 0)

nx.draw(g.to_networkx(), with_labels=True)
plt.show()<jupyter_output>/Users/sanmay/anaconda3/lib/python3.8/site-packages/dgl/base.py:45: DGLWarning: Recommend creating graphs by `dgl.graph(data)` instead of `dgl.DGLGraph(data)`.
  return warnings.warn(message, category=category, stacklevel=1)<jupyter_text>Define the edges with a start-node and an end-node<jupyter_code>for i in range(0, 9):
    g.add_edge(i, i+1)
nx.draw(g.to_networkx(), with_labels=True)
plt.show()<jupyter_output>/Users/sanmay/anaconda3/lib/python3.8/site-packages/dgl/base.py:45: DGLWarning: DGLGraph.add_edge is deprecated. Please use DGLGraph.add_edges
  return warnings.warn(message, category=category, stacklevel=1)<jupyter_text>Nodes as a vector in DGL and NetworkX<jupyter_code>g.nodes()
g.to_networkx().nodes()<jupyter_output><empty_output><jupyter_text>Features to nodes and edges<jupyter_code># --- creating a graph with 10 nodes -- #
g = dgl.DGLGraph()
g.add_nodes(10)
g.nodes[1]
g.edges()<jupyter_output><empty_output><jupyter_text>Assigning data on the nodes<jupyter_code>x = th.randn(10, 3)
g.ndata['x'] = x # -- assigning a name to the feature : 'x'
g.ndata<jupyter_output><empty_output><jupyter_text>Data on a particular node<jupyter_code>g.nodes[3].data['x'], g.ndata['x'][3]<jupyter_output><empty_output><jupyter_text>What if I want to assign features to any individual node ?<jupyter_code># access node set with integer, list, or integer tensor
g.nodes[0].data['x'] = 2 * th.ones(1, 3)
g.nodes[[1, 2]].data['x'] = th.zeros(2, 3)<jupyter_output><empty_output><jupyter_text>Another way to add edges at one go<jupyter_code># edge broadcasting will do star graph in one go!
g = dgl.DGLGraph(); g.add_nodes(10)
src = th.tensor(list(range(1, 10)));
g.add_edges(src, 0)
nx.draw(g.to_networkx(), with_labels=True)
plt.show()<jupyter_output><empty_output><jupyter_text>Adding feature to edges in two different ways<jupyter_code>g.edata['w'] = th.randn(9, 2)

# access edge set with IDs in integer, list, or integer tensor
g.edges[1].data['w'] = th.randn(1, 2)
g.edges[[0, 1, 2]].data['w'] = th.zeros(3, 2)
g.edges[th.tensor([0, 1, 2])].data['w'] = th.zeros(3, 2)

# one can also access the edges by giving endpoints
g.edges[1, 0].data['w'] = th.ones(1, 2)                 # edge 1 -> 0
g.edges[[1, 2, 3], [0, 0, 0]].data['w'] = th.ones(3, 2) # edges [1, 2, 3] -> 0
x = th.randn(10, 3)
g.ndata['x'] = x<jupyter_output><empty_output><jupyter_text>Accessing the edges<jupyter_code>g.edges()<jupyter_output><empty_output><jupyter_text>How to acceess the data on the edges ?<jupyter_code>g.edges[6, 0] 
g.edges[6, 0].data
edg = g.edges[6, 0] 
edg.data
g.nodes[1]<jupyter_output><empty_output><jupyter_text>Message passing a node data is updated by aggregating information from neighbouring nodes  basic in-built functions<jupyter_code>## -- a random graph in dgl -- ##
g = dgl.rand_graph(100, 10)
g.ndata['h'] = torch.randn((g.num_nodes(), 10))
g.edata['w'] = torch.randn((g.num_edges(), 1))
g
g.apply_edges(lambda edges: {'ex' : edges.src['h'] + edges.dst['h']})
g
g.apply_nodes(lambda nodes: {'nx' : nodes.data['h'] * 2})
g
g.update_all(message_func=fn.u_mul_e('h', 'w', 'm'), reduce_func=fn.sum('m', 'h_new'))
g
#g.apply_nodes??
N = 30  # number of nodes
DAMP = 0.85  # damping factor
K = 10  # number of iterations
g = nx.nx.erdos_renyi_graph(N, 0.1)
g = dgl.DGLGraph(g)

plt.rcParams["figure.figsize"] = (8,8)
nx.draw(g.to_networkx(),  node_color=[[.2, .5, .8,]], with_labels=True)
plt.show()<jupyter_output><empty_output><jupyter_text>Assign to different data on the nodes<jupyter_code>g.ndata['pv'] = torch.ones(N) / N
g.ndata['deg'] = g.out_degrees(g.nodes()).float()
g
g.nodes()
g.out_degrees(3), g.in_degrees(3), g.out_degrees(7)
g.ndata['pv']
g.ndata['deg']<jupyter_output><empty_output><jupyter_text>Make four copies of the same graph for illustration purpose<jupyter_code>g1, g2, g3, g4 = g.local_var(), g.local_var(), g.local_var(), g.local_var()<jupyter_output><empty_output><jupyter_text>Implementation of the page rank algorithm  $PV(u) = \frac{1-d}{N} + d ~\times~~ \sum_{v \in \mathcal{N}(u) } \frac{PV(v)}{D(v)} $  The node aggregation function is invariant under permutation of the neighbouring nodes.  It's a two stage process, first the 'message' is passed by each node along all the edges, originating from the node. At the next step, all the 'messages' received by a node is used to update (in a permutation invariant way) to update the node data.<jupyter_code>def pagerank_message_func(edges):
    return {'pv' : edges.src['pv'] / edges.src['deg']}
def pagerank_reduce_func(nodes):
    msgs = torch.sum(nodes.mailbox['pv'], dim=1)
    pv = (1 - DAMP) / N + DAMP * msgs
    return {'pv' : pv}<jupyter_output><empty_output><jupyter_text>Notice the usage of 'mailbox', it stores all the data on a node, received through edges when the message function is executed. Each node has its individual 'mailbox'. Also notice, the contents of the mailbox are summed up and hence the final result doesn't depend on the order of the nodes which sent messages to a particular node<jupyter_code>import dgl.function as fn
for g in [g1, g2, g3] : 
    g.update_all(message_func=fn.copy_src(src='pv', out='m'),
                 reduce_func=fn.sum(msg='m',out='m_sum'))
def pagerank_naive(g):
        
    g.update_all(message_func=pagerank_message_func,
                 reduce_func=pagerank_reduce_func
                )
pagerank_naive(g1)
g1
def pagerank_builtin(g):
    g.ndata['pv'] = g.ndata['pv'] / g.ndata['deg']
    g.update_all(message_func=fn.copy_src(src='pv', out='m'),
                 reduce_func=fn.sum(msg='m',out='m_sum'))
    g.ndata['pv'] = (1 - DAMP) / N + DAMP * g.ndata['m_sum']
pagerank_builtin(g4)
g4.ndata['pv'], g4.ndata['deg']
adj = g.adjacency_matrix()
adj
dgl.base.DGLError
torch.nn.Parameter(torch.Tensor(5, 3))
g.in_degrees().view(-1, 1).shape
dgl.knn_graph??<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15957007027697395, 'fraction_numerical': 0.022598870056497175, 'mean_word_length': 5.2353951890034365, 'pattern_counts': {'":': 0, '<': 70, '<?xml version=': 0, '>': 72, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '514521', 'license': 'MIT', 'n_tokens_mistral': 2971, 'n_tokens_neox': 2844, 'n_words': 655, 'path': 'Tutorial5/Graph_Network_Tutorial_DGL.ipynb', 'repo_name': 'yish-gil/MLCourse_2021', 'size': '7257'}