## 量子プログラミング入門：TKET 編

Kentaro Yamamoto (Research Scientist)

**Cambridge Quantum Computing Japan**

kentaro.yamamoto@cambridgequantum.com

### Overview

In this webinar, we will provide demos on `pytket` and `qermit`, both of which are open-source Python libraries developed by Cambridge Quantum.
`pytket` is a Python interface to TKET --- platform-agnostic quantum software development kit (QSDK), wheras `qermit` is a quantum error mitigation framework integrated with `pytket`.
Examples are taken from quantum chemistry problems, but the codes are available for general purposes.
By using `pytket` and `qermit`, **one can quickly prototype and run the platform-agnostic quantum programs.**

### Why TKET?

How does pytket satisfy the constraints of NISQ-era quantum computers?

* A Generic Compilation Strategy for the Unitary Coupled Cluster Ansatz → **arXiv:2007.10515**

* t|ket$\rangle$: A Retargetable Compiler for NISQ Devices → **arXiv:2003.10611**

* Phase Gadget Synthesis for Shallow Circuits → **arXiv:1906.01734**

* On the qubit routing problem → **arXiv:1902.08091**

[github.com/CQCL/pytket](https://github.com/CQCL/pytket)

### Required python packages

Please install the packages
```
pytket          # Quantum circuit construction
pytket-qiskit   # Quantum circuit compilation
qermit          # Quantum circuit execution (from CQCL/qermit)
```
If these packages are not installed in your environment yet, uncomment the following cell and install them.

```python
#!pip install pytket==0.15.0
#!pip install pytket-qiskit==0.18.0
#!pip install qermit==0.1.2
```

Version numbers are specified to make sure this script works,
but in general it is recommended to install the latest versions.

[Getting started with Qiskit](https://qiskit.org/documentation/getting_started.html) would be helpful for users who are not familiar with setting up Python environment, including the IBM Quantum account setup to access IBM's hardware used in the following examples.

### TKET Walled Garden: Playing with retargetability

```python
# Preparing backends
from pytket.extensions.qiskit import IBMQBackend, IBMQEmulatorBackend, AerBackend

device_id = 'ibmq_quito'
# backend = IBMQBackend(device_id)
backend = IBMQEmulatorBackend(device_id)
noiseless_backend = AerBackend()
```

Construct a quantum circuit for a Bell pair $|\psi\rangle = \frac{1}{2}(|00\rangle + |11\rangle)$ as a `pytket` object.

```python
# Generate quantum circuit.
from pytket import Circuit

bell_circuit = Circuit(2,2).H(0).CX(0,1).measure_all()
```

```python
from pytket.circuit.display import render_circuit_jupyter

render_circuit_jupyter(bell_circuit)
```

<!DOCTYPE html>

    <meta charset="UTF-8">

q[0]

q[1]

c[0]

c[1]

                    H

                    X

z

z

q[0]

q[1]

c[0]

c[1]

Retargeting: `pytket` $\to$ `Qiskit`

```python
# Display the quantum circuit using Qiskit.
from pytket.extensions.qiskit import tk_to_qiskit

qisk_c = tk_to_qiskit(bell_circuit)
print(type(qisk_c))
print(qisk_c)
```

    <class 'qiskit.circuit.quantumcircuit.QuantumCircuit'>
         ┌───┐     ┌─┐
    q_0: ┤ H ├──■──┤M├───
         └───┘┌─┴─┐└╥┘┌─┐
    q_1: ─────┤ X ├─╫─┤M├
              └───┘ ║ └╥┘
    c: 2/═══════════╩══╩═
                    0  1

Compile the circuit for a given backend.

```python
# Compile quantum circuit for the IBM devices.
backend.compile_circuit(bell_circuit)

qisk_c = tk_to_qiskit(bell_circuit)
print(qisk_c)
```

    node_0: ──────────────────────────────────
            ┌────┐┌─────────┐┌────┐     ┌─┐
    node_1: ┤ √X ├┤ Rz(π/2) ├┤ √X ├──■──┤M├───
            └────┘└─────────┘└────┘┌─┴─┐└╥┘┌─┐
    node_2: ───────────────────────┤ X ├─╫─┤M├
                                   └───┘ ║ └╥┘
       c: 2/═════════════════════════════╩══╩═
                                         0  1

Retargeting: `pytket` $\to$ `OpenQASM`

```python
# Convert the circuit into OpenQASM format.
from pytket.qasm import circuit_to_qasm_str

qasm_c = circuit_to_qasm_str(bell_circuit)
print(qasm_c)
```

    OPENQASM 2.0;
    include "qelib1.inc";

    qreg node[3];
    creg c[2];
    sx node[1];
    rz(0.5*pi) node[1];
    sx node[1];
    cx node[1],node[2];
    measure node[1] -> c[0];
    measure node[2] -> c[1];

```python
# Convert the circuit from OpenQASM format.
from pytket.qasm import circuit_from_qasm_str
render_circuit_jupyter(circuit_from_qasm_str(qasm_c))
```

<!DOCTYPE html>

    <meta charset="UTF-8">

node[0]

node[1]

node[2]

c[0]

c[1]

                    SX

                    Rz(0.5)

                    SX

                    X

z

z

node[0]

node[1]

node[2]

c[0]

c[1]

Make a measurement with the given backend.

```python
# Execute quantum circuit.
handle = backend.process_circuit(bell_circuit, n_shots=1000)
result = backend.get_result(handle)

# Check the shot counts.
print(result.get_counts())
```

    Counter({(0, 0): 487, (1, 1): 476, (0, 1): 20, (1, 0): 17})

### Leaving the Walled Garden via VQE

It is widely believed that a variational (hybrid quantum-classical) algorithm
is a promising approach to attain quantum advantage with NISQ devices.
In the variational algorithm,
heavy tasks such as expectation value evaluation are performed by quantum computers,
while the other tasks are handled by classical computers to drive a feedback loop.

Variational quantum eigensolver (VQE) is one of the most frequently used
variational algorithms.
In the VQE algorithm,
we consider a cost function $E(\boldsymbol{\theta})$
depending on a set of
parameters $\boldsymbol{\theta}$
describing an associated trial wavefunction $|\Psi(\boldsymbol{\theta})\rangle$,
which is expressed as

$$
    E(\boldsymbol{\theta})
    =
    \langle
        \Psi(\boldsymbol{\theta}) | \hat{H} | \Psi(\boldsymbol{\theta})
    \rangle
$$

where $\hat{H}$ is a Hamiltonian describing the system.
The trial wavefunction $|\Psi(\boldsymbol{\theta})\rangle$ is actually implemented
into the quantum computer in the following form

$$
    |\Psi(\boldsymbol{\theta})\rangle
    =
    \hat{U}(\boldsymbol{\theta}) |\Psi_{0}\rangle
$$

where $|\Psi_{0}\rangle$ corresponds to an initial state that should be
easily prepared.
The unitary operator $\hat{U}(\boldsymbol{\theta})$ is implemented as
a parameterized quantum circuit,
or ansatz.
The Hamiltonian $\hat{H}$ is usually transformed into a series of $k$-local operators such as Pauli strings (tensor product of qubit local Pauli operator)
$\{\hat{P}_{a}\}$

$$
    \hat{H} = \sum_{a}h_{a}\hat{P}_{a}
$$

where $\{h_{a}\}$ is a set of coefficients, each of which is complex valued in general.
Thus by evaluating $\langle \Psi(\boldsymbol{\theta})|\hat{P}_{a}|\Psi(\boldsymbol{\theta})\rangle$
for all $a$,
$E(\boldsymbol{\theta})$ can be evaluated.

In summary, to perform VQE algorithm, we need the following quantities:

* $\hat{H}=\sum_{a}h_{a}\hat{P}_{a}$: Hamiltonian (qubit Pauli operator)
* $|\Psi(\boldsymbol{\theta})\rangle = \hat{U}(\boldsymbol{\theta}) |\Psi_{0}\rangle$: Ansatz (parametrized quantum circuit)

#### Hamiltonian operator as a `QubitPauliOperator`

In the examples below, we use Hamiltonian coming from quantum chemistry problems.
The qubit Hamiltonian was preliminary calculatied with
[EUMEN](https://cqcl.github.io/eumen/build/html/index.html), a proprietary quantum computational chemistry packaged developed by Cambridge Quantum.
The hard-coded qubit Pauli operator below is generated from a fermionic Hamiltonian in the Hartree-Fock molecular orbital (MO) basis using Jordan-Wigner transformation.
Here the MOs are calculated for a hydrogen molecule (the H-H distance is set to 0.735 Å) with STO-3G basis set.
Qubit tapering by exploiting symmetry (alpha- and beta-spin particle number conservation) is applied to obtain a two qubit model system.

```python
# Prepare a qubit Hamiltonian in the direct (second-quantized) mapping.
from pytket.circuit import Qubit
from pytket.pauli import Pauli, QubitPauliString
from pytket.utils import QubitPauliOperator

n_qubits = 2

qubits = [Qubit(i) for i in range(n_qubits)]
qpo_identity = -0.3324042513238792
qpo_dict = {
    QubitPauliString([qubits[0]], [Pauli.Z]): -0.39793742484318023,
    QubitPauliString([qubits[1]], [Pauli.Z]): -0.39793742484318023,
    QubitPauliString([qubits[0], qubits[1]], [Pauli.Z, Pauli.Z]): 0.011280104256235296,
    QubitPauliString([qubits[0], qubits[1]], [Pauli.Y, Pauli.Y]): -0.18093119978423144,
}
operator = QubitPauliOperator(qpo_dict)
for k, v in operator._dict.items():
    print(f'{v:8.4f} {k}')
```

     -0.3979 (Zq[0])
     -0.3979 (Zq[1])
      0.0113 (Zq[0], Zq[1])
     -0.1809 (Yq[0], Yq[1])

```python
# Some reference energy values
hf_energy = -1.116998996754
cc_energy = -1.137306035759
```

####  Ansatz as a symbolic `Circuit` and circuit optimization with TKET

In some chemistry-motivated ansatzes such as unitary coupled cluster (UCC),
a Pauli exponential $e^{i\theta_{m}\hat{P}_{m}}$ is a building block as
$$
    \hat{U}(\boldsymbol{\theta}) = \prod_{m}e^{i\theta_{m}\hat{P}_{m}}
$$
There is a general recipe to efficiently implement a Pauli exponential to a circuit, which is called Pauli gadget.
`pytket` provides an easy way to construct a pauli gadget as shown below:

```python
from pytket.circuit import PauliExpBox, fresh_symbol, OpType
from pytket.passes import DecomposeBoxes

box = PauliExpBox([Pauli.I, Pauli.Z, Pauli.X, Pauli.Y], fresh_symbol('tm'))
circ = Circuit(4)
circ.add_pauliexpbox(box, circ.qubits)
DecomposeBoxes().apply(circ)
render_circuit_jupyter(circ)
```

<!DOCTYPE html>

    <meta charset="UTF-8">

q[0]

q[1]

q[2]

q[3]

                    H

                    V

                    X

                    X

                    Rz(tm)

                    X

                    X

                    H

                    Vdg

q[0]

q[1]

q[2]

q[3]

In NISQ era, it is important to reduce the circuit depth.
`pytket` has various compilation passes to perform circuit optimization.
In the following example we optimize a large pauli gadgets by using `pytket.PauliSimp`.

```python
import numpy as np

def get_random_pauli_gadgets(n_qubits, n_pauli_gadgets, max_entangle):
    """ """
    paulis = [Pauli.I, Pauli.X, Pauli.Y, Pauli.Z]
    circ = Circuit(n_qubits)
    for i in range(n_pauli_gadgets):
        ls_paulis = [np.random.choice(paulis) for k in range(max_entangle)]
        if ls_paulis.count(Pauli.Y) % 2 == 0:
            continue
        if len(ls_paulis) - ls_paulis.count(Pauli.I) <= 1:
            continue
        # print(ls_paulis)
        qubits = np.random.choice(
            [i for i in range(n_qubits)], size=max_entangle, replace=False
        )
        box = PauliExpBox(ls_paulis, fresh_symbol('x'))
        circ.add_pauliexpbox(box, sorted(qubits))
    DecomposeBoxes().apply(circ)
    return circ

circ = get_random_pauli_gadgets(
    n_qubits=12, n_pauli_gadgets=500, max_entangle=8
)
print('Circuit depth:', circ.depth())
print('CX depth:     ', circ.depth_by_type(OpType.CX))
# render_circuit_jupyter(circ)
```

    Circuit depth: 3017
    CX depth:      2417

Now apply the compiler pass to optimize the circuit.

```python
# Circuit optimization by using compiler passes.
from pytket.passes import PauliSimp
circx = circ.copy()
PauliSimp().apply(circx)
print('Circuit depth:', circx.depth())
print('CX depth:     ', circx.depth_by_type(OpType.CX))
```

    Circuit depth: 2364
    CX depth:      1739

#### Simple 2 qubit 1 parameter ansatz for demonstration

For the simple demo, we use the ansatz given as

$$
    \Psi(\theta)
    =
    \hat{U}(\theta)|\Psi_{0}\rangle
    =
    e^{i\theta \hat{Y}_{0}\hat{X}_{1}}|00\rangle
$$

This ansatz is equivalent to the configuration interaction doubles (CID) expressed as

$$
    \Psi(\theta) = \cos(\theta)|1100\rangle + \sin(\theta)|0011\rangle
$$

in the original computational basis in the Jordan-Wigner mapping.

```python
from pytket.circuit import PauliExpBox, fresh_symbol
from pytket.passes import DecomposeBoxes
from sympy import pi

n_qubits = 2
ansatz = Circuit(n_qubits)
t = fresh_symbol('t')
box = PauliExpBox([Pauli.Y, Pauli.X], -1 * (-2 * t / pi))
ansatz.add_pauliexpbox(box, ansatz.qubits)
DecomposeBoxes().apply(ansatz)
ansatz0 = ansatz.copy()
render_circuit_jupyter(ansatz)
```

<!DOCTYPE html>

    <meta charset="UTF-8">

q[0]

q[1]

                    V

                    H

                    X

                    Rz(2*t/pi)

                    X

                    Vdg

                    H

q[0]

q[1]

### Circuit Execution

* [github.com/CQCL/qermit](https://github.com/CQCL/qermit)
* [User manual](https://cqcl.github.io/qermit/manual/index.html)

Preparation for using `MitEx` object of `qermit`.

```python
# Generate ObservableTracker object to store the measurement circuit.
from qermit import ObservableTracker
tracker = ObservableTracker(operator)

# Ansatz.
from qermit import AnsatzCircuit, SymbolsDict

optimal_parameter = -0.11223625
ansatz = ansatz0.copy()
ansatz.symbol_substitution({t: optimal_parameter})
ansatz_circuit = AnsatzCircuit(ansatz, 8000, SymbolsDict())

# Prepare experiment by specifing the ansatz and the operator.
from qermit import ObservableExperiment

experiment = ObservableExperiment(ansatz_circuit, tracker)
```

First run the simple `MitEx` without noise mitigation.

```python
from qermit import MitEx

mitex = MitEx(backend)
mitex.get_task_graph()
```

```python
result = mitex.run([experiment])
energy = sum(result[0]._dict.values()) + qpo_identity
print(f'E  = {energy:13.6f} Ha')
print(f'E0 = {cc_energy:13.6f} Ha (reference CC energy)')
```

    E  =     -1.108424 Ha
    E0 =     -1.137306 Ha (reference CC energy)

Then run the `MitEx` with noise mitigation technique.

```python
from qermit.clifford_noise_characterisation import gen_CDR_MitEx

cdr_mitex = gen_CDR_MitEx(device_backend=backend,
                      simulator_backend=noiseless_backend,
                      n_non_cliffords=2,
                      n_pairs=2,
                      total_state_circuits=50
)
cdr_mitex.get_task_graph()
```

```python
# This may take some time...
result = cdr_mitex.run([experiment])
energy = sum(result[0]._dict.values()) + qpo_identity
print(f'E  = {energy:13.6f} Ha')
print(f'E0 = {cc_energy:13.6f} Ha (reference CC energy)')
```

    E  =     -1.133449 Ha
    E0 =     -1.137306 Ha (reference CC energy)


**ALEXANDRA RAIBOLT**

MSc Student @ IME and BI & Data Management @ Firjan

# Construa sua própria Rede Neural Convolucional em 3, 2, 1...!

Uma introdução as Redes Neurais Convolucionais em Python, usando o framework TensorFlow com Keras.

## Visão Geral

Este Jupyter Notebook mostra passo a passo, o processo de construção de uma Rede Neural Convolucional para reconhecimento e clasificação de Dígitos Manuscritos em Python, usando o framework TensorFlow com Keras.

Neste exemplo, usamos o conjunto de dados [MNIST](http://yann.lecun.com/exdb/mnist "MNIST").

**Aviso prévio:**

O modelo de Redes Neurais Convolucional proposto nesta palestra foi implementado em Python (versão 5.4.0) usando o framework TensorFlow (versão 1.4.0) com Keras (versão 2.2.4) usando uma arquitetura baseada em GPU, e pode não funcionar com outras versões.

## Requisitos

- Python 3 (versão 5.4.0);
- TensorFlow (versão 1.4.0);
- Keras (versão 2.2.4);
- Jupyter Notebook (versão 4.4.0).

## Dependências

- matplotlib;
- numpy.

Você pode instalar dependências ausentes com [pip](https://pip.pypa.io/en/stable "pip"). E instalar o TensorFlow via [TensorFlow](https://www.tensorflow.org/install "TensorFlow") link.

## O que é Aprendizado de Máquina?

Aprendizado de Máquina (em inglês, *Machine Learning*) é uma subárea da Inteligencia Artifical que tem como propósito automatizar máquinas através da criação de modelos analíticos. Tal ideia se baseia na premissa de que máquinas tem a habilidade de aprender, tomar decisões, extrair *insights* e reconhecer padrões (através de dados) sem a necessidade de serem explicitamente programadas, ou seja, sem interferência humana.

## Tipos de Aprendizado de Máquina:

# Aprendizado Supervisionado:

Um conjunto de dados de entradas e suas respectivas saídas desejadas (denominadas como classes) são fornecidas por um “professor” ao modelo/computador, mostrando que há relação entre os dados de entrada e de saída.

O objetivo deste tipo de aprendizado está em mapear as entradas para as suas respectivas saídas desejadas ao encontrar uma regra geral que seja apta a classificar novos dados entre as classes já presentes no conjunto de dados.

## Classificação:

Em uma tarefa de classificação, o objetivo é mapear os tipos de entradas e categoriza-las em classes distintas, ou seja, realiza previsões onde os resultados de saída são de natureza discreta.

Algoritmos de classificação podem ser usados para problemas como: classificação de gênero, classificação de imagens médicas, classificação de expressões faciais, etc.

K-vizinhos mais próximos, Máquinas de Vetores de Suporte e Redes Neurais Convolucionais são exemplos de algoritmos de classificação.

## O que é Rede Neural Artificial?

Tendo como exemplo, a identificação de objetos e ambientes distintos, é questionado como o cérebro humano é capaz de traduzir uma imagem na retina humana para algo onde um indivíduo consiga identificar e classificar.

Compreendendo toda a complexidade do cérebro humano, tornou-se necessário o estudo e compreensão de como os sistemas neurais biológicos funcionam, e a partir de tais estudos, pesquisadores foram capazes de proporcionar a estrutura básica para a construção de modelos de Redes Neurais Artificiais (em inglês, *Artificial Neural Networks - ANN*).

## O que é Rede Neural Convolucional?

As diversas arquiteturas de Redes Neurais Convolucionais são aplicadas com sucesso em desafios de reconhecimentoe classificação de imagens, embora sejam também utilizadas em processamento de vídeo, e Processamento de Linguagem Natural (em inglês, *Natural Language Processing*).

Uma Rede Neural Convolucional pode ser caracterizada como sendo uma ANN de múltiplas camadas onde primariamente faz-se a suposição de que os dados de entrada sejam imagens.

As Redes Neurais Convolucionais hoje, são consideradas estado-da-arte para a resolução de problemas na área de Visão Computacional, possuindo uma variedade de modelos e de arquiteturas (AlexNet, Inception, ResNet, etc.)

Uma arquitetura básica de CNN pode ser dividida em camadas.

## Arquitetura de uma Rede Neural Convolucional

## Camada de Entrada (ou em inglês, Input Layer)

Onde a imagem (ou base de imagens) é inserida na rede.

Computacionalmente falando, toda imagem pode ser representada como sendo uma matriz de valores de pixel, como pode ser visto abaixo:

Portanto, ao receber uma imagem em escala de cinza como entrada, o computador "enxergará" uma matriz de valores de pixel (*m* x *n* x *1*), onde o valor 1 refere-se a um componente pré-estabelecido de uma imagem – Canal - neste caso o valor 1 refere-se a uma imagem em escala de cinza. Portanto, será atribuído valores de intensidade de pixels dentro do intervalode 0 à 255 (onde 0 representa pontos pretos e 255 representa pontos brancos) para cada ponto da matriz.

## Mãos a obra!

```python
# Imports
from keras.datasets import mnist
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Dense, Dropout, Flatten
from keras.models import Sequential
from keras.optimizers import Adam
import keras
import matplotlib.pyplot as plt
import numpy as np
```

    Using TensorFlow backend.

```python
# Dimensions of the data

# We know that images are 28 pixels in each dimension
in_height = 28
in_width = 28

# Tuple with height and width of images used to reshape arrays
image_shape = (in_height, in_width)

# We know MNIST has 60.000 training set images
num_data_train_images = 60000

# We know MNIST has 10.000 training set images
num_data_test_images = 10000

# Classes info
classes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

# Number of classes
num_classes = len(classes)

# Number of colour channels for the images: 1 channel for gray-scale
# Channels mean number of primary colors
num_channels = 1
```

```python
# Load Dataset

# The data, split between train and test sets
# The datasets are 3D arrays: (batch, height, width)
# Returns the values of the function for images_set, labels_set on train-set
# Returns the values of the function for images_set, labels_set on test-set
(data_train_images, data_train_labels), (data_test_images, data_test_labels) = mnist.load_data()

# Our CNN accepts only 4D arrays: (batch, height, width, channels)

# Reshape data_train_images
data_train_images = data_train_images.reshape(num_data_train_images, in_height, in_width, num_channels)

# Reshape data_test_images
data_test_images = data_test_images.reshape(num_data_test_images, in_height, in_width, num_channels)
```

```python
print("Size of:")
print("- Training-set:\t\t{}".format(len(data_train_images)))
print("- Test-set:\t\t{}".format(len(data_test_images)))
```

    Size of:
    - Training-set:		60000
    - Test-set:		10000

## Codificação One-Hot

As classes que representam os dez digitos manuscritos são representados por valores numéricos.

Utilizar valores numéricos para representar rótulos categóricos em algoritmos de ML não é considerado como sendo uma boa prática, pois é possível que estes valores gerados possam prejudicar e influenciar a efetividade do algoritmo através do processo de aprendizado.

Na presença desta restrição, convertemos os valores numéricos que representam os rótulos categóricos (tanto os valores numéricos do conjunto de treinamento quanto os valores numéricos do conjunto de teste) para vetores binários que representam cada um dos valores numéricos, através da codificação One-Hot.

A codificação One-Hot é uma codificação de estados que consiste em gerar $ n $ vetores binários para $ n $ valores numéricos que representam cada um dos rótulos categóricos. Deste modo, One-Hot realiza a codificação para cada estado, de forma que cada valor numérico seja representado por um único vetor binário de tamanho $ n $ igual a quantidade de classes presentes no conjunto de dados.

Por exemplo: a saída $ 8 $, de acordo com a codificação One-Hot seria: $ [0, 0, 0, 0, 0, 0, 0, 0, 1, 0] $

```python
# Class labels are One-Hot coded, meaning that each label is a vector with 10 elements,
# all of which are zero except one element
data_train_labels = keras.utils.to_categorical(data_train_labels, num_classes)
data_test_labels = keras.utils.to_categorical(data_test_labels, num_classes)

data_test_cls = np.argmax(data_test_labels, axis=1)

print(data_test_cls)
```

    [7 2 1 ..., 4 5 6]

```python
# Helper-function for catch the class
# number and print its corresponding name
def get_number(num_class):
    if num_class == 0:
        return 'Zero'
    elif num_class == 1:
        return 'One'
    elif num_class == 2:
        return 'Two'
    elif num_class == 3:
        return 'Three'
    elif num_class == 4:
        return 'Four'
    elif num_class == 5:
        return 'Five'
    elif num_class == 6:
        return 'Six'
    elif num_class == 7:
        return 'Seven'
    elif num_class == 8:
        return 'Eight'
    elif num_class == 9:
        return 'Nine'
```

```python
# Helper-function for plotting images
def plot_images(images, cls_true, cls_pred=None):
    assert len(images) == len(cls_true) == 9

    # Create figure with 3x3 sub-plots
    fig, axes = plt.subplots(3, 3)
    fig.subplots_adjust(hspace=0.3, wspace=0.3)

    for i, ax in enumerate(axes.flat):
        # Plot image
        ax.imshow(images[i].reshape(image_shape), cmap='gray')

        # Show true and predicted classes
        # T = True, and P = Predicted
        if cls_pred is None:
            xlabel = "T: {0}".format(get_number(cls_true[i]))
        else:
            xlabel = "T: {0}, P: {1}".format(get_number(cls_true[i]), get_number(cls_pred[i]))

        # Show the classes as the label on the x-axis
        ax.set_xlabel(xlabel)

        # Remove ticks from the plot
        ax.set_xticks([])
        ax.set_yticks([])

    plt.show()
```

```python
# Plot a few images to see if data is correct

# Get the first images from the test-set
images = data_test_images[0:9]

# Get the true classes for those images
cls_true = data_test_cls[0:9]

# Plot the images and labels using our helper-function above
plot_images(images=images, cls_true=cls_true)
```

```python
# We build a sequential model
# The Sequential model is a linear stack of layers
model = Sequential()
```

## Camada de Convolução (ou em inglês, Convolutional Layer)

É a principal operação que ocorre nas imagens inseridas na rede, e seu principal objetivo está em extrair recursos de imagens de entrada.

Uma imagem $ g $ é gerada pela convolução de um filtro $ f $ (comumente conhecido na literatura em inglês como kernel) com a imagem de entrada idefinida pela equação abaixo:

$$ g(m,n)=\sum_{i=1}^{q}\sum_{j=1}^{q}f(i,j)I(m-i,n-j) $$

onde $ q $ é a dimensão da máscara de convolução.

Um filtro pode ressaltar determinada característica presente em uma imagem, como por exemplo, sombras, bordas, entre outros.

A Figura abaixo apresenta o resultado de uma convolução aplicada a uma imagem de entrada.

Onde o resultado da convolução é comumente conhecido como um mapa de recursos (em inglês,feature maps), e onde o filtro convolucional $ f $ utilizado foi uma matriz de tamanho $ 3x3 $ como na representação a seguir:

\begin{equation}
\label{equation:kernel}
f = \begin{bmatrix}
1 &0   &0 \\
0 &-1  &0 \\
0 &0   &0
\end{bmatrix}
\end{equation}

## Camada  ReLU (ou em inglês, ReLU Layer)

É utilizada para que a rede possa convergir mais rápido, aplica para as camadas escondidas da rede uma função de ativação não linear à saída $ x $ da camada anterior, como abaixo:

\begin{equation}
\label{equation:relu}
f(x) = max(0, x)
\end{equation}

Portanto, a saída será $ 0 $ quando a entrada for menor que $ 0 (x < 0) $, ou a saída será $ 1 $ caso contrário. Desta forma a ativação é limitada exclusivamente a $ 0 $, logo, a função de ativação ReLU contribui em uma etapa de treinamento da rede mais rápida para arquiteturas profundas

```python
# Convolutional Layer 1

# Convolution filters are 3 x 3 pixels
filter_height_1 = 3
filter_width_1 = 3

# There are 8 of these filters
num_filters_1 = 32

# Convolutional Layer 2

# Convolution filters are 3 x 3 pixels
filter_height_2 = 3
filter_width_2 = 3

# There are 16 of these filters
num_filters_2 = 64
```

```python
# Convolutional Layer 1
model.add(Conv2D(filters=num_filters_1,
                 kernel_size=(filter_height_1, filter_width_1),
                 activation='relu',
                 input_shape=(in_height, in_width, num_channels)))
```

```python
# Convolutional Layer 2
model.add(Conv2D(filters=num_filters_2,
                 kernel_size=(filter_height_2, filter_width_2),
                 activation='relu'))
```

## Camada de Subamostragem (ou em inglês, Pooling Layer)

Reduz a dimensionalidade dos feature maps que serão utilizados pelas camadas seguintes, entretanto, sem perder as informações mais relevantes.  Desta forma,  podemos definir a camada de subamostragem como uma convolução que possui o objetivo de reduzir o tamanho espacial da representação, de forma a contribuir com a diminuição de quantidade de parâmetros e processamento computacional da rede.

A função do Max-Pooling está em pegar o valor máximo dos elementos do feature maps presentes na janela $ 2x2 $ e agrupa-los como no exemplo abaixo:

```python
# This is 2x2 max-pooling, which means that we
# consider 2x2 windows and select the largest value
# in each window. Then we move 2 pixels to the next window
model.add(MaxPooling2D(pool_size=(2, 2),
                       strides=(2, 2)))
```

```python
# Dropout randomly switches off some neurons in
# the network which forces the data to find new paths
model.add(Dropout(0.25))
```

## Camada de Flatten (ou em inglês, Flatten Layer)

Após passar pelas camadas de convolução, ReLU e subamostragem a próxima etapa de uma CNN é a classificação, ou seja, é necessário passar pela camada totalmente conectada.  A camada totalmente conectada aceita apenas como entrada, dados de dimensão 1D.

A camada Flatten possui o objetivo de "achatar" todo o volume de dados 3D em um único vetor 1D de características para posteriormente servir de entrada para a camada totalmente conectada.

```python
# It allows the output to be processed by standard fully connected layers
model.add(Flatten())
```

## Camada totalmente conectada (ou em inglês, Fully Connected Layer)

Essa camada age como um classificador, se localiza ao final da rede, geralmente é do tipo soft-max usado para classificar a imagem de entrada. Realiza tal tarefa ao reconhecer padrões que foram gerados pelas camadas anteriores. Portanto, estima a probabilidade de quais das $ n $ classes a imagem de entrada pertence.

```python
# Fully-connected layer
# Number of neurons in fully-connected layer
fc_size = 128

model.add(Dense(fc_size, activation='relu'))
```

```python
# Dropout randomly switches off some neurons in
# the network which forces the data to find new paths
model.add(Dropout(0.5))
```

```python
# Normalization of class-number output with 10 neurons (number of output classes)
# and it uses softmax activation function.
# Each neuron will give the probability of that class.
#It’s a multi-class classification that’s why softmax activation function
model.add(Dense(num_classes, activation='softmax'))
```

```python
# Define the type of loss function, optimizer and the
# metrics evaluated by the model during training and test

# Cross Entropy as a Loss function
# The lower the loss, the better a model
# The loss is calculated on training and its interperation is how well the model is doing for this one set
# Loss is not a percentage. It is a summation of the errors made for each example in training  set

# AdamOptimizer as optimizer
# It is an optimization algorithm used to perform the update of weights
# in an iterative way according to the training data set
# Needs few memory requirements, in addition to being computationally efficient

# and Accuracy as metrics to improve the performance of our neural network

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adam(),
              metrics=['accuracy'])
```

```python
# Training

# Epoch:
# One Epoch is when an ENTIRE dataset is passed forward
# and backward through the neural network only ONCE

# Batch Size:
#Total number of training examples present in a single batch

# Iterations:
# Iterations is the number of batches needed to complete one epoch

# Verbose:
# Integer 0, 1, or 2
# Verbosity mode: 0 = silent, 1 = progress bar, 2 = one line per epoch

batch_size = 128
epochs = 12

history = model.fit(data_train_images, data_train_labels,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1)
```

    Epoch 1/12
    60000/60000 [==============================] - 27s 457us/step - loss: 1.7346 - acc: 0.8363
    Epoch 2/12
    60000/60000 [==============================] - 10s 163us/step - loss: 0.1262 - acc: 0.9625
    Epoch 3/12
    60000/60000 [==============================] - 10s 164us/step - loss: 0.0964 - acc: 0.9708
    Epoch 4/12
    60000/60000 [==============================] - 10s 162us/step - loss: 0.0776 - acc: 0.9761
    Epoch 5/12
    60000/60000 [==============================] - 10s 163us/step - loss: 0.0677 - acc: 0.9795
    Epoch 6/12
    60000/60000 [==============================] - 10s 165us/step - loss: 0.0610 - acc: 0.9808
    Epoch 7/12
    60000/60000 [==============================] - 10s 164us/step - loss: 0.0561 - acc: 0.9823
    Epoch 8/12
    60000/60000 [==============================] - 10s 164us/step - loss: 0.0486 - acc: 0.9849
    Epoch 9/12
    60000/60000 [==============================] - 10s 165us/step - loss: 0.0483 - acc: 0.9847
    Epoch 10/12
    60000/60000 [==============================] - 10s 164us/step - loss: 0.0437 - acc: 0.9860
    Epoch 11/12
    60000/60000 [==============================] - 10s 164us/step - loss: 0.0462 - acc: 0.9857
    Epoch 12/12
    60000/60000 [==============================] - 10s 163us/step - loss: 0.0404 - acc: 0.9871

## Google Cloud Platform

Oferecida pelo Google, o GCP é um conjunto de ferramentas, soluções e serviços de computação em nuvem que atua na mesma infraestrutura central do Google, ao lado de seus produtos, como o YouTube e o seu próprio mecanismo de busca.

O GCP disponibiliza uma série de soluções para processamento computacional, rede, armazenamento de dados, banco de dados escaláveis, exploração de dados, Internet das Coisas (em inglês,Internet of Things- IoT), ML, ferramentas de gerenciamento, monitoramento, registro, diagnósticos, entre outros.

Este Notebook foi processado por  meio de uma instância de máquina virtual de alto desempenho na infraestrutura do Google - Google Compute Engine (GCE) - um componente Infraestrutura como Serviço (em inglês, Infrastructure as a Service- IaaS).

O GCE é um recurso disponível na plataforma Google Cloud Platform (GCP). A instância de máquina virtual on demand é composta por uma GPU NVIDIA® Tesla® K80 com memória da GPU de 12GB GDDR5 e 8 vCPUs disponíveis, além de 16 GB de memória RAM e 50GB de HD e é acessada por meio da interface de linha de comandos.

```python
# Test
score = model.evaluate(data_test_images, data_test_labels, verbose=0)

# Print test accuracy
print('Test accuracy:', score[1])
```

    Test accuracy: 0.9894

## Dicas:

- [TensorFlow](https://www.tensorflow.org/ "TensorFlow");
- [Keras](https://keras.rstudio.com/index.html "Keras");
- [An Intuitive Explanation of Convolutional Neural Networks](https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets "An Intuitive Explanation of Convolutional Neural Networks");
- [Hvass-Labs](https://github.com/Hvass-Labs/TensorFlow-Tutorials "Hvass-Labs");
- [How to set up Tensorflow with CUDA and cuDNN on a free Google Cloud instance](https://www.youtube.com/watch?v=abEf3wQJBmE "How to set up Tensorflow with CUDA and cuDNN on a free Google Cloud instance").

## Leituras Recomendadas:

- BEALE, Russell; JACKSON, Tom. [Neural Computing-an introduction](https://bayanbox.ir/view/7901640340179926235/Neural-Computing-An-Introduction.pdf "Neural Computing-an introduction"). CRC Press, 1990.

## Referências:

- [Towards Data Science](https://towardsdatascience.com/build-your-own-convolution-neural-network-in-5-mins-4217c2cf964f "Towards Data Science");
- [Hvass-Labs](https://github.com/Hvass-Labs/TensorFlow-Tutorials/blob/master/02_Convolutional_Neural_Network.ipynb "Hvass-Labs").

# Distribuição MIT

Código lançado sob a licença [MIT](https://github.com/whoisraibolt/Convolutional-Neural-Network-MNIST/blob/master/LICENSE "MIT").


##### Copyright 2019 Google LLC.

Licensed under the Apache License, Version 2.0 (the "License")

```
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
```

# On Variational Bounds of Mutual Information
Ben Poole, Sherjil Ozair, Aäron van den Oord, Alexander A. Alemi, George Tucker

*ICML 2019*

[paper](https://arxiv.org/abs/1905.06922) / [slides](https://docs.google.com/presentation/d/1dz0AVBy6VDz7SU43m0IQjuJDviEuf3NpWSeIWov_DYc/present?usp=sharing) / [video](https://www.facebook.com/icml.imls/videos/2202320806483370/?t=2562) / [poster](https://drive.google.com/file/d/1C5ZYqSJRaOupQelqYgVbc9lJ7__0CDXA/view?usp=sharing)

This notebook contains code for most of the variational bounds on mutual information presented in the paper, and experiments on the toy Gaussian problem.

Comments? Complaints? Questions? Bug Ben on Twitter [@poolio](https://twitter.com/poolio)

Thanks to [Zhe Dong](https://www.linkedin.com/in/hoogendong/) for valuable feedback on this colab.

### Introduction

Here we give a brief overview of the challenges of MI estimation and a few of the variational bounds we presented in the paper. Please checkout the [paper](https://arxiv.org/abs/1905.06922) for more details.

Our goal is to build efficient estimators of mutual information (MI) that leverage neural networks. Mutual information is given by:
$$I(X; Y) = \mathbb{E}_{p(x,y)}\left[\log \frac{p(x,y)}{p(x)p(y)}\right].$$
Estimating MI is challenging as we often only have access to samples $x, y$ but do not know the densities.

To overcome these challenges, we review and presented several variational estimators of MI. These estimators replace the intractable MI objective with a tractable objective that lower or upper bounds MI, and depends on neural-network-powered critics and baselines. For example, the $I_\text{NWJ}$ bound is given by:

$$ I(X; Y) \ge 1 + \mathbb{E}_{p(x, y)} \left[\color{blue} f(x,y) \right] - \mathbb{E}_{p(x)p(y)} \left[  \exp{{\color{blue} f(x , y)}}\right] $$
where $\color{blue} f$ is a neural network that takes $x$ and $y$ as input and outputs a scalar.

Below, we code up several variational lower and upper bounds on mutual information:
- Existing lower bounds: $I_\text{NWJ}, \,I_\text{InfoNCE}$
- New lower bounds: $I_\text{TUBA},\, I_{\alpha},\, I_\text{JS}$
- Structured lower bound ($I_\text{TNCE}$) and upper bounds ($I_\text{BA}$, $I_\text{MBU}$) with known $p(y|x)$

Here's a table of the bounds we'll code up here as well as the mathematical objectives:

We apply these bounds to a toy problem, where $(x, y)$ are jointly Gaussian, and we vary the correlation between $x$ and $y$ over time to increase the mutual information. For the bounds with neural network critics, we train the critics to tighten the variational bounds.

### Practical suggestions
- MI estimates with neural networks are highly sensitive to the representation of the input data, architecture, optimizer, and more. When applying these estimators to new datasets, you will need to experiment!
- For representation learning, the $I_\text{NCE}$ bounds are a good place to start. Implementing them is simple.
- For estimation, start with the $I_\alpha$ bounds with a small $\alpha$. The implementation is trickier, but they can greatly reduce variance vs. $I_\text{NWJ}$.
- If you have known structure use it. For example, if you know a conditional distribution $p(y|x)$, use one of the structured bounds.

### Implementation notes:
- For most bounds, we will work with a matrix, denoted by `f` or `scores` that contains the output of the critic for every pair of elements in the minibatch: `scores[i, j] = critic(x[j], y[i])`
- Most variational lower bounds on MI require samples from both the joint $p(x, y)$ and marginal distributions $p(x)p(y)$. For a minibatch of size $K$, we have $K$ samples from the joint $p(x,y)$ and can form samples from the marginal by using all pairs $x_i, y_j$ where $i \ne j$, giving $K \times (K -1)$ samples from the marginal. The diagonal elements of the `scores` matrix correspond to the $K$ critic scores for samples from the joint, and the off-diagonal elements correspond to the critic scores for samples from the marginal.
- In this notebook we assume that we use all other elements in the minibatch as negative samples. For certain InfoMax tasks, for example when negatives come from the same image but at other spatial locations, one has to be more careful about summing over and selecting the right positive and negative examples to form the two expectations.
- Separable critics are the more efficient and popular architecture. Instead of having to do $K^2$ forward passes, you only have to do $2 \times K$ followed by an inner product.

## Setup
Notebook should work in TF 1.X, but uses Eager-mode.

```
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
tf.enable_eager_execution()

import tensorflow_probability as tfp
tfd = tfp.distributions
tfkl = tf.keras.layers
tfp = tfp.layers

import pandas as pd # used for exponential moving average
from scipy.special import logit
import numpy as np
import matplotlib.pyplot as plt
```

## Variational bound implementations
###  TUBA and NWJ lower bounds
The $I_\text{TUBA}$ variational lower bound on mutual information is given by:
$$ I(X; Y) \ge 1 + \mathbb{E}_{p(x, y)} \left[\log {e^{\color{blue} f(x,y)}\over{\color{green}  a(y)}} \right] - \mathbb{E}_{p(x)p(y)} \left[  \frac{e^{{\color{blue} f(x , y)}}}{\color{green} a(y)}\right] \triangleq I_\text{TUBA}$$
These bounds can be low bias, but are typically high variance. To implement this bound, we need the critic scores for all elements in the minibatch: `scores[i, j] = critic(x[i], y[j])`, and the value of the baseline `a[j] = baseline(y[j])` for every `y`. Instead of requiring the baseline to be non-negative, we assume that the argument passed in is $\log {\color{green} a(y)}$, and will exponentiate it to get the baseline ${\color{green} a(y)}$.

The implementation requires averaging the exponentiated critic over all independent samples, corresponding to the off-diagonal terms in `scores`,  which is done by the `reduce_logmeanexp_nodiag` function.

The $I_\text{NWJ}$ bound just uses the constant $e$ for the baseline, which is equivalent to adding 1 to the critic scores.

```
def reduce_logmeanexp_nodiag(x, axis=None):
  batch_size = x.shape[0].value
  logsumexp = tf.reduce_logsumexp(x - tf.linalg.tensor_diag(np.inf * tf.ones(batch_size)), axis=axis)
  if axis:
    num_elem = batch_size - 1.
  else:
    num_elem  = batch_size * (batch_size - 1.)
  return logsumexp - tf.math.log(num_elem)

def tuba_lower_bound(scores, log_baseline=None):
  if log_baseline is not None:
    scores -= log_baseline[:, None]
  batch_size = tf.cast(scores.shape[0], tf.float32)
  # First term is an expectation over samples from the joint,
  # which are the diagonal elmements of the scores matrix.
  joint_term = tf.reduce_mean(tf.linalg.diag_part(scores))
  # Second term is an expectation over samples from the marginal,
  # which are the off-diagonal elements of the scores matrix.
  marg_term = tf.exp(reduce_logmeanexp_nodiag(scores))
  return 1. + joint_term -  marg_term

def nwj_lower_bound(scores):
  # equivalent to: tuba_lower_bound(scores, log_baseline=1.)
  return tuba_lower_bound(scores - 1.)
```

### InfoNCE contrastive lower bound
The InfoNCE variational lower bound is the easiest to implement, and typically provides low variance but high bias estimates of MI:
$$I(X; Y) \ge \mathbb{E}_{p^K(x,y)}\left[\frac{1}{K} \sum_{i=1}^K \log \frac{e^{\color{blue}f(x_i, y_i)}}{\frac{1}{K}\sum_{j=1}^K e^{\color{blue} f(x_j, y_i)}}\right].$$
The expectation $p^K(x,y)$ draws $K$ samples from the joint $(x, y)$. So for a minibatch of $K$ examples, we are effectively forming a single sample Monte-Carlo estimate of the quantity inside the expectation.

Here we use `reduce_logsumexp` so the implementation matches the math, but you can also implement this with `sparse_softmax_cross_entropy_with_logits` using a different label for each of the `K` elements in the minibatch.

```
def infonce_lower_bound(scores):
  """InfoNCE lower bound from van den Oord et al. (2018)."""
  nll = tf.reduce_mean(tf.linalg.diag_part(scores) - tf.reduce_logsumexp(scores, axis=1))
  # Alternative implementation:
  # nll = -tf.nn.sparse_softmax_cross_entropy_with_logits(logits=scores, labels=tf.range(batch_size))
  mi = tf.math.log(tf.cast(scores.shape[0].value, tf.float32)) + nll
  return mi
```

### Interpolated lower bounds
The interpolated lower bounds are the trickiest to implement, but also provide the best of both worlds: lower bias or variance relative to the NWJ and InfoNCE bounds. We start with the multisample TUBA bound from the paper:
$$I(X_1; Y) \geq 1+\mathbb{E}_{p(x_{1:K})p(y|x_1)}\left[\log \frac{e^{f(x_1,y)}}{a(y; x_{1:K})}\right]
- \mathbb{E}_{p(x_{1:K})p(y)}\left[ \frac{e^{f(x_1,y)}}{a(y; x_{1:K})}\right]$$
Coding up this bound is tricky as we need $K+1$ samples from the joint $p(x,y)$: $K$ of these samples are used for the first term, and the additional sample is used for the independent sample from $p(y)$ for the second term. In the minibatch setting, we can implement this by holding out the $j$th sample from the joint,  and using $y_j$ for the indepent sample from $p(y)$ for the second term, and $x_\ne j$ (all elements in the minibatch except element $j$) for the samples from $p(x_{1:K})$. A single-sample Monte-Carlo approximation to the NWJ bound is then given by:

$$1 + \log \frac{e^{f(x_1, y_1)}}{a(y_1; x_{\ne j}, \alpha)} - \frac{e^{f(x_1, y_j)}}{a(y_j; x_{\ne j}, \alpha)}$$
Instead of just using $(x_1, y_1)$ as the only positive example, we can use all samples from the joint in the minibatch: $(x_i, y_i)$. Summing over the $K$ elements which are not the element we are holding out for the second expectation ($y_j$), yields:
$$1 + \frac{1}{K}\sum_{i\ne j} \left(\log \frac{e^{f(x_i, y_i)}}{a(y_i; x_{\ne j}, \alpha)} - \frac{e^{f(x_i, y_j)}}{a(y_j; x_{\ne j}, \alpha)}\right)$$

Furthermore, in the minibatch setting we can choose any element in the minibatch to hold out for the second expectation. Summing over all possible (leave one out) combinations yields:
$$1 + \frac{1}{K+1} \sum_{j=1}^{K+1} \frac{1}{K}\sum_{i\ne j} \left(\log \frac{e^{f(x_i, y_i)}}{a(y_i; x_{\ne j}, \alpha)} - \frac{e^{f(x_i, y_j)}}{a(y_j; x_{\ne j}, \alpha)}\right)$$
where the interpolated baseline is given by $a(y; x_{1:M}) = \alpha \frac{1}{K} \sum_{l=1}^K e^{f(x_l, y)} + (1-\alpha) q(y)$. We work in log-space for numerical stability, using `compute_log_loomean` to compute the leave one out InfoNCE baselines, and `log_interpolate` to compute the log baseline for the interpolated bound.

```
def log_interpolate(log_a, log_b, alpha_logit):
  """Numerically stable implementation of log(alpha * a + (1-alpha) * b)."""
  log_alpha = -tf.nn.softplus(-alpha_logit)
  log_1_minus_alpha = -tf.nn.softplus(alpha_logit)
  y = tf.reduce_logsumexp(tf.stack((log_alpha + log_a, log_1_minus_alpha + log_b)), axis=0)
  return y

def compute_log_loomean(scores):
  """Compute the log leave-one-out mean of the exponentiated scores.

  For each column j we compute the log-sum-exp over the row holding out column j.
  This is a numerically stable version of:
  log_loosum = scores + tfd.softplus_inverse(tf.reduce_logsumexp(scores, axis=1, keepdims=True) - scores)
  Implementation based on tfp.vi.csiszar_divergence.csiszar_vimco_helper.
  """
  max_scores = tf.reduce_max(scores, axis=1, keepdims=True)
  lse_minus_max = tf.reduce_logsumexp(scores - max_scores, axis=1, keepdims=True)
  d = lse_minus_max + (max_scores - scores)
  d_ok = tf.not_equal(d, 0.)
  safe_d = tf.where(d_ok, d, tf.ones_like(d))
  loo_lse = scores + tfd.softplus_inverse(safe_d)
  # Normalize to get the leave one out log mean exp
  loo_lme = loo_lse - tf.math.log(scores.shape[1].value - 1.)
  return loo_lme

def interpolated_lower_bound(scores, baseline, alpha_logit):
  """Interpolated lower bound on mutual information.

  Interpolates between the InfoNCE baseline ( alpha_logit -> -infty),
  and the single-sample TUBA baseline (alpha_logit -> infty)

  Args:
    scores: [batch_size, batch_size] critic scores
    baseline: [batch_size] log baseline scores
    alpha_logit: logit for the mixture probability

  Returns:
    scalar, lower bound on MI
  """
  batch_size = scores.shape[0].value
  # Compute InfoNCE baseline
  nce_baseline = compute_log_loomean(scores)
  # Inerpolated baseline interpolates the InfoNCE baseline with a learned baseline
  interpolated_baseline = log_interpolate(
      nce_baseline, tf.tile(baseline[:, None], (1, batch_size)), alpha_logit)
  # Marginal term.
  critic_marg = scores - tf.linalg.diag_part(interpolated_baseline)[:, None]
  marg_term = tf.exp(reduce_logmeanexp_nodiag(critic_marg))

  # Joint term.
  critic_joint = tf.linalg.diag_part(scores)[:, None] - interpolated_baseline
  joint_term = (tf.reduce_sum(critic_joint) -
                tf.reduce_sum(tf.linalg.diag_part(critic_joint))) / (batch_size * (batch_size - 1.))
  return 1 + joint_term  - marg_term
```

### JS-KL hybrid lower bound
We can also use different approaches for training the critic vs. estimating mutual informaiton. The $I_\text{JS}$ bound just trains the critic with the standard lower bound on the Jensen-Shannon divergence used in GANs, and then evaluates the critic using the $I_\text{NWJ}$ lower bound on KL (mutual information).

We use a fun trick to simplify the code here:
`z = x + tf.stop_gradient(y - x)`.

`z` gives the value of `y` on the forward pass, but the gradient of `x` on the backward pass.

```
def js_fgan_lower_bound(f):
  """Lower bound on Jensen-Shannon divergence from Nowozin et al. (2016)."""
  f_diag = tf.linalg.tensor_diag_part(f)
  first_term = tf.reduce_mean(-tf.nn.softplus(-f_diag))
  n = tf.cast(f.shape[0], tf.float32)
  second_term = (tf.reduce_sum(tf.nn.softplus(f)) - tf.reduce_sum(tf.nn.softplus(f_diag))) / (n * (n - 1.))
  return first_term - second_term

def js_lower_bound(f):
  """NWJ lower bound on MI using critic trained with Jensen-Shannon.

  The returned Tensor gives MI estimates when evaluated, but its gradients are
  the gradients of the lower bound of the Jensen-Shannon divergence."""
  js = js_fgan_lower_bound(f)
  mi = nwj_lower_bound(f)
  return js + tf.stop_gradient(mi - js)
```

### Structured Bounds

See the end of the notebook for code for the structured bounds. The implementation when using these bounds is often different, as we use a known conditional distribution $p(y|x)$ instead of a critic function $f(x, y)$ that scores all pairs of points. For simplicity, we will just demonstrate the minibatch lower bound $I_\text{TNCE}$, which is equivalent to $I_\text{NCE}$ but using the critic $f(x,y) = \log p(y|x)$.

### Putting it together

```
def estimate_mutual_information(estimator, x, y, critic_fn,
                                baseline_fn=None, alpha_logit=None):
  """Estimate variational lower bounds on mutual information.

  Args:
    estimator: string specifying estimator, one of:
      'nwj', 'infonce', 'tuba', 'js', 'interpolated'
    x: [batch_size, dim_x] Tensor
    y: [batch_size, dim_y] Tensor
    critic_fn: callable that takes x and y as input and outputs critic scores
      output shape is a [batch_size, batch_size] matrix
    baseline_fn (optional): callable that takes y as input
      outputs a [batch_size]  or [batch_size, 1] vector
    alpha_logit (optional): logit(alpha) for interpolated bound

  Returns:
    scalar estimate of mutual information
  """
  scores = critic_fn(x, y)
  if baseline_fn is not None:
    # Some baselines' output is (batch_size, 1) which we remove here.
    log_baseline = tf.squeeze(baseline_fn(y))
  if estimator == 'infonce':
    mi = infonce_lower_bound(scores)
  elif estimator == 'nwj':
    mi = nwj_lower_bound(scores)
  elif estimator == 'tuba':
    mi = tuba_lower_bound(scores, log_baseline)
  elif estimator == 'js':
    mi = js_lower_bound(scores)
  elif estimator == 'interpolated':
    assert alpha_logit is not None, "Must specify alpha_logit for interpolated bound."
    mi = interpolated_lower_bound(scores, log_baseline, alpha_logit)
  return mi
```

## Neural network architectures

**Critics**: we consider two choices of neural network architectures for $\color{blue} f(x,y)$:
1. **Separable**: $f(x,y) = g(x)^Th(y)$ where $g$ and $h$ are two different MLPs
2. **Concat**: $f(x,y) = g([x, y])$ where we concatenate $x$ and $y$ and feed them into a single MLP

Using a separable baseline is typically more efficient, as you only have to do `batch_size` forward passes through each network vs. the `batch_size * batch_size` with the concat critic.

**Baselines**: we consider three possibilities for the baseline $\color{green}a(y)$:
1. **Constant**: $a(y)$ is a fixed constant (as in $I_\text{NWJ}$)
2. **Unnormalized**: $a(y)$ is a neural network that produces a scalar output (representing $\log a(y)$)
3. **Gaussian**: $a(y)$ is a Gaussian distribution. Here we fix the mean and variance to be 1, but you could use any tractable density with learnable parameters as well.

### Critics

```
def mlp(hidden_dim, output_dim, layers, activation):
  return tf.keras.Sequential(
      [tfkl.Dense(hidden_dim, activation) for _ in range(layers)] +
      [tfkl.Dense(output_dim)])

class SeparableCritic(tf.keras.Model):
  def __init__(self, hidden_dim, embed_dim, layers, activation, **extra_kwargs):
    super(SeparableCritic, self).__init__()
    self._g = mlp(hidden_dim, embed_dim, layers, activation)
    self._h = mlp(hidden_dim, embed_dim, layers, activation)

  def call(self, x, y):
    scores = tf.matmul(self._h(y), self._g(x), transpose_b=True)
    return scores

class ConcatCritic(tf.keras.Model):
  def __init__(self, hidden_dim, layers, activation, **extra_kwargs):
    super(ConcatCritic, self).__init__()
    # output is scalar score
    self._f = mlp(hidden_dim, 1, layers, activation)

  def call(self, x, y):
    batch_size = tf.shape(x)[0]
    # Tile all possible combinations of x and y
    x_tiled = tf.tile(x[None, :],  (batch_size, 1, 1))
    y_tiled = tf.tile(y[:, None],  (1, batch_size, 1))
    # xy is [batch_size * batch_size, x_dim + y_dim]
    xy_pairs = tf.reshape(tf.concat((x_tiled, y_tiled), axis=2), [batch_size * batch_size, -1])
    # Compute scores for each x_i, y_j pair.
    scores = self._f(xy_pairs)
    return tf.transpose(tf.reshape(scores, [batch_size, batch_size]))

def gaussian_log_prob_pairs(dists, x):
  """Compute log probability for all pairs of distributions and samples."""
  mu, sigma = dists.mean(), dists.stddev()
  sigma2 = sigma**2
  normalizer_term = tf.reduce_sum(-0.5 * (np.log(2. * np.pi) + 2.0 *  tf.math.log(sigma)), axis=1)[None, :]
  x2_term = -tf.matmul(x**2, 1.0 / (2 * sigma2), transpose_b=True)
  mu2_term = - tf.reduce_sum(mu**2 / (2 * sigma2), axis=1)[None, :]
  cross_term = tf.matmul(x, mu / sigma2, transpose_b=True)
  log_prob = normalizer_term + x2_term + mu2_term + cross_term
  return log_prob

def build_log_prob_conditional(rho, **extra_kwargs):
  """True conditional distribution."""
  def log_prob_conditional(x, y):
    mu = x * rho
    q_y = tfd.MultivariateNormalDiag(mu, tf.ones_like(mu) * tf.cast(tf.sqrt(1.0 - rho**2), tf.float32))
    return gaussian_log_prob_pairs(q_y, y)
  return log_prob_conditional

CRITICS = {
    'separable': SeparableCritic,
    'concat': ConcatCritic,
    'conditional': build_log_prob_conditional,
}
```

### Baselines

```
def log_prob_gaussian(x):
  return tf.reduce_sum(tfd.Normal(0., 1.).log_prob(x), -1)

BASELINES= {
    'constant': lambda: None,
    'unnormalized': lambda: mlp(hidden_dim=512, output_dim=1, layers=2, activation='relu'),
    'gaussian': lambda: log_prob_gaussian,
}
```

## Experiments

### Dataset: correlated Gaussian
We experiment with a super simple correlated Gaussian dataset:
\begin{align}
x &\sim \mathcal{N}(0, I_d)\\
y &\sim \mathcal{N}(\rho x, (1 - \rho^2) I_d)
\end{align}
where $d$ is the dimensionality, and $\rho$ is the correlation. Each pair of dimensions $(x_i, y_i)$ has correlation $\rho$, and correlation 0 with all other dimensions. We can control the information by varying the correlation $\rho$:
$$I(X; Y) = -\frac{d}{2} \log \left(1 - \rho^2\right)$$

```
def sample_correlated_gaussian(rho=0.5, dim=20, batch_size=128):
  """Generate samples from a correlated Gaussian distribution."""
  x, eps = tf.split(tf.random.normal((batch_size, 2 * dim)), 2, axis=1)
  y = rho * x + tf.sqrt(tf.cast(1. - rho**2, tf.float32)) * eps
  return x, y

def rho_to_mi(dim, rho):
  return -0.5  * np.log(1-rho**2) * dim

def mi_to_rho(dim, mi):
  return np.sqrt(1-np.exp(-2.0 / dim * mi))

def mi_schedule(n_iter):
  """Generate schedule for increasing correlation over time."""
  mis = np.round(np.linspace(0.5, 5.5-1e-9, n_iter)) *2.0#0.1
  return mis.astype(np.float32)
```

```
plt.figure(figsize=(6,3))
for i, rho in enumerate([0.5, 0.99]):
  plt.subplot(1, 2, i + 1)
  x, y = sample_correlated_gaussian(batch_size=500, dim=1, rho=rho)
  plt.scatter(x[:, 0], y[:, 0])
  plt.title(r'$\rho=%.2f$,  $I(X; Y)=%.1f$' % (rho, rho_to_mi(1, rho)))
  plt.xlim(-3, 3); plt.ylim(-3, 3);
```

### Training code

```
def train_estimator(critic_params, data_params, mi_params):
  """Main training loop that estimates time-varying MI."""
  # Ground truth rho is only used by conditional critic
  critic = CRITICS[mi_params.get('critic', 'concat')](rho=None, **critic_params)
  baseline = BASELINES[mi_params.get('baseline', 'constant')]()

  opt = tf.keras.optimizers.Adam(opt_params['learning_rate'])

  @tf.function
  def train_step(rho, data_params, mi_params):
    # Annoying special case:
    # For the true conditional, the critic depends on the true correlation rho,
    # so we rebuild the critic at each iteration.
    if mi_params['critic'] == 'conditional':
      critic_ = CRITICS['conditional'](rho=rho)
    else:
      critic_ = critic

    with tf.GradientTape() as tape:
      x, y = sample_correlated_gaussian(dim=data_params['dim'], rho=rho, batch_size=data_params['batch_size'])
      mi = estimate_mutual_information(mi_params['estimator'], x, y, critic_, baseline, mi_params.get('alpha_logit', None))
      loss = -mi

      trainable_vars = []
      if isinstance(critic, tf.keras.Model):
        trainable_vars += critic.trainable_variables
      if isinstance(baseline, tf.keras.Model):
        trainable_vars += baseline.trainable_variables
      grads = tape.gradient(loss, trainable_vars)
      opt.apply_gradients(zip(grads, trainable_vars))
    return mi

  # Schedule of correlation over iterations
  mis = mi_schedule(opt_params['iterations'])
  rhos = mi_to_rho(data_params['dim'], mis)

  estimates = []
  for i in range(opt_params['iterations']):
    estimates.append(train_step(rhos[i], data_params, mi_params).numpy())

  return np.array(estimates)
```

Dataset, optimization, and critic parameters. Try experimenting with these.

```
data_params = {
    'dim': 20,
    'batch_size': 64,
}

critic_params = {
    'layers': 2,
    'embed_dim': 32,
    'hidden_dim': 256,
    'activation': 'relu',
}

opt_params = {
    'iterations': 20000,
    'learning_rate': 5e-4,
}
```

Build a dictionary of the mutual information estimators to train and their parameters.

```
critic_type = 'concat' # or 'separable'

estimators = {
    'NWJ': dict(estimator='nwj', critic=critic_type, baseline='constant'),
    'TUBA': dict(estimator='tuba', critic=critic_type, baseline='unnormalized'),
    'InfoNCE': dict(estimator='infonce', critic=critic_type, baseline='constant'),
    'JS': dict(estimator='js', critic=critic_type, baseline='constant'),
    'TNCE': dict(estimator='infonce', critic='conditional', baseline='constant'),
    # Optimal critic for TUBA
    #'TUBA_opt': dict(estimator='tuba', critic='conditional', baseline='gaussian')
}

# Add interpolated bounds
def sigmoid(x):
  return 1/(1. + np.exp(-x))
for alpha_logit in [-5., 0., 5.]:
  name = 'alpha=%.2f' % sigmoid(alpha_logit)
  estimators[name] = dict(estimator='interpolated', critic=critic_type,
                          alpha_logit=alpha_logit, baseline='unnormalized')
```

Train each estimator and store mutual information estimates (this takes ~2 minutes on a modern GPU).

```
estimates = {}
for estimator, mi_params in estimators.items():
  print("Training %s..." % estimator)
  estimates[estimator] = train_estimator(critic_params, data_params, mi_params)
```

    Training NWJ...
    Training TUBA...
    Training InfoNCE...
    Training JS...
    Training TNCE...
    Training alpha=0.01...
    Training alpha=0.50...
    Training alpha=0.99...

### Results

```
# Smooting span for Exponential Moving Average
EMA_SPAN = 200

# Ground truth MI
mi_true = mi_schedule(opt_params['iterations'])

# Names specifies the key and ordering for plotting estimators
names = np.sort(list(estimators.keys()))
lnames = list(map(lambda s: s.replace('alpha', '$\\alpha$'), names))

nrows = min(2, len(estimates))
ncols = int(np.ceil(len(estimates) / float(nrows)))
fig, axs = plt.subplots(nrows, ncols, figsize=(2.7 * ncols, 3 * nrows))
if len(estimates) == 1:
  axs = [axs]
axs = np.ravel(axs)

for i, name in enumerate(names):
  plt.sca(axs[i])
  plt.title(lnames[i])
  # Plot estimated MI and smoothed MI
  mis = estimates[name]
  mis_smooth = pd.Series(mis).ewm(span=EMA_SPAN).mean()
  p1 = plt.plot(mis, alpha=0.3)[0]
  plt.plot(mis_smooth, c=p1.get_color())
  # Plot true MI and line for log(batch size)
  plt.plot(mi_true, color='k', label='True MI')
  estimator = estimators[name]['estimator']
  if 'interpolated' in estimator or 'nce' in estimator:
    # Add theoretical upper bound lines
    if 'interpolated' in estimator:
      log_alpha = -np.log( 1+ tf.exp(-estimators[name]['alpha_logit']))
    else:
      log_alpha = 1.
    plt.axhline(1 + np.log(data_params['batch_size']) - log_alpha, c='k', linestyle='--', label=r'1 + log(K/$\alpha$)' )
  plt.ylim(-1, mi_true.max()+1)
  plt.xlim(0, opt_params['iterations'])
  if i == len(estimates) - ncols:
    plt.xlabel('steps')
    plt.ylabel('Mutual information (nats)')
plt.legend(loc='best', fontsize=8, framealpha=0.0)
plt.gcf().tight_layout();
```

## Structured Bounds

The structured lower bounds use known conditional and learned marginal distributions to lower and upper bound mutual information. Check back soon for some more examples with these bounds!

```
def log_prob_pairs(dists, samples):
  if isinstance(dists, (tfd.Normal, tfd.MultivariateNormalDiag)):
    return gaussian_log_prob_pairs(dists, samples)
  batch_size = tf.shape(samples)[0]
  multiples = [1] * (1 + len(samples.get_shape().as_list()))
  multiples[1] = tf.shape(samples)[0]
  samples_tiled = tf.tile(samples[:, None], multiples)
  # Compute log probs, size [batch_size, batch_size]
  log_probs = dists.log_prob(samples_tiled)
  return log_probs

def variational_upper_bound(conditional_dist, marginal_dist, samples):
  """Variational upper bound on mutual information.

  Args:
    conditional_dist: true conditional density, p(y|x)
    marginal_dist: approximate marginal density, m(y)
    samples: samples from the conditional distribution p(y|x)

  Returns:
    scalar, upper bound on mutual information
  """
  return tf.reduce_mean(conditional_dist.log_prob(samples) -
                        marginal_dist.log_prob(samples))

def minibatch_upper_bound(conditional_dist, samples):
  """Minibatch upper bound on mutual information.

  Args:
    conditional_dist: approximate conditional density, e(y|x)
    samples: samples from conditional_dist

  Returns:
    scalar, upper bound on mutual information
  """
  log_probs = log_prob_pairs(conditional_dist, samples)
  # Batch marginal holds out self (along diagonal), and averages over
  # all other elements in the batch.
  mask = tf.eye(tf.shape(samples)[0])
  log_prob_marginal = tf.reduce_mean(reduce_logmeanexp_nodiag(log_probs, axis=1))
  log_prob_cond = tf.reduce_mean(tf.linalg.tensor_diag_part(log_probs))
  return log_prob_cond - log_prob_marginal

def minibatch_lower_bound(conditional_dist, samples):
  """Minibatch lower bound on mutual information.

  Args:
    conditional_dist: approximate conditional density, e(y|x)
    samples: samples from conditional_dist

  Returns:
    scalar, lower bound on mutual information
  """
  batch_marginal_dist = tfd.MixtureSameFamily(
      mixture_distribution=tfd.Categorical(logits=tf.zeros(conditional_dist.batch_shape)),
      components_distribution=conditional_dist)
  return variational_upper_bound(conditional_dist, batch_marginal_dist, samples)
```


# Cool things

## Running console commands

In jupyter, the bang (`!`) symbolizes an escape into the underlying console (bash, zsh, powershell, cmd).

You can use this to easily grab the stdout of a commandline parameter and use it in a variable.

```python
!ls
```

```python
!uname -a
!whoami
!ls /dev
!lspci
```

```python
files = !ls
["%s.bak" % f for f in files]
```

## Latex

Jupyter formats latex.

$
\begin{equation}
  x = a_0 + \cfrac{1}{a_1
          + \cfrac{1}{a_2
          + \cfrac{1}{a_3 + \cfrac{1}{a_4} } } }
\end{equation}
$

## JupyterLab

Jupyterlab is the future UI for jupyter.

On binder, it is included by default and can be accessed by replacing `tree` in the url by `lab`.

## Save as feature

You can download a notebook as many formats including source code format, html, latex, pdf.

## Convert to presentation

You can use a notebook for presentations and keynotes.

Edit slides by going to View|Cell Toolbar|Slideshow

```python
!jupyter nbconvert 01-Cool-Things.ipynb --to slides # --reveal-prefix=reveal.js --post serve
```

## Examples in the wild

* [A beginner python course I made for my friends](https://hub.mybinder.org/user/bwrsandman-learningpython-6vqa2dhl/tree)
* [Coursea Machine Learning Ex1](https://github.com/jdwittenauer/ipython-notebooks/blob/master/notebooks/ml/ML-Exercise1.ipynb)
* [geojson-extension](https://mybinder.org/v2/gh/binder-examples/jupyterlab/master?urlpath=lab)
* [LIGO black hole detection](https://losc.ligo.org/s/events/GW150914/LOSC_Event_tutorial_GW150914.html)
* [An open RNA-Seq data analysis pipeline tutorial with an example of reprocessing data from a recent Zika virus study](http://nbviewer.jupyter.org/github/maayanlab/Zika-RNAseq-Pipeline/blob/master/Zika.ipynb)
* [Python for Signal Processing](http://nbviewer.jupyter.org/github/unpingco/Python-for-Signal-Processing/blob/master/Compressive_Sampling.ipynb)
* [Timeseries Classification: KNN & DTW](http://nbviewer.jupyter.org/github/markdregan/K-Nearest-Neighbors-with-Dynamic-Time-Warping/blob/master/K_Nearest_Neighbor_Dynamic_Time_Warping.ipynb)
* [OpenCV Detecting Faces](https://github.com/handee/opencv-gettingstarted/blob/master/5%20detecting%20faces%20and%20other%20things.ipynb)
* [Analysis of the Gaza-Israel 2012 crisis](http://nbviewer.jupyter.org/gist/darribas/4121857)
* [Extracting and manipulating article metadata (RDF) from Het Laatste Nieuws](http://nbviewer.jupyter.org/url/mhermans.net/files/tmp/demo_rdf_HLN.ipynb)
* [Map of US Airports (Javascript)](https://nbviewer.jupyter.org/gist/Fil/efb1c9f3f0a9092c420dfe4cef8def96)
* [Blogging with the IPython Notebook](http://nbviewer.jupyter.org/github/fperez/blog/blob/master/120907-Blogging%20with%20the%20IPython%20Notebook.ipynb)
* [XKCD-style graphs](http://nbviewer.jupyter.org/url/jakevdp.github.com/downloads/notebooks/XKCD_plots.ipynb)


# Q# notebooks #

This notebook will show you how to use IQ# to write your own quantum application notebooks.

## Compiling Q# operations

An operation is the basic unit of quantum execution in Q#. It is roughly equivalent to a function in C or C++ or Python, or a static method in C# or Java.

IQ#, the Q# Jupyter kernel, allows you to write Q# operations directly on a code cell and compile them by running the cell (pressing  Ctrl/⌘+Enter). For example:

```qsharp
operation SayHello(name: String) : Unit {

    // The following line will simply write a message to the console:
    Message($"Hello {name}!");

}
```

SayHello

When you **Run** the cell, Q# compiles the code and returns the name of the operations it found.
In this case, it found only one operation (`SayHello`).

If the compiler detects any errors, it will instead show the list of errors in the output. For example:

```qsharp
operation InvalidQ() : Unit {

    // The `FooBar` operation doesn't exist, so the following line
    // will generate a `No variable with that name exists.` error:
    FooBar("Hello again!");

    // `Message` takes only one string argument, so the following line
    // will generate a `Unexpected argument tuple.` error:
    Message(1, 2);
}
```

    C:\snippet_.qs(5,5): error QS5022: No identifier with the name "FooBar" exists.
    C:\snippet_.qs(9,12): error QS6211: Unexpected argument tuple. Expecting an argument of type String.

Q# operations can call other operations previously defined; they can also call all the operations defined in the
[Microsoft.Quantum.Intrinsic](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic) and
[Microsoft.Quantum.Canon](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.canon) namespaces (like [Message](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.message)).

For example, you can create a new operation that calls the previously compiled `SayHello`:

```qsharp
operation HelloAndres() : Unit {
    SayHello("Andres");
}
```

HelloAndres

They can also use all [Q# standard library](https://docs.microsoft.com/qsharp/api/) operations defined in other namespaces by importing the namespace using the `open` statement. For example, to use [`PI`](https://docs.microsoft.com/qsharp/api/prelude/microsoft.quantum.math.pi) you would need to open the ` Microsoft.Quantum.Math` namespace; optionally you can call the operation providing its fully qualified name, for example:

```qsharp
open Microsoft.Quantum.Math;

operation HelloPi() : Unit {
    let pi = Microsoft.Quantum.Convert.DoubleAsString(PI());
    SayHello(pi);
}
```

HelloPi

You can define multiple operations in a single cell and use any valid Q# code, for example:

```qsharp
open Microsoft.Quantum.Random;

/// # Summary
/// Sets the qubit's state to |+⟩
operation SetPlus(q: Qubit) : Unit {
    Reset(q);
    H(q);
}

/// # Summary
/// Sets the qubit's state to |-⟩
operation SetMinus(q: Qubit) : Unit {
    Reset(q);
    X(q);
    H(q);
}

/// # Summary
/// Randomly prepares the qubit into |+⟩ or |-⟩
operation PrepareRandomMessage(q: Qubit) : Unit {

    let choice = DrawRandomInt(0, 1);

    if choice == 0 {
        Message("Prepared |-⟩");
        SetMinus(q);
    } else {
        Message("Prepared |+⟩");
        SetPlus(q);
    }
}
```

SetPlus

SetMinus

PrepareRandomMessage

and create other operations that uses them:

```qsharp
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Measurement;

operation SampleRandomBit() : Result {
    use q = Qubit();
    SetPlus(q);
    return MResetZ(q);
}

operation TestPrepareQubits() : Result {
    mutable r = Zero;

    use qubits = Qubit[5];
    ApplyToEach(PrepareRandomMessage, qubits);
    DumpMachine();

    set r = Measure([PauliX, PauliX, PauliX, PauliX, PauliX], qubits);

    ResetAll(qubits);

    return r;
}
```

SampleRandomBit

TestPrepareQubits

## Simulating Q# operations

Once a Q# operation has been successfully compiled, you can use the `%simulate` command to simulate it. For example:

```qsharp
%simulate HelloPi
```

    Hello 3.141592653589793!

    ()

```qsharp
%simulate SampleRandomBit
```

`%simulate` will print any console output on the notebook, and it will return the operation's return value. If the operation returns `Unit` it prints `()`, otherwise it prints the actual value.

`%simulate` only accepts operations that take no arguments. If you want to call an operation that accepts parameters, like `SayHello`, create a wrapper operation that calls it with the corresponding values, like `HelloPi`.

As mentioned, all messages are printed on the notebook; this includes calls to [`DumpMachine`](https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.extensions.diagnostics.dumpmachine):

```qsharp
%config dump.basisStateLabelingConvention = "bitstring"
```

    "bitstring"

```qsharp
%simulate TestPrepareQubits
```

    Prepared |+⟩
    Prepared |+⟩
    Prepared |-⟩
    Prepared |+⟩
    Prepared |+⟩

                            Qubit IDs
                            0, 1, 2, 3, 4

                                Basis state (bitstring)
                                Amplitude	Meas. Pr.	Phase

                                $\left|00000\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00001\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00010\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00011\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00100\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00101\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00110\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|00111\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01000\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01001\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01010\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01011\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01100\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01101\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01110\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|01111\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10000\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10001\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10010\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10011\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10100\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10101\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10110\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|10111\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11000\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11001\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11010\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11011\right\rangle$
                                $0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11100\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11101\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11110\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

                                $\left|11111\right\rangle$
                                $-0.1768 + 0.0000 i$

                                    <progress
                                        max="100"
                                        value="3.1250000000000013"
                                        style="width: 100%;"
                                    >

                                 ↑

    One

## Estimating resources

The `%estimate` command lets you estimate the resources a given quantum operation will need to execute, without actually executing the operation. Similar to `%simulate` it takes the name of a no-arguments operation. However, `%estimate` does not keep track of the qubit's state and will not return the output of the operation, instead it returns the estimated values of how many resources, like Qubits and CNOT gates, the corresponding operation will use:

```qsharp
%estimate TestPrepareQubits
```

Metric	Sum	Max

CNOT	0	0

QubitClifford	7	7

R	0	0

Measure	11	11

T	0	0

Depth	0	0

Width	5	5

QubitCount	5	5

BorrowedWidth	0	0

To learn more about resources estimation, take a look at [The ResourcesEstimator Target Machine](https://docs.microsoft.com/azure/quantum/user-guide/machines/resources-estimator) documentation.

## The Workspace

The notebook uses the folder it lives on disk to define a workspace. It will try to compile all the Q# files (i.e. all files with a `.qs` extension) it finds under the current folder and will make the operations it finds available to operations in the notebook. For example, the [Operations.qs](/Operations.qs) file in this folder defines two operations:
* Microsoft.Quantum.Samples.IsMinus
* Microsoft.Quantum.Samples.IsPlus

To get the list of operations defined in the workspace, you can use the `%workspace` command:

```qsharp
%workspace
```

Microsoft.Quantum.Samples.IsMinus

Microsoft.Quantum.Samples.IsPlus

These operations can be used in this notebook, for example:

```qsharp
open Microsoft.Quantum.Samples;

operation CheckPlus() : Bool {
    mutable result = false;

    use q = Qubit();
    SetPlus(q);
    set result = IsPlus(q);

    Reset(q);

    return result;
}
```

CheckPlus

```qsharp
%simulate CheckPlus
```

    True

To pick up any changes you make to a Q# file in the workspace, use `%workspace reload`.

```qsharp
%workspace reload
```

    Reloading workspace: done!

Microsoft.Quantum.Samples.IsMinus

Microsoft.Quantum.Samples.IsPlus

## Getting Help ##

Q# supports adding documentation to operations via comments in the code. When such documentation exists, you can access it from the notebook by adding a question mark before or after the operation name on a code cell, for example:

```qsharp
Microsoft.Quantum.Intrinsic.X?
```

 Microsoft.Quantum.Intrinsic.X

Summary

Applies the Pauli $X$ gate.

\begin{align}
\sigma_x \mathrel{:=}
\begin{bmatrix}
0 & 1 \\
1 & 0
\end{bmatrix}.
\end{align}

This documentation is available for any operations in the Prelude, Canon and Workspace, or even those defined locally in the notebook:

```qsharp
PrepareRandomMessage?
```

 PrepareRandomMessage

## Other commands ##

### `%who`

`%who` returns the list of all local and workspace operations available.

```qsharp
%who
```

CheckPlus

HelloAndres

HelloPi

Microsoft.Quantum.Samples.IsMinus

Microsoft.Quantum.Samples.IsPlus

PrepareRandomMessage

SampleRandomBit

SayHello

SetMinus

SetPlus

TestPrepareQubits

### `%package`

`%package` allows you to load nuget packages and makes available any Q# operations defined on them. For example, to use the operations from [Q#'s Quantum Chemistry Library](https://docs.microsoft.com/azure/quantum/user-guide/libraries/chemistry), you must load the [Microsoft.Quantum.Chemistry](https://www.nuget.org/packages/Microsoft.Quantum.Chemistry/) nuget package:

```qsharp
%package Microsoft.Quantum.Chemistry
```

    Adding package Microsoft.Quantum.Chemistry: done!

Microsoft.Quantum.Standard::0.15.2101125897

Microsoft.Quantum.Standard.Visualization::0.15.2101125897

Microsoft.Quantum.Chemistry::0.15.2101125897

`%package` returns the list of nuget packages currently loaded and their version.

### `%project`

`%project` allows you to load Q# projects from folders outside the current workspace and makes available any Q# operations defined in them. For example, to use the operations from the [quantum random number generator sample](https://github.com/microsoft/Quantum/tree/rmshaffer/project-reference-samples/samples/getting-started/qrng), you may add a reference to the `.csproj` file in that folder:

```qsharp
%project ../qrng/Qrng.csproj
```

    Adding reference to project: ../qrng/Qrng.csproj

    Reloading workspace: done!

C:\Users\cgran\source\repos\microsoft\quantum\samples\getting-started\qrng\Qrng.csproj

`%project` returns the list of Q# projects currently loaded.

### `%version`

`%version` simply returns the current versions of IQ# and of Jupyter Core (a library used by IQ#):

```qsharp
%version
```

Component	Version

iqsharp	0.15.2101125897

Jupyter Core	1.5.0.0

.NET Runtime	.NETCoreApp,Version=v3.1

```qsharp

```