# License
***
Copyright (C) 2017 J. Patrick Hall, jphall@gwu.edu

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

***
# **Basic** Gradient Descent for Multiple Linear Regression

```python
# imports
import pandas as pd # import pandas for easy data manipulation using data frames
import numpy as np  # import numpy for numeric calculations on matrices
import time         # for timers

# import h2o to check calculations
import h2o
from h2o.estimators.glm import H2OGeneralizedLinearEstimator
```

#### Assign global constants

```python
# data-related constants
IN_FILE_PATH = '../data/loan_clean.csv'
Y            = 'STD_IMP_REP_loan_amnt'
DROPS        = ['id', 'GRP_REP_home_ownership', 'GRP_addr_state', 'GRP_home_ownership',
                'GRP_purpose', 'GRP_verification_status', '_WARN_']

# model-related constants
LEARN_RATE   = 0.05 # how much each gradient descent step impacts parameters
CONV         = 1e-10 # desired precision in parameters
MAX_ITERS    = 10000 # maximum number of gradient descent steps to allow
```

### Import clean data and convert to numpy matrices

```python
# import data using Pandas
raw = pd.read_csv(IN_FILE_PATH)
raw.describe()
```

      id
      bad_loan
      GRP_REP_home_ownership
      GRP_addr_state
      GRP_home_ownership
      GRP_purpose
      GRP_verification_status
      _WARN_
      STD_IMP_REP_annual_inc
      STD_IMP_REP_delinq_2yrs
      STD_IMP_REP_dti
      STD_IMP_REP_emp_length
      STD_IMP_REP_int_rate
      STD_IMP_REP_loan_amnt
      STD_IMP_REP_longest_credit_lengt
      STD_IMP_REP_revol_util
      STD_IMP_REP_term_length
      STD_IMP_REP_total_acc

      count
      163987.000000
      163987.000000
      163987.000000
      163987.000000
      163987.000000
      163987.000000
      163987.000000
      0.0
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05
      1.639870e+05

      mean
      91994.000000
      0.192595
      2.574003
      11.409337
      2.574003
      3.244940
      2.340356
      NaN
      2.387342e-11
      2.408736e-12
      6.806950e-11
      -3.563309e-11
      -8.939301e-12
      8.310596e-11
      5.061841e-11
      -1.473947e-11
      -1.500741e-10
      8.045720e-13

      std
      47339.113634
      0.394338
      0.667526
      9.971926
      0.667526
      2.267289
      0.504086
      NaN
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00
      1.000000e+00

      min
      10001.000000
      0.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      NaN
      -1.767456e+00
      -3.921962e-01
      -2.119639e+00
      -1.621390e+00
      -1.907046e+00
      -1.587129e+00
      -2.224451e+00
      -2.164541e+00
      -5.164956e-01
      -2.058862e+00

      25%
      50997.500000
      0.000000
      2.000000
      3.000000
      2.000000
      2.000000
      2.000000
      NaN
      -6.595203e-01
      -3.921962e-01
      -7.380602e-01
      -7.663281e-01
      -6.840838e-01
      -7.667612e-01
      -7.212383e-01
      -7.235035e-01
      -5.164956e-01
      -7.471426e-01

      50%
      91994.000000
      0.000000
      3.000000
      8.000000
      3.000000
      2.000000
      2.000000
      NaN
      -2.225562e-01
      -3.921962e-01
      -2.257573e-02
      8.873407e-02
      -5.191344e-02
      -2.114351e-01
      -1.199531e-01
      7.707309e-02
      -5.164956e-01
      -1.350069e-01

      75%
      132990.500000
      0.000000
      3.000000
      17.000000
      3.000000
      3.000000
      3.000000
      NaN
      3.686305e-01
      -3.921962e-01
      6.955785e-01
      1.228817e+00
      5.917510e-01
      6.215541e-01
      4.813321e-01
      7.815805e-01
      -5.164956e-01
      5.645768e-01

      max
      173987.000000
      1.000000
      5.000000
      37.000000
      5.000000
      14.000000
      3.000000
      NaN
      4.618062e+00
      4.156695e+00
      3.037149e+00
      1.228817e+00
      2.837680e+00
      2.767132e+00
      3.143160e+00
      3.036350e+00
      1.971879e+00
      3.068467e+00

```python
# select target column
y = raw[Y].as_matrix()
print(y)
```

    [-1.01918221 -1.33470843 -1.34732948 ... -0.03158515  1.83948532
      0.49534363]

```python
# create input matrix
# add an additional column of 1's for intercept
# by overlaying inputs onto matrix of 1's
numeric = raw.drop(DROPS + [Y], axis=1).as_matrix()
N, p = numeric.shape
X = np.ones(shape=(N, p + 1))
X[:,1:] = numeric
```

### Linear Regression by Solving the Normal Equation

\begin{equation}
X\hat{\beta} = y
\end{equation}

\begin{equation}
\hat{\beta} = (X^{T}X)^{-1}X^Ty
\end{equation}

The normal equation estimates coefficients that globally minimize the squared error between a rectangular input data matrix $X$ and a column vector of target values $y$. I.e. it presents an optimal solution to an underspecified set of linear equations.

```python
X_transpose = np.transpose(X)
beta_hat = np.linalg.inv(X_transpose.dot(X))
beta_hat = beta_hat.dot(X_transpose)
beta_hat = beta_hat.dot(y)
print('Model parameters:\n', beta_hat)
```

    Model parameters:
     [-0.00953184  0.04949174  0.4184344  -0.04237427  0.08966404  0.0301429
      0.08011731  0.05023822  0.01554239  0.31239211  0.04139277]

### Basic Gradient Descent Routines with L2 ("Ridge"/"Tikhonov") Regularization
\begin{equation}
\hat{\beta} = \underset{\beta}{argmin}\sum_{i=1}^{N} (y_i - \beta x_i)^2 + \lambda\sum_{j=0}^p \beta^2_j
\end{equation}

#### Define squared loss function
* For linear regression, we directly minimize the squared distance between the regression plane and points in the conditional distribution of **y** given **X**.
* Direct minimization of the squared loss function is typically preferred to solving the normal equation directly due to numerical stability and scalability issues.
* It is convenient to use a scaled mean squared error (MSE) formula:
\begin{equation}
\frac{1}{2N}\sum_{i=1}^{N} (y_i - \beta x_i)^2
\end{equation}

```python
def squared_loss(n, x, y, betas, lambda_=0):

    """ Squared loss function for multiple linear regression.

    :param n: Number of rows in x.
    :param x: Matrix of numeric inputs.
    :param y: Vector of known target values.
    :param beta: Vector of current model parameters.
    :param lambda_: Scale factor for L2 regularization, default 0.
    :return: Scalar MSE value.

    """

    yhat = x.dot(betas)

    return (1 / (2 * n)) * (((y - yhat)**2).sum() + lambda_ * (betas**2).sum())
```

#### Define gradient of loss function
* The derivative of the loss function w.r.t the model parameters is used to update model parameters at each gradient descent step.
* The gradient of our MSE loss function is trivial:

```python
def grad(n, y, yhat, x, beta_j, lambda_=0):

    """ Analytical gradient of scaled MSE loss function with L2 regularization.

    :param n: Number of rows in X.
    :param y: Vector of known target values.
    :param yhat: Vector of predicted target values.
    :param x: Vector of input values.
    :param beta_j: Model parameter for which to calculate gradient.
    :param lambda_: Scale factor for L2 regularization, default 0.
    :return: Vector of gradient values.

    """

    return (1 / n) * (x * (yhat - y) + (lambda_ * beta_j))
```

#### Define function for executing gradient descent minimization
For each gradient descent step:
* Predictions are made using the current model parameters.
* The gradient is calculated for each model pararmeter.
* The gradient is used in combination with the learning rate to update each parameter.

```python
def grad_descent(X, y, learn_rate, max_iters, sgd_mini_batch_n=0, lambda_=0):

    """ Routine for executing simple gradient descent with stochastic gradient descent option.

    :param X: Matrix of numeric data.
    :param y: Vector of known target values.
    :param learn_rate: Learning rate.
    :param max_iters: Maximum number of gradient descent steps to perform.
    :param sgd_mini_batch_n: Minibatch size for sgd optimization.
                             If > 0 minibatch stochastic gradient descent is performed.
    :param lambda_: Scale factor for L2 regularization, default 0.

    """

    tic = time.time()               # start timer
    n_betas = X.shape[1]            # number of model parameters including bias
    betas = np.zeros(shape=n_betas) # parameters start with value of 0
    n = y.shape[0]                  # number of rows in X

    # Pandas dataframe for iteration history
    iteration_frame = pd.DataFrame(columns=['Iteration', 'Loss'])

    print('Iteration history:')

    # loop for gradient descent steps
    for i in range(max_iters):

        # stochastic gradient descent
        if sgd_mini_batch_n > 0:

            samp_idx = np.random.randint(n, size=sgd_mini_batch_n)
            X_samp = X[samp_idx, :]
            y_samp = y[samp_idx]
            n_samp = X_samp.shape[0]
            yhat_samp = X_samp.dot(betas) # model predictions for iteration

            # loop for column-wise parameter updates
            for j in range(n_betas):

                # select column
                # calculate column-wise gradient
                # update corresponding parameter based on negative gradient
                # calculate loss

                xj_samp = X_samp[:, j]
                beta_j = betas[j]
                xj_grad_samp = grad(n_samp, y_samp, yhat_samp, xj_samp, beta_j, lambda_)
                betas[j] = betas[j] - learn_rate * xj_grad_samp.sum()
                iter_loss = squared_loss(n_samp, X_samp, y_samp, betas, lambda_)

        # standard gradient descent
        else:

            yhat = X.dot(betas) # model predictions for iteration

            # loop for column-wise parameter updates
            for j in range(n_betas):
                xj = X[:, j]
                beta_j = betas[j]
                xj_grad = grad(n, y, yhat, xj, beta_j, lambda_)
                betas[j] = betas[j] - learn_rate * xj_grad.sum()
                iter_loss = squared_loss(n, X, y, betas, lambda_)

        # update loss history
        iteration_frame = iteration_frame.append({'Iteration': i,
                                                  'Loss':  iter_loss},
                                                  ignore_index=True)
        # progress indicator
        if i % 1000 == 0:
            print('iter=%d loss=%.6f' % (i, iter_loss))

        # convergence check
        if i > 0:
            if np.abs(iteration_frame.iat[i-1, 1] - iteration_frame.iat[i, 1]) < CONV:
                break

    # output
    %matplotlib inline
    iteration_frame.plot.line(title='Iteration Plot', x='Iteration', y='Loss')
    print()
    print('Model parameters at iteration ' + str(i) + ':')
    print(betas)
    print()
    print('Model trained in %.2f s.' % (time.time()-tic))

```

#### Execute gradient descent

```python
grad_descent(X, y, LEARN_RATE, MAX_ITERS)
```

#### Execute L2 penalized gradient descent

```python
grad_descent(X, y, LEARN_RATE, MAX_ITERS, lambda_=0.01)
```

#### Execute stochastic gradient descent

```python
grad_descent(X, y, LEARN_RATE, MAX_ITERS, sgd_mini_batch_n=1000)
```

#### Execute L2 penalized stochastic gradient descent

```python
grad_descent(X, y, LEARN_RATE, MAX_ITERS, lambda_=0.01, sgd_mini_batch_n=1000)
```

### Use h2o to check model parameters

```python
# start h2o
h2o.init()

DROPS = ['id', 'GRP_REP_home_ownership', 'GRP_addr_state', 'GRP_home_ownership',
         'GRP_purpose', 'GRP_verification_status', '_WARN_']

# numeric columns
train = h2o.import_file(IN_FILE_PATH)
train = train.drop(DROPS)
X = train.col_names

# initialize non-penalized GLM model
loan_glm = H2OGeneralizedLinearEstimator(family='gaussian',      # uses squared error
                                         solver='IRLSM',         # necessary for non-penalized GLM
                                         standardize=False,      # data is already standardized
                                         compute_p_values=True,  # necessary for non-penalized GLM
                                         lambda_=0)              # necessary for non-penalized GLM

# train
loan_glm.train(train.col_names, Y, training_frame=train)

# print trained model info
print()
print('Model parameters:')
for name, val in loan_glm.coef().items():
    print(name, val)
print()

# shutdown h2o
h2o.cluster().shutdown()
```

    Checking whether there is an H2O instance running at http://localhost:54321..... not found.
    Attempting to start a local H2O server...
      Java Version: java version "1.8.0_171"; Java(TM) SE Runtime Environment (build 1.8.0_171-b11); Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
      Starting server from /home/patrickh/anaconda3/lib/python3.6/site-packages/h2o/backend/bin/h2o.jar
      Ice root: /tmp/tmpfk6et3so
      JVM stdout: /tmp/tmpfk6et3so/h2o_patrickh_started_from_python.out
      JVM stderr: /tmp/tmpfk6et3so/h2o_patrickh_started_from_python.err
      Server is running at http://127.0.0.1:54321
    Connecting to H2O server at http://127.0.0.1:54321... successful.
    Warning: Your H2O cluster version is too old (3 months and 14 days)! Please download and install the latest version from http://h2o.ai/download/

H2O cluster uptime:
01 secs

H2O cluster timezone:
America/New_York

H2O data parsing timezone:
UTC

H2O cluster version:
3.18.0.2

H2O cluster version age:
3 months and 14 days !!!

H2O cluster name:
H2O_from_python_patrickh_2x3z4e

H2O cluster total nodes:
1

H2O cluster free memory:
3.422 Gb

H2O cluster total cores:
8

H2O cluster allowed cores:
8

H2O cluster status:
accepting new members, healthy

H2O connection url:
http://127.0.0.1:54321

H2O connection proxy:
None

H2O internal security:
False

H2O API Extensions:
XGBoost, Algos, AutoML, Core V3, Core V4

Python version:
3.6.4 final

    Parse progress: |█████████████████████████████████████████████████████████| 100%
    glm Model Build progress: |███████████████████████████████████████████████| 100%

    Model parameters:
    Intercept -0.009531837795372164
    bad_loan 0.049491736160435965
    STD_IMP_REP_annual_inc 0.41843440383338865
    STD_IMP_REP_delinq_2yrs -0.04237426505134888
    STD_IMP_REP_dti 0.08966404330351394
    STD_IMP_REP_emp_length 0.030142903020478125
    STD_IMP_REP_int_rate 0.08011731349769513
    STD_IMP_REP_longest_credit_lengt 0.05023821933458795
    STD_IMP_REP_revol_util 0.015542387042639218
    STD_IMP_REP_term_length 0.31239210762608416
    STD_IMP_REP_total_acc 0.04139276773790677

    H2O session _sid_84a9 closed.


Trusted Notebook" width="500 px" align="left">

# Getting Started with Qiskit

Here, we provide an overview of working with Qiskit. Qiskit provides the basic building blocks necessary to program quantum computers. The basic concept of Qiskit is an array of quantum circuits. A workflow using Qiskit consists of two stages: **Build** and **Execute**. **Build** allows you to make different quantum circuits that represent the problem you are solving, and **Execute** allows you to run them on different backends.  After the jobs have been run, the data is collected. There are methods for putting this data together, depending on the program. This either gives you the answer you wanted, or allows you to make a better program for the next instance.

**Contents**

[Circuit basics](#circuit_basics)

[Simulating circuits with Qiskit Aer](#aer_simulation)

[Running circuits using the IBMQ provider](#ibmq_provider)

**Code imports**

```python
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute
```

## Circuit Basics

### Building the circuit

The basic elements needed for your first program are the QuantumCircuit, and QuantumRegister.

```python
# Create a Quantum Register with 3 qubits.
q = QuantumRegister(3, 'q')

# Create a Quantum Circuit acting on the q register
circ = QuantumCircuit(q)
```

Note: Naming the QuantumRegister is optional and not required.

After you create the circuit with its registers, you can add gates ("operations") to manipulate the registers. As you proceed through the documentation you will find more gates and circuits; the below is an example of a quantum circuit that makes a three-qubit GHZ state

$$|\psi\rangle = \left(|000\rangle+|111\rangle\right)/\sqrt{2}.$$

To create such a state, we start with a 3-qubit quantum register. By default, each qubit in the register is initialized to $|0\rangle$. To make the GHZ state, we apply the following gates:
* A Hadamard gate $H$ on qubit 0, which puts it into a superposition state.
* A controlled-Not operation ($C_{X}$) between qubit 0 and qubit 1.
* A controlled-Not operation between qubit 0 and qubit 2.

On an ideal quantum computer, the state produced by running this circuit would be the GHZ state above.

In Qiskit, operations can be added to the circuit one-by-one, as shown below.

```python
# Add a H gate on qubit 0, putting this qubit in superposition.
circ.h(q[0])
# Add a CX (CNOT) gate on control qubit 0 and target qubit 1, putting
# the qubits in a Bell state.
circ.cx(q[0], q[1])
# Add a CX (CNOT) gate on control qubit 0 and target qubit 2, putting
# the qubits in a GHZ state.
circ.cx(q[0], q[2])
```

    <qiskit.extensions.standard.cx.CnotGate at 0x11c0f2be0>

## Visualize Circuit

You can visualize your circuit using Qiskit `QuantumCircuit.draw()`, which plots circuit in the form found in many textbooks.

```python
circ.draw()
```

        ┌───┐
q_0: |0>┤ H ├──■────■──
        └───┘┌─┴─┐  │
q_1: |0>─────┤ X ├──┼──
             └───┘┌─┴─┐
q_2: |0>──────────┤ X ├
                  └───┘

In this circuit, the qubits are put in order with qubit zero at the top and qubit two at the bottom. The circuit is read left-to-right (meaning that gates which are applied earlier in the circuit show up further to the left).

## Simulating circuits using Qiskit Aer

Qiskit Aer is our package for simulating quantum circuits. It provides many different backends for doing a simulation. Here we use the basic python version.

### Statevector backend

The most common backend in Qiskit Aer is the `statevector_simulator`. This simulator returns the quantum
state which is a complex vector of dimensions $2^n$ where $n$ is the number of qubits
(so be careful using this as it will quickly get too large to run on your machine).

When representing the state of a multi-qubit system, the tensor order used in qiskit is different than that use in most physics textbooks. Suppose there are $n$ qubits, and qubit $j$ is labeled as $Q_{j}$. In most textbooks (such as Nielsen and Chuang's "Quantum Computation and Information"), the basis vectors for the $n$-qubit state space would be labeled as $Q_{0}\otimes Q_{1} \otimes \cdots \otimes Q_{n}$. **This is not the ordering used by qiskit!** Instead, qiskit uses an ordering in which the $n^{\mathrm{th}}$ qubit is on the _left_ side of the tesnsor product, so that the basis vectors are labeled as  $Q_n\otimes \cdots  \otimes  Q_1\otimes Q_0$.

For example, if qubit zero is in state 0, qubit 1 is in state 0, and qubit 2 is in state 1, qiskit would represent this state as $|100\rangle$, whereas most physics textbooks would represent it as $|001\rangle$.

This difference in labeling affects the way multi-qubit operations are represented as matrices. For example, qiskit represents a controlled-X ($C_{X}$) operation with qubit 0 being the control and qubit 1 being the target as

$$C_X = \begin{pmatrix} 1 & 0 & 0 & 0 \\  0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\\end{pmatrix}.$$

To run the above circuit using the statevector simulator, first you need to import Aer and then set the backend to `statevector_simulator`.

```python
# Import Aer
from qiskit import BasicAer

# Run the quantum circuit on a statevector simulator backend
backend = BasicAer.get_backend('statevector_simulator')
```

Now we have chosen the backend it's time to compile and run the quantum circuit. In Qiskit we provide the `execute` function for this. ``execute`` returns a ``job`` object that encapsulates information about the job submitted to the backend.

Tip: You can obtain the above parameters in Jupyter. Simply place the text cursor on a function and press Shift+Tab.

```python
# Create a Quantum Program for execution
job = execute(circ, backend)
```

When you run a program, a job object is made that has the following two useful methods:
`job.status()` and `job.result()` which return the status of the job and a result object respectively.

Note: Jobs run asynchronously but when the result method is called it switches to synchronous and waits for it to finish before moving on to another task.

```python
result = job.result()
```

The results object contains the data and Qiskit provides the method
`result.get_statevector(circ)` to return the state vector for the quantum circuit.

```python
outputstate = result.get_statevector(circ, decimals=3)
print(outputstate)
```

    [0.707+0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j 0.   +0.j
     0.707+0.j]

Qiskit also provides a visualization toolbox to allow you to view these results.

Below, we use the visualization function to plot the real and imaginary components of the state vector.

```python
from qiskit.tools.visualization import plot_state_city
plot_state_city(outputstate)
```

### Unitary backend

Qiskit Aer also includes a `unitary_simulator` that works _provided all the elements in the circuit are unitary operations_. This backend calculates the $2^n \times 2^n$ matrix representing the gates in the quantum circuit.

```python
# Run the quantum circuit on a unitary simulator backend
backend = BasicAer.get_backend('unitary_simulator')
job = execute(circ, backend)
result = job.result()

# Show the results
print(result.get_unitary(circ, decimals=3))
```

    [[ 0.707+0.j  0.707+0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
       0.   +0.j  0.   +0.j]
     [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
       0.707+0.j -0.707+0.j]
     [ 0.   +0.j  0.   +0.j  0.707+0.j  0.707+0.j  0.   +0.j  0.   +0.j
       0.   +0.j  0.   +0.j]
     [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.707+0.j -0.707+0.j
       0.   +0.j  0.   +0.j]
     [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.707+0.j  0.707+0.j
       0.   +0.j  0.   +0.j]
     [ 0.   +0.j  0.   +0.j  0.707+0.j -0.707+0.j  0.   +0.j  0.   +0.j
       0.   +0.j  0.   +0.j]
     [ 0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
       0.707+0.j  0.707+0.j]
     [ 0.707+0.j -0.707+0.j  0.   +0.j  0.   +0.j  0.   +0.j  0.   +0.j
       0.   +0.j  0.   +0.j]]

### OpenQASM backend

The simulators above are useful because they provide information about the state output by the ideal circuit and the matrix representation of the circuit. However, a real experiment terminates by _measuring_ each qubit (usually in the computational $|0\rangle, |1\rangle$ basis). Without measurement, we cannot gain information about the state. Measurements cause the quantum system to collapse into classical bits.

For example, suppose we make independent measurements on each qubit of the three-qubit GHZ state
$$|\psi\rangle = |000\rangle +|111\rangle)/\sqrt{2},$$
and let $xyz$ denote the bitstring that results. Recall that, under the qubit labeling used by Qiskit, $x$ would correspond to the outcome on qubit 2, $y$ to the outcome on qubit 1, and $z$ to the outcome on qubit 0. This representation of the bitstring puts the most significant bit (MSB) on the left, and the least significant bit (LSB) on the right. This is the standard ordering of binary bitstrings. We order the qubits in the same way, which is why Qiskit uses a non-standard tensor product order.

The probability of obtaining outcome $xyz$ is given by
$$\mathrm{Pr}(xyz) = |\langle xyz | \psi \rangle |^{2}.$$
By explicit computation, we see there are only two bitstrings that will occur: $000$ and $111$. If the bitstring $000$ is obtained, the state of the qubits is $|000\rangle$, and if the bitstring is $111$, the qubits are left in the state $|111\rangle$. The probability of obtaining 000 or 111 is the same; namely, 1/2:
$$\begin{align}
\mathrm{Pr}(000) &= |\langle 000 | \psi \rangle |^{2} = \frac{1}{2}\\
\mathrm{Pr}(111) &= |\langle 111 | \psi \rangle |^{2} = \frac{1}{2}.
\end{align}$$

To simulate a circuit that includes measurement, we need to add measurements to the original circuit above, and use a different Aer backend.

```python
# Create a Classical Register with 3 bits.
c = ClassicalRegister(3, 'c')
# Create a Quantum Circuit
meas = QuantumCircuit(q, c)
meas.barrier(q)
# map the quantum measurement to the classical bits
meas.measure(q,c)

# The Qiskit circuit object supports composition using
# the addition operator.
qc = circ+meas

#drawing the circuit
qc.draw()
```

        ┌───┐           ░ ┌─┐
q_0: |0>┤ H ├──■────■───░─┤M├──────
        └───┘┌─┴─┐  │   ░ └╥┘┌─┐
q_1: |0>─────┤ X ├──┼───░──╫─┤M├───
             └───┘┌─┴─┐ ░  ║ └╥┘┌─┐
q_2: |0>──────────┤ X ├─░──╫──╫─┤M├
                  └───┘ ░  ║  ║ └╥┘
 c_0: 0 ═══════════════════╩══╬══╬═
                              ║  ║
 c_1: 0 ══════════════════════╩══╬═
                                 ║
 c_2: 0 ═════════════════════════╩═

This circuit adds a classical register, and three measurements that are used to map the outcome of qubits to the classical bits.

To simulate this circuit, we use the ``qasm_simulator`` in Qiskit Aer. Each run of this circuit will yield either the bitstring 000 or 111. To build up statistics about the distribution of the bitstrings (to, e.g., estimate $\mathrm{Pr}(000)$), we need to repeat the circuit many times. The number of times the circuit is repeated can be specified in the ``execute`` function, via the ``shots`` keyword.

```python
# Use Aer's qasm_simulator
backend_sim = BasicAer.get_backend('qasm_simulator')

# Execute the circuit on the qasm simulator.
# We've set the number of repeats of the circuit
# to be 1024, which is the default.
job_sim = execute(qc, backend_sim, shots=1024)

# Grab the results from the job.
result_sim = job_sim.result()
```

Once you have a result object, you can access the counts via the function `get_counts(circuit)`. This gives you the _aggregated_ binary outcomes of the circuit you submitted.

```python
counts = result_sim.get_counts(qc)
print(counts)
```

    {'000': 526, '111': 498}

Approximately 50 percent of the time the output bitstring is 000. Qiskit also provides a function `plot_histogram` which allows you to view the outcomes.

```python
from qiskit.tools.visualization import plot_histogram
plot_histogram(counts)
```

The estimated outcome probabilities $\mathrm{Pr}(000)$ and  $\mathrm{Pr}(111)$ are computed by taking the aggregate counts and dividing by the number of shots (times the circuit was repeated). Try changing the ``shots`` keyword in the ``execute`` function and see how the estimated probabilities change.

## Running circuits using the IBMQ provider

To faciliate access to real quantum computing hardware, we have provided a simple API interface.
To access IBMQ devices, you'll need an API token. For the public IBM Q devices, you can generate an API token [here](https://quantumexperience.ng.bluemix.net/qx/account/advanced) (create an account if you don't already have one). For Q Network devices, login to the q-console, click your hub, group, and project, and expand "Get Access" to generate your API token and access url.

Our IBMQ provider lets you run your circuit on real devices or on our HPC simulator. Currently, this provider exists within Qiskit, and can be imported as shown below. For details on the provider, see [The IBMQ Provider](the_ibmq_provider.ipynb).

```python
from qiskit import IBMQ
```

After generating your API token, call, `IBMQ.save_account('MY_TOKEN')`. For Q Network users, you'll also need to include your access url: `IBMQ.save_account('MY_TOKEN', 'URL')`

This will store your IBMQ credentials in a local file.  Unless your registration information has changed, you only need to do this once.  You may now load your accounts by calling,

```python
IBMQ.load_accounts()
```

Once your account has been loaded, you can view the list of backends available to you.

```python
print("Available backends:")
IBMQ.backends()
```

    Available backends:

    [<IBMQBackend('ibmqx4') from IBMQ()>,
     <IBMQBackend('ibmq_16_melbourne') from IBMQ()>,
     <IBMQBackend('ibmq_qasm_simulator') from IBMQ()>,
     <IBMQBackend('ibmq_20_tokyo') from IBMQ(ibm-q-internal, research, yorktown)>,
     <IBMQBackend('ibmq_qasm_simulator') from IBMQ(ibm-q-internal, research, yorktown)>]

### Running circuits on real devices

Today's quantum information processors are small and noisy, but are advancing at a fast pace. They provide a great opportunity to explore what [noisy, intermediate-scale quantum (NISQ)](https://arxiv.org/abs/1801.00862) computers can do.

The IBMQ provider uses a queue to allocate the devices to users. We now choose a device with the least busy queue which can support our program (has at least 3 qubits).

```python
from qiskit.providers.ibmq import least_busy

large_enough_devices = IBMQ.backends(filters=lambda x: x.configuration().n_qubits > 4 and
                                                       not x.configuration().simulator)
backend = least_busy(large_enough_devices)
print("The best backend is " + backend.name())
```

    The best backend is ibmqx4

To run the circuit on the backend, we need to specify the number of shots and the number of credits we are willing to spend to run the circuit. Then, we execute the circuit on the backend using the ``execute`` function.

```python
from qiskit.tools.monitor import job_monitor
shots = 1024           # Number of shots to run the program (experiment); maximum is 8192 shots.
max_credits = 3        # Maximum number of credits to spend on executions.

job_exp = execute(qc, backend=backend, shots=shots, max_credits=max_credits)
job_monitor(job_exp)
```

    HTML(value="
Job Status: job is being initialized
")

``job_exp`` has a ``.result()`` method that lets us get the results from running our circuit.

Note: When the .result() method is called, the code block will wait until the job has finished before releasing the cell.

```python
result_exp = job_exp.result()
```

Like before, the counts from the execution can be obtained using ```get_counts(qc)```

```python
counts_exp = result_exp.get_counts(qc)
plot_histogram([counts_exp,counts])
```

### Simulating circuits using a HPC simulator

The IBMQ provider also comes with a remote optimized simulator called ``ibmq_qasm_simulator``. This remote simulator is capable of simulating up to 32 qubits. It can be used the
same way as the remote real backends.

```python
backend = IBMQ.get_backend('ibmq_qasm_simulator', hub=None)
```

```python
shots = 1024           # Number of shots to run the program (experiment); maximum is 8192 shots.
max_credits = 3        # Maximum number of credits to spend on executions.

job_hpc = execute(qc, backend=backend, shots=shots, max_credits=max_credits)
```

```python
result_hpc = job_hpc.result()
```

```python
counts_hpc = result_hpc.get_counts(qc)
plot_histogram(counts_hpc)
```

### Retrieving a previously ran job

If your experiment takes longer to run then you have time to wait around, or if you simply want to retrieve old jobs back, the IBMQ backends allow you to do that.
First you would need to note your job's ID:

```python
jobID = job_exp.job_id()

print('JOB ID: {}'.format(jobID))
```

    JOB ID: 5c56667159faae0051bceb52

Given a job ID, that job object can be later reconstructed from the backend using retrieve_job:

```python
job_get=backend.retrieve_job(jobID)
```

and then the results can be obtained from the new job object.

```python
job_get.result().get_counts(qc)
```

    {'100': 33,
     '110': 47,
     '010': 21,
     '111': 346,
     '001': 21,
     '101': 112,
     '011': 32,
     '000': 412}

```python

```


##QCNN for electron-photon classification

```
!pip install tensorflow==2.3.1
```

    Collecting tensorflow==2.3.1
      Downloading https://files.pythonhosted.org/packages/eb/18/374af421dfbe74379a458e58ab40cf46b35c3206ce8e183e28c1c627494d/tensorflow-2.3.1-cp37-cp37m-manylinux2010_x86_64.whl (320.4MB)
         |████████████████████████████████| 320.4MB 55kB/s
    Requirement already satisfied: h5py<2.11.0,>=2.10.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (2.10.0)
    Requirement already satisfied: gast==0.3.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.3.3)
    Requirement already satisfied: grpcio>=1.8.6 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.32.0)
    Requirement already satisfied: tensorboard<3,>=2.3.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (2.4.1)
    Requirement already satisfied: google-pasta>=0.1.8 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.2.0)
    Collecting tensorflow-estimator<2.4.0,>=2.3.0
      Downloading https://files.pythonhosted.org/packages/e9/ed/5853ec0ae380cba4588eab1524e18ece1583b65f7ae0e97321f5ff9dfd60/tensorflow_estimator-2.3.0-py2.py3-none-any.whl (459kB)
         |████████████████████████████████| 460kB 45.4MB/s
    Requirement already satisfied: protobuf>=3.9.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (3.12.4)
    Requirement already satisfied: wrapt>=1.11.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.12.1)
    Collecting numpy<1.19.0,>=1.16.0
      Downloading https://files.pythonhosted.org/packages/d6/c6/58e517e8b1fb192725cfa23c01c2e60e4e6699314ee9684a1c5f5c9b27e1/numpy-1.18.5-cp37-cp37m-manylinux1_x86_64.whl (20.1MB)
         |████████████████████████████████| 20.1MB 11.8MB/s
    Requirement already satisfied: wheel>=0.26 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.36.2)
    Requirement already satisfied: six>=1.12.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.15.0)
    Requirement already satisfied: absl-py>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (0.12.0)
    Requirement already satisfied: keras-preprocessing<1.2,>=1.1.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.1.2)
    Requirement already satisfied: astunparse==1.6.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.6.3)
    Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (3.3.0)
    Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow==2.3.1) (1.1.0)
    Requirement already satisfied: google-auth-oauthlib<0.5,>=0.4.1 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.4.3)
    Requirement already satisfied: tensorboard-plugin-wit>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.8.0)
    Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.3.4)
    Requirement already satisfied: requests<3,>=2.21.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2.23.0)
    Requirement already satisfied: google-auth<2,>=1.6.3 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.28.0)
    Requirement already satisfied: setuptools>=41.0.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (54.2.0)
    Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.7/dist-packages (from tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.0.1)
    Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from google-auth-oauthlib<0.5,>=0.4.1->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.3.0)
    Requirement already satisfied: importlib-metadata; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.8.1)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (1.24.3)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2.10)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (2020.12.5)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.0.4)
    Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (4.2.1)
    Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.2.8)
    Requirement already satisfied: rsa<5,>=3.1.4; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (4.7.2)
    Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<0.5,>=0.4.1->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.1.0)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata; python_version < "3.8"->markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.4.1)
    Requirement already satisfied: typing-extensions>=3.6.4; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from importlib-metadata; python_version < "3.8"->markdown>=2.6.8->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (3.7.4.3)
    Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth<2,>=1.6.3->tensorboard<3,>=2.3.0->tensorflow==2.3.1) (0.4.8)
    ERROR: datascience 0.10.6 has requirement folium==0.2.1, but you'll have folium 0.8.3 which is incompatible.
    ERROR: albumentations 0.1.12 has requirement imgaug<0.2.7,>=0.2.5, but you'll have imgaug 0.2.9 which is incompatible.
    Installing collected packages: tensorflow-estimator, numpy, tensorflow
      Found existing installation: tensorflow-estimator 2.4.0
        Uninstalling tensorflow-estimator-2.4.0:
          Successfully uninstalled tensorflow-estimator-2.4.0
      Found existing installation: numpy 1.19.5
        Uninstalling numpy-1.19.5:
          Successfully uninstalled numpy-1.19.5
      Found existing installation: tensorflow 2.4.1
        Uninstalling tensorflow-2.4.1:
          Successfully uninstalled tensorflow-2.4.1
    Successfully installed numpy-1.18.5 tensorflow-2.3.1 tensorflow-estimator-2.3.0

```
!pip install tensorflow-quantum
```

    Collecting tensorflow-quantum
      Downloading https://files.pythonhosted.org/packages/53/02/878b2d4e7711f5c7f8dff9ff838e8ed84d218a359154ce06c7c01178a125/tensorflow_quantum-0.4.0-cp37-cp37m-manylinux2010_x86_64.whl (5.9MB)
         |████████████████████████████████| 5.9MB 9.6MB/s
    Collecting sympy==1.5
      Downloading https://files.pythonhosted.org/packages/4d/a7/25d5d6b3295537ab90bdbcd21e464633fb4a0684dd9a065da404487625bb/sympy-1.5-py2.py3-none-any.whl (5.6MB)
         |████████████████████████████████| 5.6MB 22.9MB/s
    Collecting cirq==0.9.1
      Downloading https://files.pythonhosted.org/packages/18/05/39c24828744b91f658fd1e5d105a9d168da43698cfaec006179c7646c71c/cirq-0.9.1-py3-none-any.whl (1.6MB)
         |████████████████████████████████| 1.6MB 49.9MB/s
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy==1.5->tensorflow-quantum) (1.2.1)
    Requirement already satisfied: requests~=2.18 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.23.0)
    Requirement already satisfied: sortedcontainers~=2.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.3.0)
    Requirement already satisfied: google-api-core[grpc]<2.0.0dev,>=1.14.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.26.2)
    Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.7.4.3)
    Requirement already satisfied: numpy~=1.16 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.18.5)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.1.5)
    Requirement already satisfied: protobuf~=3.12.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.12.4)
    Requirement already satisfied: matplotlib~=3.0 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (3.2.2)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (1.4.1)
    Collecting freezegun~=0.3.15
      Downloading https://files.pythonhosted.org/packages/17/5d/1b9d6d3c7995fff473f35861d674e0113a5f0bd5a72fe0199c3f254665c7/freezegun-0.3.15-py2.py3-none-any.whl
    Requirement already satisfied: networkx~=2.4 in /usr/local/lib/python3.7/dist-packages (from cirq==0.9.1->tensorflow-quantum) (2.5)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (1.24.3)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests~=2.18->cirq==0.9.1->tensorflow-quantum) (2020.12.5)
    Requirement already satisfied: setuptools>=40.3.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (54.2.0)
    Requirement already satisfied: packaging>=14.3 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (20.9)
    Requirement already satisfied: googleapis-common-protos<2.0dev,>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.53.0)
    Requirement already satisfied: six>=1.13.0 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.15.0)
    Requirement already satisfied: google-auth<2.0dev,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.28.0)
    Requirement already satisfied: pytz in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (2018.9)
    Requirement already satisfied: grpcio<2.0dev,>=1.29.0; extra == "grpc" in /usr/local/lib/python3.7/dist-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (1.32.0)
    Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas->cirq==0.9.1->tensorflow-quantum) (2.8.1)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (1.3.1)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (0.10.0)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib~=3.0->cirq==0.9.1->tensorflow-quantum) (2.4.7)
    Requirement already satisfied: decorator>=4.3.0 in /usr/local/lib/python3.7/dist-packages (from networkx~=2.4->cirq==0.9.1->tensorflow-quantum) (4.4.2)
    Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (4.2.1)
    Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (0.2.8)
    Requirement already satisfied: rsa<5,>=3.1.4; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (4.7.2)
    Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth<2.0dev,>=1.21.1->google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq==0.9.1->tensorflow-quantum) (0.4.8)
    Installing collected packages: sympy, freezegun, cirq, tensorflow-quantum
      Found existing installation: sympy 1.7.1
        Uninstalling sympy-1.7.1:
          Successfully uninstalled sympy-1.7.1
    Successfully installed cirq-0.9.1 freezegun-0.3.15 sympy-1.5 tensorflow-quantum-0.4.0

Parts of the notebook are adapted from the examples by tfq

```
import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
```

```
#mounting google drive
from google.colab import drive
import numpy as np
drive.mount("/content/drive")

```

    Mounted at /content/drive

```
#load data from the drive
data = np.load('/content/drive/My Drive/Datasets/electron-photon.npz')
```

```
#inspect data
import matplotlib.pyplot as plt
train_data = data["x_train"]

photon = train_data[np.where(data["y_train"]==0)]
electron = train_data[np.where(data["y_train"]==1)]

```

```
import matplotlib.pyplot as plt

threshold = 0.0
x_train = data['x_train'][..., tf.newaxis]
y_train = data['y_train']
x_test = data['x_test'][..., tf.newaxis]/np.max(x_train)
y_test = data['y_test']
resize_shape = (100,4,4,1)
x_train_small = np.empty(resize_shape)
x_test_small = np.empty(resize_shape)

x_train_small_bin = np.empty(resize_shape)
x_test_small_bin = np.empty(resize_shape)

#reduce data dimension
for image in range(x_train.shape[0]):
  x_train_small[image] = tf.image.resize_with_crop_or_pad(x_train[image], 4,4).numpy()
for image in range(x_test.shape[0]):
  x_test_small[image] = tf.image.resize_with_crop_or_pad(x_test[image], 4,4).numpy()

x_train_small_scaled = tf.keras.utils.normalize(x_train_small, axis=-1, order=2)
x_test_small_scaled =  tf.keras.utils.normalize(x_test_small, axis=-1, order=2)                                                        #x_test_small/np.max(x_train_small)

#binary thresholding
for image in range(x_train.shape[0]):
  x_train_small_bin[image] = np.array(x_train_small[image] > threshold, dtype=np.float32)
for image in range(x_test.shape[0]):
  x_test_small_bin[image] = np.array(x_test_small[image] > threshold, dtype=np.float32)

```

```
plt.imshow(x_train_small[9,:,:,0])

print(x_train_small_bin[0,:,:,0])
```

The qubits at pixel indices with values that exceed the threshold of 0.0, are rotated through an X  gate.

```
def to_circuit(data_):
    """Encode truncated classical image into quantum datapoint."""
    val = np.ndarray.flatten(data_)
    qubits = cirq.GridQubit.rect(1, 16)
    circuit = cirq.Circuit()
    for i, value in enumerate(val):
        if value:
            circuit.append(cirq.X(qubits[i]))

    return circuit

x_train_circ = [to_circuit(x) for x in x_train_small_bin]
x_test_circ = [to_circuit(x) for x in x_test_small_bin]

x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)
x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)
```

```
print (x_train_tfcirc.shape)
print (y_train.shape)
```

    (100,)
    (100,)

```
SVGCircuit(x_test_circ[0])
```

QCNN layers

```
def one_qubit_unitary(bit, symbols):
    """Cirq circuit enacting a rotation of the bloch sphere about the X,
    Y and Z axis, that depends on the values in `symbols`.
    """
    return cirq.Circuit(
        cirq.X(bit)**symbols[0],
        cirq.Y(bit)**symbols[1],
        cirq.Z(bit)**symbols[2])

def two_qubit_unitary(bits, symbols):
    """Cirq circuit that creates an arbitrary two qubit unitary."""
    circuit = cirq.Circuit()
    circuit += one_qubit_unitary(bits[0], symbols[0:3])
    circuit += one_qubit_unitary(bits[1], symbols[3:6])
    circuit += [cirq.ZZ(*bits)**symbols[6]]
    circuit += [cirq.YY(*bits)**symbols[7]]
    circuit += [cirq.XX(*bits)**symbols[8]]
    circuit += one_qubit_unitary(bits[0], symbols[9:12])
    circuit += one_qubit_unitary(bits[1], symbols[12:])
    return circuit

def two_qubit_pool(source_qubit, sink_qubit, symbols):
    """Cirq circuit for parameterized 'pooling', that
    reduce entanglement down from two qubits to just one."""
    pool_circuit = cirq.Circuit()
    sink_basis_selector = one_qubit_unitary(sink_qubit, symbols[0:3])
    source_basis_selector = one_qubit_unitary(source_qubit, symbols[3:6])
    pool_circuit.append(sink_basis_selector)
    pool_circuit.append(source_basis_selector)
    pool_circuit.append(cirq.CNOT(control=source_qubit, target=sink_qubit))
    pool_circuit.append(sink_basis_selector**-1)
    return pool_circuit
```

```
def quantum_conv_circuit(bits, symbols):
    """Quantum Convolution Layer.
    Return a Cirq circuit with the cascade of `two_qubit_unitary` applied
    to all pairs of qubits in `bits`.
    """
    circuit = cirq.Circuit()
    for first, second in zip(bits[0::2], bits[1::2]):
        circuit += two_qubit_unitary([first, second], symbols)
    for first, second in zip(bits[1::2], bits[2::2] + [bits[0]]):
        circuit += two_qubit_unitary([first, second], symbols)
    return circuit
```

Quantum pooling

```
def quantum_pool_circuit(source_bits, sink_bits, symbols):
    """layer for a quantum pooling operation.
    """
    circuit = cirq.Circuit()
    for source, sink in zip(source_bits, sink_bits):
        circuit += two_qubit_pool(source, sink, symbols)
    return circuit
```

Model

```
def create_model_circuit(qubits):
    """sequence of alternating convolution and pooling operators
    """
    model_circuit = cirq.Circuit()
    symbols = sympy.symbols('qconv0:84')

    model_circuit += quantum_conv_circuit(qubits, symbols[0:15])
    model_circuit += quantum_pool_circuit(qubits[:8], qubits[8:],
                                          symbols[15:21])
    model_circuit += quantum_conv_circuit(qubits[8:], symbols[21:36])
    model_circuit += quantum_pool_circuit(qubits[8:12], qubits[12:],
                                          symbols[36:42])
    model_circuit += quantum_conv_circuit(qubits[12:], symbols[42:57])
    model_circuit += quantum_pool_circuit(qubits[12:14], qubits[14:],
                                          symbols[57:63])
    model_circuit += quantum_conv_circuit(qubits[14:], symbols[63:78])
    model_circuit += quantum_pool_circuit(qubits[14:15], qubits[15:],
                                          symbols[78:84])
    return model_circuit

# qubits and readout operators in Cirq.
cluster_state_bits = cirq.GridQubit.rect(1, 16)
readout_operators = cirq.Z(cluster_state_bits[-1])

input_ = tf.keras.Input(shape=(), dtype=tf.dtypes.string)

quantum_model = tfq.layers.PQC(create_model_circuit(cluster_state_bits),
                               readout_operators)(input_)

qcnn_model = tf.keras.Model(inputs=[input_], outputs=[quantum_model])

# visualize model
tf.keras.utils.plot_model(qcnn_model,
                          show_shapes=True,
                          show_layer_names=False,
                          dpi=70)
```

Model training

```
@tf.function
def custom_accuracy(y_true, y_pred):
    y_true = tf.squeeze(y_true)
    y_pred = tf.map_fn(lambda x: 1.0 if x >= 0.0 else 0.0, y_pred)
    return tf.keras.backend.mean(tf.keras.backend.equal(y_true, y_pred))

qcnn_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.01),
                   loss=tf.losses.mse,
                   metrics=[custom_accuracy])

history = qcnn_model.fit(x=x_train_tfcirc,
                         y=y_train,
                         batch_size=32,
                         epochs=15,
                         verbose=1,
                         validation_data=(x_test_tfcirc, y_test))
```

```
#Validation accuracy from the last epoch
print (max(qcnn_model.history.history['val_custom_accuracy']))
```

    0.578125

```
plt.plot(history.history['loss'][1:], label='Training')
plt.plot(history.history['val_loss'][1:], label='Validation')
plt.title('Quantum CNN loss plot for electron-photon classification')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
```

Hybrid quantum-classical networks

```

def multi_readout_model_circuit(qubits):
    model_circuit = cirq.Circuit()
    symbols = sympy.symbols('qconv0:21')
    model_circuit += quantum_conv_circuit(qubits, symbols[0:15])
    model_circuit += quantum_pool_circuit(qubits[:8], qubits[8:],
                                          symbols[15:21])
    return model_circuit

cluster_state_bits = cirq.GridQubit.rect(1, 16)
readout_operators = [cirq.Z(bit) for bit in cluster_state_bits[8:]]

input_hybrid = tf.keras.Input(shape=(), dtype=tf.dtypes.string)

#a single quantum convolution layer

quantum_model_hybrid = tfq.layers.PQC(
    multi_readout_model_circuit(cluster_state_bits),
    readout_operators)(input_hybrid)

# classical dense layer 1
d1_ = tf.keras.layers.Dense(4)(quantum_model_hybrid)

# classical dense layer 2

d3_ = tf.keras.layers.Dense(1)(d1_)

hybrid_model = tf.keras.Model(inputs=[input_hybrid], outputs=[d3_])

# model structure
tf.keras.utils.plot_model(hybrid_model,
                          show_shapes=True,
                          show_layer_names=False,
                          dpi=70)
```

```
@tf.function
def custom_accuracy(y_true, y_pred):
    y_true = tf.squeeze(y_true)
    y_pred = tf.map_fn(lambda x: 1.0 if x >= 0.0 else 0.0, y_pred)
    return tf.keras.backend.mean(tf.keras.backend.equal(y_true, y_pred))

hybrid_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.01),
                   loss=tf.losses.mse,
                   metrics=[custom_accuracy])

history = hybrid_model.fit(x=x_train_tfcirc,
                         y=y_train,
                         batch_size=32,
                         epochs=30,
                         verbose=1,
                         validation_data=(x_test_tfcirc, y_test))
```

```
plt.plot(history.history['loss'][1:], label='Training')
plt.plot(history.history['val_loss'][1:], label='Validation')
plt.title('Hybrid Quantum-classical CNN loss plot for electron-photon classification')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
```