```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Toggle cell visibility here.

## Osservatore dello stato: sistemi non osservabili
Questo esempio mostra cosa succede quando un osservatore è progettato per un sistema lineare non osservabile.

Ricordando la forma standard di osservabilità:

\begin{cases}
\underbrace{\begin{bmatrix} \dot{x}_o \\ \dot{x}_{\bar{o}} \end{bmatrix}}_{\dot{\textbf{x}}} = \begin{bmatrix} A_o & 0 \\ A_{o\bar{o}} & A_{\bar{o}} \end{bmatrix} \underbrace{\begin{bmatrix} x_o \\ x_{\bar{o}} \end{bmatrix}}_{\textbf{x}} + \begin{bmatrix} B_o \\ B_{\bar{o}} \end{bmatrix} \textbf{u} \\
\textbf{y} = \begin{bmatrix} C_o & 0 \end{bmatrix} \textbf{x},
\end{cases}

costruire:
- un sistema non osservabile la cui dinamica non osservabile è stabile;
- un sistema non osservabile la cui dinamica non osservabile è instabile;
- un sistema non osservabile la cui dinamica non osservabile ha dimensione 1 e autovalore 0. Cosa cambia in questo caso?

```python
#Preparatory Cell

%matplotlib inline
import control as control
import numpy
import sympy as sym
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    def dummychangecallback(self,change):
        pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(control.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        control.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

```python
# Preparatory cell

A = numpy.matrix('-1 1 0; 0 -2 0; 0 1 2')
B = numpy.matrix('0.7; 0.5; 1')
C = numpy.matrix('1 0 0')
X0 = numpy.matrix('2; 2; 2')
F = numpy.matrix('-1 0 3; 0 -2 -1; 0 1 -2')
G = numpy.matrix('0; 0; 1')
L = numpy.matrix([[4],[-2/3],[2/3]])
sol1 = numpy.linalg.eig(A)

Adefault = [A,
            numpy.matrix('-1 1 0; 0 -2 0; 0 1 -2')]
Bdefault = B
Cdefault = C

Aw = matrixWidget(3,3)
Aw.setM(A)
Bw = matrixWidget(3,1)
Bw.setM(B)
Cw = matrixWidget(1,3)
Cw.setM(C)
X0w = matrixWidget(3,1)
X0w.setM(X0)
Fw = matrixWidget(3,3)
Fw.setM(F)
Gw = matrixWidget(3,1)
Gw.setM(G)
Lw = matrixWidget(3,1)
Lw.setM(L)

eig1o = matrixWidget(1,1)
eig2o = matrixWidget(2,1)
eig3o = matrixWidget(1,1)
eig1o.setM(numpy.matrix([-3]))
eig2o.setM(numpy.matrix([[-3],[0]]))
eig3o.setM(numpy.matrix([-3]))
```

```python
# Misc

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)

# default systems
sel_system = widgets.Dropdown(
                              options=[('Manuale','Manual'), ('Stato instabile e inosservabile','Unstable unobservable state'), ('Stato stabile e inosservabile','Stable unobservable state')],
                              value='Unstable unobservable state',
                              description='',
                              disabled=False
)

#define type of ipout
selu = widgets.Dropdown(
    options=[('impulso','impulse'), ('gradino','step'), ('sinusoide','sinusoid'), ('onda quadra','square wave')],
    value='impulse',
    description='Input:',
    disabled=False
)
# Define the values of the input
u = widgets.FloatSlider(
    value=1,
    min=0,
    max=20.0,
    step=0.1,
    description='input u:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)
period = widgets.FloatSlider(
    value=0.5,
    min=0.0,
    max=10,
    step=0.01,
    description='Periodo: ',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
)
```

```python
def main_callback(A, B, C, X0w, L, u, period, sel_system, selu, DW):
    global Adefault, Bdefault, Cdefault, Aw, Bw, Cw, Lw

    if sel_system == 'Manual':
        sol = numpy.linalg.eig(A-L*C)
    elif sel_system == 'Unstable unobservable state':
        A = Adefault[0]
        B = Bdefault
        C = Cdefault
        Aw.setM(A)
        Bw.setM(B)
        Cw.setM(C)
        L = numpy.matrix([[4],[2],[2]])
        Lw.setM(L)
        sol = numpy.linalg.eig(A-L*C)
    elif sel_system == 'Stable unobservable state':
        A = Adefault[1]
        B = Bdefault
        C = Cdefault
        Aw.setM(A)
        Bw.setM(B)
        Cw.setM(C)
        L = numpy.matrix([[4],[2],[10]])
        Lw.setM(L)
        sol = numpy.linalg.eig(A-L*C)

    sol1 = numpy.linalg.eig(A)

    print('Gli autovalori del sistema sono:',round(sol1[0][0],4),',',round(sol1[0][1],4),'e',round(sol1[0][2],4))
    print('Gli autovalori dell\'osservatore sono:',round(sol[0][0],4),',',round(sol[0][1],4),'e',round(sol[0][2],4))

    sys = sss(A,B,C,0)
    syso = sss(A-L*C, numpy.concatenate((B,L),axis=1), numpy.eye(3), numpy.zeros(6).reshape((3,2)))
    if (numpy.real([sol[0][0],sol[0][1],sol[0][2]]) == [0, 0, 0]).all():
        T = numpy.linspace(0,20,1000)
    else:
        if min(numpy.abs(numpy.real([sol[0][0],sol[0][1],sol[0][2]]))) != 0:
            T = numpy.linspace(0,10*1/min(numpy.abs(numpy.real([sol[0][0],sol[0][1],sol[0][2]]))),1000)
        else:
            T = numpy.linspace(0,10*1/max(numpy.abs(numpy.real([sol[0][0],sol[0][1],sol[0][2]]))),1000)

    if selu == 'impulse': #selu
        U = [0 for t in range(0,len(T))]
        U[0] = u
        T, yout, xout = control.forced_response(sys,T,U,X0w)
        T, youto, xouto = control.forced_response(syso,T,numpy.matrix([U,yout]),[[0],[0],[0]])
    if selu == 'step':
        U = [u for t in range(0,len(T))]
        T, yout, xout = control.forced_response(sys,T,U,X0w)
        T, youto, xouto = control.forced_response(syso,T,numpy.matrix([U,yout]),[[0],[0],[0]])
    if selu == 'sinusoid':
        U = u*numpy.sin(2*numpy.pi/period*T)
        T, yout, xout = control.forced_response(sys,T,U,X0w)
        T, youto, xouto = control.forced_response(syso,T,numpy.matrix([U,yout]),[[0],[0],[0]])
    if selu == 'square wave':
        U = u*numpy.sign(numpy.sin(2*numpy.pi/period*T))
        T, yout, xout = control.forced_response(sys,T,U,X0w)
        T, youto, xouto = control.forced_response(syso,T,numpy.matrix([U,yout]),[[0],[0],[0]])

    fig = plt.figure(num='Simulation', figsize=(16,10))

    fig.add_subplot(321)
    plt.ylabel('$X_1$ vs $X_{1est}$ (uscita del sistema)')
    plt.plot(T,xout[0])
    plt.plot(T,xouto[0])
    plt.xlabel('t [s]')
    plt.legend(['Reale','Stimato'])
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.grid()

    fig.add_subplot(323)
    plt.ylabel('$X_2$ vs $X_{2est}$')
    plt.plot(T,xout[1])
    plt.plot(T,xouto[1])
    plt.xlabel('t [s]')
    plt.legend(['Reale','Stimato'])
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.grid()

    fig.add_subplot(325)
    plt.ylabel('$X_3$ vs $X_{3est}$')
    plt.plot(T,xout[2])
    plt.plot(T,xouto[2])
    plt.xlabel('t [s]')
    plt.legend(['Reale','Stimato'])
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.grid()

    fig.add_subplot(322)
    plt.ylabel('$e_1$')
    plt.plot(T,xouto[0]-xout[0])
    plt.xlabel('t [s]')
    plt.grid()

    fig.add_subplot(324)
    plt.ylabel('$e_1$')
    plt.plot(T,xouto[1]-xout[1])
    plt.xlabel('t [s]')
    plt.grid()

    fig.add_subplot(326)
    plt.ylabel('$e_1$')
    plt.plot(T,xouto[2]-xout[2])
    plt.xlabel('t [s]')
    plt.grid()

    fig1 = plt.figure(num='Output simulation', figsize=(16,5))
    fig1.add_subplot(111)
    plt.ylabel('$y$')
    plt.plot(T,yout)
    plt.xlabel('t [s]')
    plt.grid()

alltogether = widgets.VBox([widgets.HBox([sel_system, widgets.Label(' ',border=3), selu]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.Label('A:',border=3), Aw,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('B:',border=3), Bw,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('X0:',border=3), X0w,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('L:',border=3), Lw]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.Label('C:',border=3), Cw]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([u,
                                          period,
                                          START])])
out = widgets.interactive_output(main_callback, {'A':Aw, 'B':Bw, 'C':Cw, 'X0w':X0w, 'L':Lw,
                                                 'u':u, 'period':period, 'sel_system':sel_system,
                                                 'selu':selu, 'DW':DW})
out.layout.height = '980px'
display(out, alltogether)
```

    Output(layout=Layout(height='980px'))

    VBox(children=(HBox(children=(Dropdown(index=1, options=(('Manuale', 'Manual'), ('Stato instabile e inosservab…


## Heading

I am trying out Jupyter to webpage conversion using jekyll.

```python
import keras as kr
```

---
title: "Title"
author: "Me"
header-includes:
   - \usepackage{bbm}
output:
    pdf_document
---

Let us try some latex. $56+x+c+\pi$

$$d+\phi^2_3$$

\begin{equation}
d+\phi^2_3
\end{equation}

~~strike~~. **bold**
```python
import stuff
print(stuff)```

\begin{document}
\begin{center}
\begin{Large}
\textbf{A Self-driving car simulation using Q-Learning}\\
\end{Large}
\vspace{2mm}
\begin{small}
\textbf{Tushar Agarwal} (agarwal.270@osu.edu), \textbf{Wenxiao Zhan} (zhan.137@osu.edu)\\
\end{small}
\end{center}

\section*{Introduction}

Creating intelligent machines has long been a human endeavor with a rich past as well as present. Reinforcement learning is one major component of this endeavor. As the name suggests, reinforcement learning refers to a reward based learning system designed for a machine to teach it the correct behavior of acting in a specified environment. It has its roots in behaviorist psychology and can be simply thought of as the reward based training routine usually followed while teaching pet animals like dogs. Such a training requires a time-dependent modeling of the processes. \\

\subsection*{Components}
Important components of an RL agent are the policy, the value function and the model.
\begin{itemize}

\item The model predicts how the environment will behave next. It has 2 main sub-components:-\\
The transition probability: $\mathcal{P}(s,a,s')=P(s_{t+1}=s'|s_{t}=s,a_{t}=a)$\\
The Reward: $r(s,a)=E[r_{t+1}|s_{t}=s,a_{t}=a]$

\item The policy $\pi$ defines the agents behavior and is a mapping from state $s_t$ to action $a_t$. It can be be deterministic or probabilistic.\\
Deterministic policy: $a_t=\pi(s_t)$\\
Probabilistic policy: $\pi(a|s)=P(a_t=a|s_t=s)$

\item Value function is a prediction of the future reward. It tries to measure the goodness or badness of states $V(s_t)$ or state-action pairs $q(s_t,a_t)$ to help make a decision between available set of actions $A$ at a given state.\\
State value function for a given policy $\pi$: $V_{\pi}(s)=E_{\pi}[G_t|s_t=s]$\\
State-action value function for a given policy $\pi$: $Q_{\pi}(s,a)=E_{\pi}[G_t|s_t=s,a_t=a]$\\

where $G_t=r_t+\sum_{i>t}\gamma^{i-t}r_i$ is a measure of the cumulative reward.
The term $\gamma \in [0,1)$ is a discount factor used to represent the fact that the present reward is more valuable than the future reward as the future is always uncertain. This is a very practical assumption and has real life manifestations like time value of money in finances. It also allows the cumulative reward to be finite in case of infinite time horizon, i.e. when the agent takes actions forever.

\end{itemize}


```python
%matplotlib inline
```

.. currentmodule:: dgl

Working with Heterogeneous Graphs
=================================

**Author**: Quan Gan, `Minjie Wang <https://jermainewang.github.io/>`_, Mufei Li,
George Karypis, Zheng Zhang

In this tutorial, you learn about:

* Examples of heterogenous graph data and typical applications.

* Creating and manipulating a heterogenous graph in DGL.

* Implementing `Relational-GCN <https://arxiv.org/abs/1703.06103>`_, a popular GNN model,
  for heterogenous graph input.

* Training a model to solve a node classification task.

Heterogeneous graphs, or *heterographs* for short, are graphs that contain
different types of nodes and edges. The different types of nodes and edges tend
to have different types of attributes that are designed to capture the
characteristics of each node and edge type. Within the context of
graph neural networks, depending on their complexity, certain node and edge types
might need to be modeled with representations that have a different number of dimensions.

DGL supports graph neural network computations on such heterogeneous graphs, by
using the heterograph class and its associated API.

Examples of heterographs
-----------------------
Many graph datasets represent relationships among various types of entities.
This section provides an overview for several graph use-cases that show such relationships
and can have their data represented as heterographs.

Citation graph
~~~~~~~~~~~~~~~
The Association for Computing Machinery publishes an `ACM dataset <https://aminer.org/citation>`_ that contains two
million papers, their authors, publication venues, and the other papers
that were cited. This information can be represented as a heterogeneous graph.

The following diagram shows several entities in the ACM dataset and the relationships among them
(taken from `Shi et al., 2015 <https://arxiv.org/pdf/1511.04854.pdf>`_).

.. figure:: https://data.dgl.ai/tutorial/hetero/acm-example.png#

This graph has three types of entities that correspond to papers, authors, and publication venues.
It also contains three types of edges that connect the following:

* Authors with papers corresponding to *written-by* relationships

* Papers with publication venues corresponding to *published-in* relationships

* Papers with other papers corresponding to *cited-by* relationships

Recommender systems
~~~~~~~~~~~~~~~~~~~~
The datasets used in recommender systems often contain
interactions between users and items. For example, the data could include the
ratings that users have provided to movies. Such interactions can be modeled
as heterographs.

The nodes in these heterographs will have two types, *users* and *movies*. The edges
will correspond to the user-movie interactions. Furthermore, if an interaction is
marked with a rating, then each rating value could correspond to a different edge type.
The following diagram shows an example of user-item interactions as a heterograph.

.. figure:: https://data.dgl.ai/tutorial/hetero/recsys-example.png

Knowledge graph
~~~~~~~~~~~~~~~~
Knowledge graphs are inherently heterogenous. For example, in
Wikidata, Barack Obama (item Q76) is an instance of a human, which could be viewed as
the entity class, whose spouse (item P26) is Michelle Obama (item Q13133) and
occupation (item P106) is politician (item Q82955). The relationships are shown in the following.
diagram.

.. figure:: https://data.dgl.ai/tutorial/hetero/kg-example.png

Creating a heterograph in DGL
-----------------------------
You can create a heterograph in DGL using the :func:`dgl.heterograph` API.
The argument to :func:`dgl.heterograph` is a dictionary. The keys are tuples
in the form of ``(srctype, edgetype, dsttype)`` specifying the relation name
and the two entity types it connects. Such tuples are called *canonical edge
types*. The values are data to initialize the graph structures, that is, which
nodes the edges actually connect.

For instance, the following code creates the user-item interactions heterograph shown earlier.

```python
# Each value of the dictionary is a pair of source and destination arrays.
# Nodes are integer IDs starting from zero. Nodes IDs of different types have
# separate countings.
import dgl
import numpy as np

ratings = dgl.heterograph(
    {('user', '+1', 'movie') : (np.array([0, 0, 1]), np.array([0, 1, 0])),
     ('user', '-1', 'movie') : (np.array([2]), np.array([1]))})
```

    Using backend: pytorch

DGL supports creating a graph from a variety of data sources. The following
code creates the same graph as the above.

Creating from scipy matrix

```python
import scipy.sparse as sp
plus1 = sp.coo_matrix(([1, 1, 1], ([0, 0, 1], [0, 1, 0])), shape=(3, 2))
minus1 = sp.coo_matrix(([1], ([2], [1])), shape=(3, 2))
ratings = dgl.from_scipy(
    {('user', '+1', 'movie') : plus1,
     ('user', '-1', 'movie') : minus1})

# Creating from networkx graph
import networkx as nx
plus1 = nx.DiGraph()
plus1.add_nodes_from(['u0', 'u1', 'u2'], bipartite=0)
plus1.add_nodes_from(['m0', 'm1'], bipartite=1)
plus1.add_edges_from([('u0', 'm0'), ('u0', 'm1'), ('u1', 'm0')])
# To simplify the example, reuse the minus1 object.
# This also means that you could use different sources of graph data
# for different relationships.
ratings = dgl.heterograph(
    {('user', '+1', 'movie') : plus1,
     ('user', '-1', 'movie') : minus1})
```

Manipulating heterograph
------------------------
You can create a more realistic heterograph using the ACM dataset. To do this, first
download the dataset as follows:

```python
import scipy.io
import urllib.request

data_url = 'https://data.dgl.ai/dataset/ACM.mat'
data_file_path = '/tmp/ACM.mat'

urllib.request.urlretrieve(data_url, data_file_path)
data = scipy.io.loadmat(data_file_path)
print(list(data.keys()))
```

    ['__header__', '__version__', '__globals__', 'TvsP', 'PvsA', 'PvsV', 'AvsF', 'VvsC', 'PvsL', 'PvsC', 'A', 'C', 'F', 'L', 'P', 'T', 'V', 'PvsT', 'CNormPvsA', 'RNormPvsA', 'CNormPvsC', 'RNormPvsC', 'CNormPvsT', 'RNormPvsT', 'CNormPvsV', 'RNormPvsV', 'CNormVvsC', 'RNormVvsC', 'CNormAvsF', 'RNormAvsF', 'CNormPvsL', 'RNormPvsL', 'stopwords', 'nPvsT', 'nT', 'CNormnPvsT', 'RNormnPvsT', 'nnPvsT', 'nnT', 'CNormnnPvsT', 'RNormnnPvsT', 'PvsP', 'CNormPvsP', 'RNormPvsP']

The dataset stores node information by their types: ``P`` for paper, ``A``
for author, ``C`` for conference, ``L`` for subject code, and so on. The relationships
are stored as SciPy sparse matrix under key ``XvsY``, where ``X`` and ``Y``
could be any of the node type code.

The following code prints out some statistics about the paper-author relationships.

```python
print(type(data['PvsA']))
print('#Papers:', data['PvsA'].shape[0])
print('#Authors:', data['PvsA'].shape[1])
print('#Links:', data['PvsA'].nnz)
```

    <class 'scipy.sparse.csc.csc_matrix'>
    #Papers: 12499
    #Authors: 17431
    #Links: 37055

Converting this SciPy matrix to a heterograph in DGL is straightforward.

```python
pa_g = dgl.from_scipy(data['PvsA'], ('paper', 'written-by', 'author'))
# equivalent (shorter) API for creating heterograph with two node types:
pa_g = dgl.bipartite(data['PvsA'], 'paper', 'written-by', 'author')
```

You can easily print out the type names and other structural information.

```python
print('Node types:', pa_g.ntypes)
print('Edge types:', pa_g.etypes)
print('Canonical edge types:', pa_g.canonical_etypes)

# Nodes and edges are assigned integer IDs starting from zero and each type has its own counting.
# To distinguish the nodes and edges of different types, specify the type name as the argument.
print(pa_g.number_of_nodes('paper'))
# Canonical edge type name can be shortened to only one edge type name if it is
# uniquely distinguishable.
print(pa_g.number_of_edges(('paper', 'written-by', 'author')))
print(pa_g.number_of_edges('written-by'))
print(pa_g.successors(1, etype='written-by'))  # get the authors that write paper #1

# Type name argument could be omitted whenever the behavior is unambiguous.
print(pa_g.number_of_edges())  # Only one edge type, the edge type argument could be omitted
```

A homogeneous graph is just a special case of a heterograph with only one type
of node and edge. In this case, all the APIs are exactly the same as in
:class:`DGLGraph`.

```python
# Paper-citing-paper graph is a homogeneous graph
pp_g = dgl.heterograph({('paper', 'citing', 'paper') : data['PvsP']})
# equivalent (shorter) API for creating homogeneous graph
pp_g = dgl.graph(data['PvsP'], 'paper', 'cite')

# All the ntype and etype arguments could be omitted because the behavior is unambiguous.
print(pp_g.number_of_nodes())
print(pp_g.number_of_edges())
print(pp_g.successors(3))
```

Create a subset of the ACM graph using the paper-author, paper-paper,
and paper-subject relationships.  Meanwhile, also add the reverse
relationship to prepare for the later sections.

```python
G = dgl.heterograph({
        ('paper', 'written-by', 'author') : data['PvsA'],
        ('author', 'writing', 'paper') : data['PvsA'].transpose(),
        ('paper', 'citing', 'paper') : data['PvsP'],
        ('paper', 'cited', 'paper') : data['PvsP'].transpose(),
        ('paper', 'is-about', 'subject') : data['PvsL'],
        ('subject', 'has', 'paper') : data['PvsL'].transpose(),
    })

print(G)
```

**Metagraph** (or network schema) is a useful summary of a heterograph.
Serving as a template for a heterograph, it tells how many types of objects
exist in the network and where the possible links exist.

DGL provides easy access to the metagraph, which could be visualized using
external tools.

```python
# Draw the metagraph using graphviz.
import pygraphviz as pgv
def plot_graph(nxg):
    ag = pgv.AGraph(strict=False, directed=True)
    for u, v, k in nxg.edges(keys=True):
        ag.add_edge(u, v, label=k)
    ag.layout('dot')
    ag.draw('graph.png')

plot_graph(G.metagraph)
```

Learning tasks associated with heterographs
-------------------------------------------
Some of the typical learning tasks that involve heterographs include:

* *Node classification and regression* to predict the class of each node or
  estimate a value associated with it.

* *Link prediction* to predict if there is an edge of a certain
  type between a pair of nodes, or predict which other nodes a particular
  node is connected with (and optionally the edge types of such connections).

* *Graph classification/regression* to assign an entire
  heterograph into one of the target classes or to estimate a numerical
  value associated with it.

In this tutorial, we designed a simple example for the first task.

A semi-supervised node classification example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Our goal is to predict the publishing conference of a paper using the ACM
academic graph we just created. To further simplify the task, we only focus
on papers published in three conferences: *KDD*, *ICML*, and *VLDB*. All
the other papers are not labeled, making it a semi-supervised setting.

The following code extracts those papers from the raw dataset and prepares
the training, validation, testing split.

```python
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

pvc = data['PvsC'].tocsr()
# find all papers published in KDD, ICML, VLDB
c_selected = [0, 11, 13]  # KDD, ICML, VLDB
p_selected = pvc[:, c_selected].tocoo()
# generate labels
labels = pvc.indices
labels[labels == 11] = 1
labels[labels == 13] = 2
labels = torch.tensor(labels).long()

# generate train/val/test split
pid = p_selected.row
shuffle = np.random.permutation(pid)
train_idx = torch.tensor(shuffle[0:800]).long()
val_idx = torch.tensor(shuffle[800:900]).long()
test_idx = torch.tensor(shuffle[900:]).long()
```

Relational-GCN on heterograph
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We use `Relational-GCN <https://arxiv.org/abs/1703.06103>`_ to learn the
representation of nodes in the graph. Its message-passing equation is as
follows:

\begin{align}h_i^{(l+1)} = \sigma\left(\sum_{r\in \mathcal{R}}
   \sum_{j\in\mathcal{N}_r(i)}W_r^{(l)}h_j^{(l)}\right)\end{align}

Breaking down the equation, you see that there are two parts in the
computation.

(i) Message computation and aggregation within each relation $r$

(ii) Reduction that merges the results from multiple relationships

Following this intuition, perform message passing on a heterograph in
two steps.

(i) Per-edge-type message passing

(ii) Type wise reduction

```python
import dgl.function as fn

class HeteroRGCNLayer(nn.Module):
    def __init__(self, in_size, out_size, etypes):
        super(HeteroRGCNLayer, self).__init__()
        # W_r for each relation
        self.weight = nn.ModuleDict({
                name : nn.Linear(in_size, out_size) for name in etypes
            })

    def forward(self, G, feat_dict):
        # The input is a dictionary of node features for each type
        funcs = {}
        for srctype, etype, dsttype in G.canonical_etypes:
            # Compute W_r * h
            Wh = self.weight[etype](feat_dict[srctype])
            # Save it in graph for message passing
            G.nodes[srctype].data['Wh_%s' % etype] = Wh
            # Specify per-relation message passing functions: (message_func, reduce_func).
            # Note that the results are saved to the same destination feature 'h', which
            # hints the type wise reducer for aggregation.
            funcs[etype] = (fn.copy_u('Wh_%s' % etype, 'm'), fn.mean('m', 'h'))
        # Trigger message passing of multiple types.
        # The first argument is the message passing functions for each relation.
        # The second one is the type wise reducer, could be "sum", "max",
        # "min", "mean", "stack"
        G.multi_update_all(funcs, 'sum')
        # return the updated node feature dictionary
        return {ntype : G.nodes[ntype].data['h'] for ntype in G.ntypes}
```

Create a simple GNN by stacking two ``HeteroRGCNLayer``. Since the
nodes do not have input features, make their embeddings trainable.

```python
class HeteroRGCN(nn.Module):
    def __init__(self, G, in_size, hidden_size, out_size):
        super(HeteroRGCN, self).__init__()
        # Use trainable node embeddings as featureless inputs.
        embed_dict = {ntype : nn.Parameter(torch.Tensor(G.number_of_nodes(ntype), in_size))
                      for ntype in G.ntypes}
        for key, embed in embed_dict.items():
            nn.init.xavier_uniform_(embed)
        self.embed = nn.ParameterDict(embed_dict)
        # create layers
        self.layer1 = HeteroRGCNLayer(in_size, hidden_size, G.etypes)
        self.layer2 = HeteroRGCNLayer(hidden_size, out_size, G.etypes)

    def forward(self, G):
        h_dict = self.layer1(G, self.embed)
        h_dict = {k : F.leaky_relu(h) for k, h in h_dict.items()}
        h_dict = self.layer2(G, h_dict)
        # get paper logits
        return h_dict['paper']
```

Train and evaluate
~~~~~~~~~~~~~~~~~~
Train and evaluate this network.

```python
# Create the model. The output has three logits for three classes.
model = HeteroRGCN(G, 10, 10, 3)

opt = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

best_val_acc = 0
best_test_acc = 0

for epoch in range(100):
    logits = model(G)
    # The loss is computed only for labeled nodes.
    loss = F.cross_entropy(logits[train_idx], labels[train_idx])

    pred = logits.argmax(1)
    train_acc = (pred[train_idx] == labels[train_idx]).float().mean()
    val_acc = (pred[val_idx] == labels[val_idx]).float().mean()
    test_acc = (pred[test_idx] == labels[test_idx]).float().mean()

    if best_val_acc < val_acc:
        best_val_acc = val_acc
        best_test_acc = test_acc

    opt.zero_grad()
    loss.backward()
    opt.step()

    if epoch % 5 == 0:
        print('Loss %.4f, Train Acc %.4f, Val Acc %.4f (Best %.4f), Test Acc %.4f (Best %.4f)' % (
            loss.item(),
            train_acc.item(),
            val_acc.item(),
            best_val_acc.item(),
            test_acc.item(),
            best_test_acc.item(),
        ))
```

What's next?
------------
* Check out our full implementation in PyTorch
  `here <https://github.com/dmlc/dgl/tree/master/examples/pytorch/rgcn-hetero>`_.

* We also provide the following model examples:

  * `Graph Convolutional Matrix Completion <https://arxiv.org/abs/1706.02263>_`,
    which we implement in MXNet
    `here <https://github.com/dmlc/dgl/tree/v0.4.0/examples/mxnet/gcmc>`_.

  * `Heterogeneous Graph Attention Network <https://arxiv.org/abs/1903.07293>`_
    requires transforming a heterograph into a homogeneous graph according to
    a given metapath (i.e. a path template consisting of edge types).  We
    provide :func:`dgl.transform.metapath_reachable_graph` to do this.  See full
    implementation
    `here <https://github.com/dmlc/dgl/tree/master/examples/pytorch/han>`_.

  * `Metapath2vec <https://dl.acm.org/citation.cfm?id=3098036>`_ requires
    generating random walk paths according to a given metapath.  Please
    refer to the full metapath2vec implementation
    `here <https://github.com/dmlc/dgl/tree/master/examples/pytorch/metapath2vec>`_.

* :doc:`Full heterograph API reference <../../api/python/heterograph>`.