# Project Notebook

### "Supervised classification based on expected values of density matrices in a quantum computer"

### Qiskit Fall Fest Hackathon

---

**Group: Quantum Mind UN**

Diego Useche - diusecher@unal.edu.co

Oscar Alberto Bustos B. - oabustosb@unal.edu.co

This project aims to construct shallow quanum circuits that can be used to perform the classification of images of handwritten digits. To do this, two approximations are explored: first, designing and running quantum circuits whose final measurements allow to build predictions; and second, the use of density matrices to determine, using Born's rule, the expected value of probability for each possible outcome. Our work is mainly based on *Fabio  A.  González,  Alejandro  Gallego,  Santiago  Toledo-Cortés, and Vladimir Vargas-Calderón. Learning with Density Matrices and Random Features. 2021*.

**Main Contributions**

*   Most quantum machine learning algorithms are based on pure states. Our algorithm is based on mixed states.
*   We propose a quantum protocol to estimate the expected value of a (2^nx2^n) density matrix with a state.
*   We implement the prediction phase of the supervised learning method Density Matrix Kernel Density Classification (DMKDC) [González2021].
*   We apply the method to the classification
of handwritten digit images (0 and 1).

## Libraries

```python
!pip install qiskit
!pip install pylatexenc
```

    Collecting qiskit
      Downloading qiskit-0.31.0.tar.gz (13 kB)
    Collecting qiskit-terra==0.18.3
      Downloading qiskit_terra-0.18.3-cp37-cp37m-manylinux2010_x86_64.whl (6.1 MB)
         |████████████████████████████████| 6.1 MB 5.8 MB/s
    Collecting qiskit-aer==0.9.1
      Downloading qiskit_aer-0.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (17.9 MB)
         |████████████████████████████████| 17.9 MB 116 kB/s
    Collecting qiskit-ibmq-provider==0.17.0
      Downloading qiskit_ibmq_provider-0.17.0-py3-none-any.whl (236 kB)
         |████████████████████████████████| 236 kB 83.4 MB/s
    Collecting qiskit-ignis==0.6.0
      Downloading qiskit_ignis-0.6.0-py3-none-any.whl (207 kB)
         |████████████████████████████████| 207 kB 29.6 MB/s
    Collecting qiskit-aqua==0.9.5
      Downloading qiskit_aqua-0.9.5-py3-none-any.whl (2.1 MB)
         |████████████████████████████████| 2.1 MB 33.4 MB/s
    Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.4.1)
    Requirement already satisfied: numpy>=1.16.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.19.5)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.7.1)
    Requirement already satisfied: fastdtw<=0.3.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.3.4)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.1.5)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (5.4.8)
    Collecting dlx<=1.0.4
      Downloading dlx-1.0.4.tar.gz (5.5 kB)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.22.2.post1)
    Collecting quandl
      Downloading Quandl-3.6.1-py2.py3-none-any.whl (26 kB)
    Collecting docplex>=2.21.207
      Downloading docplex-2.22.213.tar.gz (634 kB)
         |████████████████████████████████| 634 kB 87.8 MB/s
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (57.4.0)
    Collecting yfinance>=0.1.62
      Downloading yfinance-0.1.64.tar.gz (26 kB)
    Collecting retworkx>=0.8.0
      Downloading retworkx-0.10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.4 MB)
         |████████████████████████████████| 1.4 MB 52.4 MB/s
    Requirement already satisfied: h5py<3.3.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (3.1.0)
    Collecting websocket-client>=1.0.1
      Downloading websocket_client-1.2.1-py2.py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 2.1 MB/s
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.17.0->qiskit) (2.23.0)
    Collecting requests-ntlm>=1.1.0
      Downloading requests_ntlm-1.1.0-py2.py3-none-any.whl (5.7 kB)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.17.0->qiskit) (2.8.2)
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.17.0->qiskit) (1.24.3)
    Collecting ply>=3.10
      Downloading ply-3.11-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 7.9 MB/s
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (2.6.0)
    Collecting python-constraint>=1.4
      Downloading python-constraint-1.4.0.tar.bz2 (18 kB)
    Collecting tweedledum<2.0,>=1.1
      Downloading tweedledum-1.1.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (943 kB)
         |████████████████████████████████| 943 kB 88.5 MB/s
    Collecting symengine>0.7
      Downloading symengine-0.8.1-cp37-cp37m-manylinux2010_x86_64.whl (38.2 MB)
         |████████████████████████████████| 38.2 MB 20 kB/s
    Collecting fastjsonschema>=2.10
      Downloading fastjsonschema-2.15.1-py3-none-any.whl (21 kB)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (0.3.4)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from docplex>=2.21.207->qiskit-aqua==0.9.5->qiskit) (1.15.0)
    Requirement already satisfied: cached-property in /usr/local/lib/python3.7/dist-packages (from h5py<3.3.0->qiskit-aqua==0.9.5->qiskit) (1.5.2)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.17.0->qiskit) (2021.5.30)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.17.0->qiskit) (3.0.4)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.17.0->qiskit) (2.10)
    Collecting ntlm-auth>=1.0.2
      Downloading ntlm_auth-1.5.0-py2.py3-none-any.whl (29 kB)
    Collecting cryptography>=1.3
      Downloading cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl (3.5 MB)
         |████████████████████████████████| 3.5 MB 41.5 MB/s
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.17.0->qiskit) (1.14.6)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.17.0->qiskit) (2.20)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.5->qiskit) (1.0.1)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-aqua==0.9.5->qiskit) (1.2.1)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from yfinance>=0.1.62->qiskit-aqua==0.9.5->qiskit) (0.0.9)
    Collecting lxml>=4.5.1
      Downloading lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl (6.3 MB)
         |████████████████████████████████| 6.3 MB 20.6 MB/s
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->qiskit-aqua==0.9.5->qiskit) (2018.9)
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.5->qiskit) (8.10.0)
    Collecting inflection>=0.3.1
      Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
    Building wheels for collected packages: qiskit, dlx, docplex, python-constraint, yfinance
      Building wheel for qiskit (setup.py) ... done
      Created wheel for qiskit: filename=qiskit-0.31.0-py3-none-any.whl size=11728 sha256=a22fcccae17c9f47d1128c1d45444b3d6867cd1e876093fe68e77327ec29bd73
      Stored in directory: /root/.cache/pip/wheels/29/dd/b2/1fe1a9ac92aaf75b267d893ae27329ea229f292a293017afc7
      Building wheel for dlx (setup.py) ... done
      Created wheel for dlx: filename=dlx-1.0.4-py3-none-any.whl size=5719 sha256=e230a5abbcd18b21b64d377a27c3a2274645655cea0f1d86acbf94df468003cc
      Stored in directory: /root/.cache/pip/wheels/78/55/c8/dc61e772445a566b7608a476d151e9dcaf4e092b01b0c4bc3c
      Building wheel for docplex (setup.py) ... done
      Created wheel for docplex: filename=docplex-2.22.213-py3-none-any.whl size=696881 sha256=db6c2c34b4bd4e7db3d94b8fcc117e5410227edd1ea2517c5cc9b9d41938b339
      Stored in directory: /root/.cache/pip/wheels/90/69/6b/1375c68a5b7ff94c40263b151c86f58bd72200bf0c465b5ba3
      Building wheel for python-constraint (setup.py) ... done
      Created wheel for python-constraint: filename=python_constraint-1.4.0-py2.py3-none-any.whl size=24081 sha256=8d7466514f654cb69b4291ef58a3e6151deec83c81a41efda7a2f7c609931b84
      Stored in directory: /root/.cache/pip/wheels/07/27/db/1222c80eb1e431f3d2199c12569cb1cac60f562a451fe30479
      Building wheel for yfinance (setup.py) ... done
      Created wheel for yfinance: filename=yfinance-0.1.64-py2.py3-none-any.whl size=24109 sha256=c5b0b5e47539717464a2e36b495e9b70285dd9788110dffbdada863b874a5e5c
      Stored in directory: /root/.cache/pip/wheels/86/fe/9b/a4d3d78796b699e37065e5b6c27b75cff448ddb8b24943c288
    Successfully built qiskit dlx docplex python-constraint yfinance
    Installing collected packages: tweedledum, symengine, retworkx, python-constraint, ply, fastjsonschema, qiskit-terra, ntlm-auth, lxml, inflection, cryptography, yfinance, websocket-client, requests-ntlm, quandl, qiskit-ignis, docplex, dlx, qiskit-ibmq-provider, qiskit-aqua, qiskit-aer, qiskit
      Attempting uninstall: lxml
        Found existing installation: lxml 4.2.6
        Uninstalling lxml-4.2.6:
          Successfully uninstalled lxml-4.2.6
    Successfully installed cryptography-35.0.0 dlx-1.0.4 docplex-2.22.213 fastjsonschema-2.15.1 inflection-0.5.1 lxml-4.6.3 ntlm-auth-1.5.0 ply-3.11 python-constraint-1.4.0 qiskit-0.31.0 qiskit-aer-0.9.1 qiskit-aqua-0.9.5 qiskit-ibmq-provider-0.17.0 qiskit-ignis-0.6.0 qiskit-terra-0.18.3 quandl-3.6.1 requests-ntlm-1.1.0 retworkx-0.10.2 symengine-0.8.1 tweedledum-1.1.1 websocket-client-1.2.1 yfinance-0.1.64
    Collecting pylatexenc
      Downloading pylatexenc-2.10.tar.gz (162 kB)
         |████████████████████████████████| 162 kB 8.6 MB/s
    Building wheels for collected packages: pylatexenc
      Building wheel for pylatexenc (setup.py) ... done
      Created wheel for pylatexenc: filename=pylatexenc-2.10-py3-none-any.whl size=136833 sha256=8b3dbfa3efbdb9cd5b8dca2f51d8abfa126830941753d5ce5e61cbfa8e29047e
      Stored in directory: /root/.cache/pip/wheels/f1/8a/f5/33ee79d4473eb201b519fa40f989b842e373237395a3421f52
    Successfully built pylatexenc
    Installing collected packages: pylatexenc
    Successfully installed pylatexenc-2.10

```python
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from qiskit import QuantumCircuit, Aer, execute
from qiskit.visualization import plot_histogram
#from qiskit import *
import numpy as np
backend = Aer.get_backend('qasm_simulator')
```

## Utils Functions

```python
# Given the density matrix find the angle of the corresponding rotation
def measure_theta(P):
  w0, v0 = np.linalg.eig(P)
  v0 = v0*np.sign(v0[0, 0])
  theta = np.sign(v0[0, 0])*np.sign(v0[1, 0])*2*np.arccos(abs(v0[0, 0]))
  return theta

# measure_theta(P_1)
```

```python
# Given an rotation angle in the y axis, print its unitary matrix
def unitary_matrix(theta):
  qc = QuantumCircuit(1, 1)
  qc.ry(theta, 0)
  unitary_backend = Aer.get_backend('unitary_simulator')
  unitary = execute(qc, unitary_backend).result().get_unitary().round(10)
  return unitary

#unitary_matrix(-1.0013820370897397)
unitary_matrix(1.047184849024927)
```

    array([[ 0.86602858+0.j, -0.4999945 +0.j],
           [ 0.4999945 +0.j,  0.86602858+0.j]])

```python
import copy
def round_lambda(lambda_param_2):
  lambda_param = copy.copy(lambda_param_2)
  for i in range(4):
    if lambda_param[i] < 0.00000001:
      lambda_param[i] = 0.00000001
  lambda_param[3] = 1 - lambda_param[0] - lambda_param[1] - lambda_param[2]
  return lambda_param
```

## Initializations

Inicializador de dos qubits

```python
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
```

```python
def ArbRot(p0, p1, p2, p3):
    qr = QuantumRegister(2)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.rx(ang0, qr[1])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.rx(ang1, qr[0])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.crx(ang2, qr[1], qr[0])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbInit"
    return gate
```

```python
def ArbRotControlled(p0, p1, p2, p3):
    qr = QuantumRegister(3)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.crx(ang0, qr[0],  qr[2])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.crx(ang1, qr[0], qr[1])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(ang2, [qr[0], qr[2]], qr[1])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbInit"
    return gate
```

```python
def ArbRotControlledTranspose(p0, p1, p2, p3):
    qr = QuantumRegister(3)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.crx(-ang0, qr[0],  qr[2])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.crx(-ang1, qr[0], qr[1])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[2]], qr[1])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbInit"
    return gate
```

```python
def ArbRotToffTranspose(p0, p1, p2, p3):
    qr = QuantumRegister(4)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.mcrx(-ang0, [qr[0], qr[1]],  qr[3])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.mcrx(-ang1, [qr[0], qr[1]], qr[2])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[1], qr[3]], qr[2])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbInitCT"
    return gate
```

```python
def ArbRotToffM2Transpose(p0, p1, p2, p3):
    qr = QuantumRegister(5)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.mcrx(-ang0, [qr[0], qr[1], qr[2]],  qr[4])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.mcrx(-ang1, [qr[0], qr[1], qr[2]], qr[3])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[1], qr[2], qr[4]], qr[3])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbInitCT"
    return gate
```

## Expected Value Density Matrix 2x2

```python
a0 = np.array([0.30507657, 0.9523278250881023])
b0 = np.array([0.75399555, 0.6568795251643924])
c0 = np.array([0.50986323, 0.8602554775727772])
```

```python
P0 = 0.35*np.outer(a0, a0) + 0.65*np.outer(b0, b0)
exp_c = c0.T @ P0 @ c0

P0, exp_c
```

    (array([[0.40210614, 0.42362127],
            [0.42362127, 0.59789386]]), 0.9186080711795597)

```python
# Find eigenvectors and eigenvalues
lambda_P, U = np.linalg.eigh(P0)

lambda_P, U
```

    (array([0.06521478, 0.93478522]), array([[-0.78267315,  0.62243292],
            [ 0.62243292,  0.78267315]]))

```python
vector_0, vector_1 = U[:, 0], U[:, 1]

vector_0, vector_1, 0.5*np.dot(vector_0, a0)**2, 0.5*np.dot(vector_1, b0)**2
```

    (array([-0.78267315,  0.62243292]),
     array([0.62243292, 0.78267315]),
     0.06265267056613329,
     0.4835708408491834)

```python
0.5 * (np.dot(vector_0, c0)**2/(np.dot(vector_0, c0)**2+np.dot(vector_1, c0)**2))
```

    0.009301806757675855

```python
theta_0, theta_1 = measure_theta(U), measure_theta(U.T)
theta_0 = theta_0+np.pi
theta_1 = theta_0-np.pi/2
theta_0, theta_1
```

    (2.4697453073741347, 0.8989489805792381)

```python
qc = QuantumCircuit(1, 1)

qc.ry(2*theta_0, 0)

qc.measure(0, 0)

counts = execute(qc, backend, shots=15000).result().get_counts()
plot_histogram(counts)
```

```python
# 2 x 2 Example

qc = QuantumCircuit(2, 2)

qc.h(0)
qc.x(0)
qc.initialize(c0, 1)
qc.cry(-(2*theta_0), 0,  1)
qc.x(0)
qc.cry(-(2*theta_1), 0,  1)
qc.measure(0, 0)
qc.measure(1, 1)

counts = execute(qc, backend, shots=15000).result().get_counts()
plot_histogram(counts)

## Works
```

```python
qc.draw('mpl')
```

### Circuit Density Matrix 2x2

```python
# 2 x 2 Example with eigenvalues
backend = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(3, 2)

qc.h(0)
qc.initialize(c0, 1)
qc.initialize(np.sqrt(lambda_P), 2)
qc.barrier()
qc.x(0)
qc.cry(-(2*theta_0), 0,  1)
qc.x(0)
qc.cry(-(2*theta_1), 0,  1)
qc.cx(0, 2)
qc.barrier()
qc.measure(1, 0)
qc.measure(2, 1)

counts = execute(qc, backend, shots=150000).result().get_counts()
plot_histogram(counts)
```

```python
qc.draw('mpl')
```

```python
# expected_value = (np.dot(vector_0, c0))**2 * lambda_P[0] + (np.dot(vector_1, c0))**2 * lambda_P[1]
expected_value = c0.T @ P0 @ c0

expected_value, (counts["00"]/150000)*2
```

    (0.9186080711795597, 0.9202666666666667)

### Conclusion

The expected value of the density matrix was 0.9186, the quantum circuit obtained 0.9203

## Expected Value Density Matrix 4x4

```python
## Examples 4x4 , we build the density matrix with b, c, and predict the data a
a0 = np.array([0.30507657, 0.9523278250881023])
a1 = np.array([0.69810362, 0.715996742829809])
b0 = np.array([0.75399555, 0.6568795251643924])
b1 = np.array([0.53842219, 0.8426752312223279])
c0 = np.array([0.50986323, 0.8602554775727772])
c1 = np.array([0.58868076, 0.8083656120876385])

a = np.kron(a0, a1)
b = np.kron(b0, b1)
c = np.kron(c0, c1)

a, b, c
```

    (array([0.21297506, 0.21843383, 0.6648235 , 0.68186362]),
     array([0.40596794, 0.63537337, 0.35367851, 0.55353611]),
     array([0.30014667, 0.4121559 , 0.50641585, 0.69540095]))

```python
# Density Matrix and expected value
P4 = 0.56*np.outer(b, b) + 0.44*np.outer(c, c)
exp_a = a.T @ P4 @ a

P4, exp_a
```

    (array([[0.13193231, 0.19887826, 0.14728557, 0.21767983],
            [0.19887826, 0.30081552, 0.21767983, 0.32306316],
            [0.14728557, 0.21767983, 0.18289064, 0.26458465],
            [0.21767983, 0.32306316, 0.26458465, 0.38436153]]), 0.8026535900618249)

```python
# Find eigenvectors and eigenvalues
lambda_P4, U4 = np.linalg.eigh(P4)
lambda_P4 = round_lambda(lambda_P4)
lambda_P4, U4
```

    (array([1.00000000e-08, 1.00000000e-08, 2.57332957e-02, 9.74266684e-01]),
     array([[-0.86865914, -0.10127904,  0.32009869, -0.36429477],
            [ 0.45816754,  0.17746336,  0.67972932, -0.54457076],
            [-0.08734651,  0.76104064, -0.48123093, -0.42615082],
            [ 0.16698618, -0.61568373, -0.45157688, -0.62380084]]))

### Circuit Density Matrix 4x4

```python
# 4 x 4 Example with eigenvalues
backend = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(6, 4)

qc.h(0)
qc.h(1)
qc.append(ArbRot(*(a**2)), [2, 3])
qc.append(ArbRot(*lambda_P4), [4, 5])
qc.barrier()
qc.x(0)
qc.x(1)
qc.append(ArbRotToffTranspose(*(U4[:, 0]**2)), [0, 1, 2, 3])
qc.x(1)
qc.append(ArbRotToffTranspose(*(U4[:, 1]**2)), [0, 1, 2, 3])
qc.x(0)
qc.x(1)
qc.append(ArbRotToffTranspose(*(U4[:, 2]**2)), [0, 1, 2, 3])
qc.x(1)
qc.append(ArbRotToffTranspose(*(U4[:, 3]**2)), [0, 1, 2, 3])
qc.cx(1, 5)
qc.cx(0, 4)

qc.measure(2, 0)
qc.measure(3, 1)
qc.measure(4, 2)
qc.measure(5, 3)

counts = execute(qc, backend, shots=150000).result().get_counts()
plot_histogram(counts)
```

```python
qc.draw('mpl')
```

```python
exp_a_calculated = (counts["0000"]/150000)*4
exp_a_theorical = a.T @ P4 @ a

exp_a_calculated, exp_a_theorical
```

    (0.8113333333333334, 0.8026535900618249)

### Conclusion

The expected value of the density matrix was 0.8026, the quantum circuit obtained 0.8113

## DMKDC Mixed State

```python
## Examples 4x4 , we build the density matrix of class 0 with b, c
## the density matrix of class 1 with d, and predict the data a
a0 = np.array([0.30507657, 0.9523278250881023])
a1 = np.array([0.69810362, 0.715996742829809])
b0 = np.array([0.75399555, 0.6568795251643924])
b1 = np.array([0.53842219, 0.8426752312223279])
c0 = np.array([0.50986323, 0.8602554775727772])
c1 = np.array([0.58868076, 0.8083656120876385])
d0 = np.array([0.10894686, 0.9940475751673762])
d1 = np.array([0.38544095, 0.9227325040677268])

a = np.kron(a0, a1)
b = np.kron(b0, b1)
c = np.kron(c0, c1)
d = np.kron(d0, d1)

a, b, c, d
```

    (array([0.21297506, 0.21843383, 0.6648235 , 0.68186362]),
     array([0.40596794, 0.63537337, 0.35367851, 0.55353611]),
     array([0.30014667, 0.4121559 , 0.50641585, 0.69540095]),
     array([0.04199258, 0.10052881, 0.38314664, 0.91724001]))

```python
# Density Matrix and expected value
P0 = 0.66*np.outer(b, b) + 0.34*np.outer(c, c)
P1 = np.outer(d, d)
exp_a_P0 = a.T @ P0 @ a
exp_a_P1 = a.T @ P1 @ a

exp_a_P0, exp_a_P1
```

    (0.7797639127048509, 0.8300298691432838)

```python
# Find eigenvectors and eigenvalues
lambda_P0_temp, U0 = np.linalg.eigh(P0)
lambda_P0 = round_lambda(lambda_P0_temp)

lambda_P1_temp, U1 = np.linalg.eigh(P1)
lambda_P1 = round_lambda(lambda_P1_temp)

(lambda_P0, U0), (lambda_P1, U1)
```

    ((array([1.00000000e-08, 1.00000000e-08, 2.33791892e-02, 9.76620791e-01]),
      array([[-0.87216761, -0.06441905,  0.30773227, -0.37479955],
             [ 0.4652687 ,  0.15791066,  0.66113058, -0.56700583],
             [-0.05505435,  0.76405582, -0.49521815, -0.40981302],
             [ 0.14077542, -0.62220022, -0.47219295, -0.60834446]])),
     (array([1.0000000e-08, 1.0000000e-08, 1.0000000e-08, 9.9999997e-01]),
      array([[ 9.83875698e-01, -1.39235844e-01,  1.04108665e-01,
               4.19925812e-02],
             [ 1.14990435e-01,  1.08386414e-01, -9.82305219e-01,
               1.00528809e-01],
             [-1.36987705e-01, -9.10200323e-01, -7.72553661e-02,
               3.83146642e-01],
             [-4.24046728e-04,  3.74701397e-01,  1.35164532e-01,
               9.17240008e-01]])))

### Circuit DMKDC

We add an additional qubit to encode the class

```python
# 4 x 4 Example with eigenvalues
backend = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(7, 5)

qc.h(0)
qc.h(1)
qc.h(2)
qc.append(ArbRot(*(a**2)), [3, 4])
qc.barrier()
qc.x(2)
qc.x(0)
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U0[:, 0]**2)), [0, 1, 2, 3, 4])
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U0[:, 1]**2)), [0, 1, 2, 3, 4])
qc.x(0)
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U0[:, 2]**2)), [0, 1, 2, 3, 4])
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U0[:, 3]**2)), [0, 1, 2, 3, 4])
qc.append(ArbRotControlled(*lambda_P0), [2, 5, 6])

qc.barrier()
qc.x(2)
qc.x(0)
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U1[:, 0]**2)), [0, 1, 2, 3, 4])
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U1[:, 1]**2)), [0, 1, 2, 3, 4])
qc.x(0)
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U1[:, 2]**2)), [0, 1, 2, 3, 4])
qc.x(1)
qc.append(ArbRotToffM2Transpose(*(U1[:, 3]**2)), [0, 1, 2, 3, 4])
qc.append(ArbRotControlled(*lambda_P1), [2, 5, 6])

qc.cx(1, 6)
qc.cx(0, 5)

qc.barrier()

qc.measure(2, 0)
qc.measure(3, 1)
qc.measure(4, 2)
qc.measure(5, 3)
qc.measure(6, 4)

counts = execute(qc, backend, shots=150000).result().get_counts()
plot_histogram(counts)
```

```python
qc.draw('mpl')
```

```python
# Expected and calculated for class 0
exp_a_calculated_P0 = (counts["00000"]/150000)*8
exp_a_theorical_P0 = a.T @ P0 @ a

# Expected and calculated for class 1
exp_a_calculated_P1 = (counts["00001"]/150000)*8
exp_a_theorical_P1 = a.T @ P1 @ a

exp_a_calculated_P0, exp_a_theorical_P0, exp_a_calculated_P1, exp_a_theorical_P1
```

    (0.7897066666666667, 0.7797639127048509, 0.83856, 0.8300298691432838)

### Conclusion

Class 0: The expected value of the density matrix was 0.7798, the quantum circuit obtained 0.7897

Class 0: The expected value of the density matrix was 0.7798, the quantum circuit obtained 0.7897

## Digits Dataset

We will work with the Digits dataset, available from Scikit-Learn. We will build a binary classifier, so we will use only the data for digits 0 and 1. The revisted dataset consiste of 360 images: each image consist of 64 pixels, and all images are labeled, so we will do supervised learning for this dataset.

```python
import matplotlib.pyplot as plt
import numpy as np

from sklearn import datasets
from sklearn.decomposition import PCA
```

```python
digits, labels = datasets.load_digits(n_class=2, return_X_y=True)
print(digits.shape)
```

    (360, 64)

```python
_, axes = plt.subplots(nrows=1, ncols=4, figsize=(10, 3))
for ax, image, label in zip(axes, digits, labels):
    ax.set_axis_off()
    ax.imshow(image.reshape(8,8), cmap=plt.cm.gray_r, interpolation='nearest')
```

We will apply dimensionality reduction on the entire data set. For this we will use the PCA method found in Scikit-Learn. We will build a graph in which the variance generated by the first principal components is shown.

```python
sklearn_pca = PCA(n_components=64)
sklearn_transf = sklearn_pca.fit_transform(digits)
varianza_expl = sklearn_pca.explained_variance_ratio_

plt.figure(figsize = (9, 6))
plt.xlabel('Número de componentes principales')
plt.ylabel('Varianza acumulada')

cum_var_exp = np.cumsum(varianza_expl)
nc = np.arange(1, varianza_expl.shape[0] + 1)
plt.plot(nc, cum_var_exp, 'g^')

plt.show()
```

```python
print(np.sum(varianza_expl[0:4]), np.sum(varianza_expl[0:8]),
      np.sum(varianza_expl[0:16]), np.sum(varianza_expl[0:32]))
```

    0.7343821751814189 0.8488897097986975 0.9349381446651397 0.9865762881675042

We choose to use only four principal components, that concentrate 73.44% of the variance of the data. Transforming each image into an array of four values will allow us to convert those values into two-qubit states in a later step.

```python
n_comps = 4
pca = PCA(n_components = n_comps)
Xpca = pca.fit_transform(digits)

plt.figure(figsize=(10,2.5))
for i in range(4):
    plt.subplot(1,4,i+1)
    plt.imshow(pca.components_[i].reshape(8,8), cmap=plt.cm.gray)
    plt.xticks([]); plt.yticks([])

```

```python
Xpca.shape
```

    (360, 4)

Once our images have been reduced to arrays, it's important to normalize those arrays. Also we will split the dataset into two groups, the images labeled '0' and the images labeled '1'. This will be useful in the construction of training states.

```python
V0 = []
V1 = []

for i in range(Xpca.shape[0]):
    if (labels[i] == 0):
        V0.append(Xpca[i] / np.linalg.norm(Xpca[i]))
    elif (labels[i] == 1):
        V1.append(Xpca[i] / np.linalg.norm(Xpca[i]))

print(V0[0:9])
print(V1[0:9])
```

    [array([-0.96461175, -0.03469186,  0.02259613,  0.26040364]), array([-0.90565791, -0.06052352, -0.41329874, -0.07283407]), array([-0.86043944,  0.0880531 , -0.44601489, -0.23013331]), array([-0.9961216 , -0.00346231, -0.05449524, -0.068993  ]), array([-0.84453737, -0.20119917, -0.40344103, -0.28898246]), array([-0.92439828, -0.37854895, -0.02073807, -0.04193372]), array([-0.95429831, -0.25833599,  0.13884569,  0.0574381 ]), array([-0.6378455 ,  0.01511889, -0.6831358 , -0.35531677]), array([-0.79328908, -0.02515803, -0.3341317 , -0.50834586])]
    [array([ 0.99668899, -0.01930275, -0.0461384 ,  0.06410702]), array([ 0.66602183,  0.73851471, -0.07803519, -0.07015306]), array([ 0.61395619,  0.78489308, -0.04819382, -0.06839597]), array([0.68803293, 0.71120081, 0.00579495, 0.14411978]), array([0.68804686, 0.68053337, 0.04259847, 0.24829663]), array([ 0.61999146,  0.78166862, -0.0609321 ,  0.02986712]), array([0.7928533 , 0.57639774, 0.0728766 , 0.1839519 ]), array([ 0.71780492,  0.68154201, -0.12011473,  0.07634809]), array([ 0.88284846,  0.32847047, -0.17294272,  0.28770916])]

```python
print(len(V0))
print(len(V1))
```

    178
    182

```python
print((V1[3]**2).round(4))
```

    [0.4734 0.5058 0.     0.0208]

## Classification

In this section, we will focus in the construction of the classifier and the quantum circuits required. Those circuits will be based in a custom gate called ArbRot designed to build an arbitrary two-state vector given the probabilities for each basis state ($|00\rangle$, $|01\rangle$, $|10\rangle$, $|11\rangle$). This gate consists on three gates, two RX and a CRX.

To build up our circuits, we will define, based on this first gate, controlled, double-controlled and triple-controlled versions.

```python
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
```

```python
def ArbRot(p0, p1, p2, p3):
    qr = QuantumRegister(2)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.rx(ang0, qr[1])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.rx(ang1, qr[0])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.crx(ang2, qr[1], qr[0])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbRot"
    return gate
```

```python
def ArbRotControlled(p0, p1, p2, p3):
    qr = QuantumRegister(3)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.crx(ang0, qr[0],  qr[2])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.crx(ang1, qr[0], qr[1])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(ang2, [qr[0], qr[2]], qr[1])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbRot-C"
    return gate
```

```python
def ArbRotControlledTranspose(p0, p1, p2, p3):
    qr = QuantumRegister(3)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.crx(-ang0, qr[0],  qr[2])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.crx(-ang1, qr[0], qr[1])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[2]], qr[1])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbRot-C-dg"
    return gate
```

```python
def ArbRotToffTranspose(p0, p1, p2, p3):
    qr = QuantumRegister(4)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.mcrx(-ang0, [qr[0], qr[1]],  qr[3])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.mcrx(-ang1, [qr[0], qr[1]], qr[2])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[1], qr[3]], qr[2])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbRot-2C-dg"
    return gate
```

```python
def ArbRotToffM2Transpose(p0, p1, p2, p3):
    qr = QuantumRegister(5)
    qc = QuantumCircuit(qr)

    ang0 = 2*np.arccos(np.sqrt(p0+p1))
    qc.mcrx(-ang0, [qr[0], qr[1], qr[2]],  qr[4])
    ang1 = 2*np.arccos(np.sqrt(p0/(p0+p1)))
    qc.mcrx(-ang1, [qr[0], qr[1], qr[2]], qr[3])
    ang2 = 2*np.arccos(np.sqrt(p2/(p2+p3))) - ang1
    qc.mcrx(-ang2, [qr[0], qr[1], qr[2], qr[4]], qr[3])
    #qc.swap(qr[0], qr[1])

    gate = qc.to_gate()
    gate.label = "ArbRot-3C-dg"
    return gate
```

To transform a four-values array into a two-qubit state, we will do the following transformation:

$$
(a,b,c,d) \rightarrow a|00\rangle + b|01\rangle + c|10\rangle + d|11\rangle
$$

This makes sense because we have normalized our arrays before, that is, $a^2+b^2+c^2+d^2=1$ for each array. In our quantum circuits, the task of building each particular state will be performed by our ArbRot gates.

### One sample per class

To perform a classification, we need to define three states:

* $|\psi_0 \rangle$, the state that holds all the information from '0' samples in the training process.
* $|\psi_1 \rangle$, the state that holds all the information from '1' samples in the training process.
* $|x \rangle$, the state corresponding to the image we want to classify.

As a first approximation, we will choose one array from the '0' partition as $|\psi_0 \rangle$, and one array from the '1' partition as $|\psi_1 \rangle$. Next, we will try to predict labels for other two arrays, each one from a different partition.

```python
import numpy as np

a0 = np.array(V0[1])
a1 = np.array(V1[1])
b = np.array(V0[2])
c = np.array(V1[2])
```

```python
0.5*np.dot(a0, b)**2, 0.5*(np.dot(a0, c)**2)
```

    (0.475345104071906, 0.16741141389309663)

```python
0.5*np.dot(a1, b)**2, 0.5*(np.dot(a1, c)**2)
```

    (0.10446724853375967, 0.49712645102867914)

In our most basic proposed circuit, we will have three qubits. The least significant ($q_0$) will act as an ancillary qubit, and the other two qubits ($q_1$ and $q_2$) will store our states. So we prepare our state $|x \rangle$ using an ArbRot; we then put an ArbRotControlled, using the ancilla qubit as control, that performs rotations given by the state $|\psi_0 \rangle$; and then we put another ArbRotControlled that performs rotations given by the state $|\psi_1 \rangle$. Finally we measure the qubits, and we expect that, when the state of the two most significant qubits is $|00 \rangle$, the ancilla qubit will store the prediction, either $|0 \rangle$ for label '0' or $|1 \rangle$ for label '1'.

To obtain a more solid prediction, we will run the circuit many shots, and then we will compare the amount of $|000 \rangle$ versus the amount of $|001 \rangle$. If $|000 \rangle$ is more common, the prediction will be '0'; otherwise, the prediction will be '1'.

```python
# Zero sample

qc = QuantumCircuit(3, 3)

qc.h(0)
qc.append(ArbRot(*(a0**2)), [2,1])

qc.barrier()

qc.x(0)
qc.append(ArbRotControlledTranspose(*(b**2)), [0,2,1])

qc.barrier()

qc.x(0)
qc.append(ArbRotControlledTranspose(*(c**2)), [0,2,1])

qc.barrier()

qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

shots = 10000
job = execute(qc, Aer.get_backend('qasm_simulator'),shots=shots)
counts = job.result().get_counts()
plot_histogram(counts)
```

```python
print((int)(counts['000'] < counts['001']))
```

    0

```python
# One sample

qc = QuantumCircuit(3, 3)

qc.h(0)
qc.append(ArbRot(*(a1**2)), [2,1])

qc.barrier()

qc.x(0)
qc.append(ArbRotControlledTranspose(*(b**2)), [0,2,1])

qc.barrier()

qc.x(0)
qc.append(ArbRotControlledTranspose(*(c**2)), [0,2,1])

qc.barrier()

qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

shots = 10000
job = execute(qc, Aer.get_backend('qasm_simulator'),shots=shots)
counts = job.result().get_counts()
plot_histogram(counts)
```

```python
print((int)(counts['000'] < counts['001']))
```

    1

As we observe, both samples were correctly classified.

The following images show the circuit used in the examples above, the first indicating the ArbRot gates applied, and the second showing the required amounts of rotations, controlled rotations and CNOTs.

```python
qc.draw('mpl')
```

```python
qc.decompose().draw('mpl')
```

### Pure State Classifier

Now, we will define properly the states $|\psi_0 \rangle$ and  $|\psi_1 \rangle$. This can be done in many ways, so here we will use the Pure State approach. This indicates that

$$
 |\psi_0 \rangle = \frac {\sum |\phi_0 \rangle} {\left \| \sum |\phi_0 \rangle \right \|}
$$

being $|\phi_0 \rangle$ the states labeled with '0'.

Similarly, we define

$$
 |\psi_1 \rangle = \frac {\sum |\phi_1 \rangle} {\left \| \sum |\phi_1 \rangle \right \|}
$$

being $|\phi_1 \rangle$ the states labeled with '1'. To construct these states, only train samples will be used.

```python
#Pure state for class 0
S_zero = np.sum(np.abs(V0[0:121]), axis=0)
#S_zero = np.sum(V0[0:121], axis=0)
S_zero = S_zero / np.linalg.norm(S_zero)
print(S_zero)
```

    [0.94247141 0.12817377 0.20752908 0.22858438]

```python
#Pure state for class 1
S_one = np.sum(np.abs(V1[0:121]), axis=0)
#S_one = np.sum(V1[0:121], axis=0)
S_one = S_one / np.linalg.norm(S_one)
print(S_one)
```

    [0.7522197  0.49571815 0.38369994 0.20298621]

Now, we will run the same circuit used before. We need a different circuit for each sample  $|x \rangle$ in the test, and each circuit will run many times to build a more accurate prediction.

```python
# Pure state case
test_zeros = []

for i in range(121, 178):
    qc = QuantumCircuit(3, 3)

    qc.h(0)
    qc.append(ArbRot(*(np.array(V0[i])**2)), [2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_zero**2)), [0,2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_one**2)), [0,2,1])

    qc.barrier()

    qc.measure(0, 0)
    qc.measure(1, 1)
    qc.measure(2, 2)

    shots = 12000
    job = execute(qc, Aer.get_backend('qasm_simulator'),shots=shots)
    counts = job.result().get_counts()
    test_zeros.append((int)(counts['000'] < counts['001']))

print(test_zeros)
```

    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]

```python
# Pure state case
test_ones = []

for i in range(121, 182):
    qc = QuantumCircuit(3, 3)

    qc.h(0)
    qc.append(ArbRot(*(np.array(V1[i])**2)), [2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_zero**2)), [0,2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_one**2)), [0,2,1])

    qc.barrier()

    qc.measure(0, 0)
    qc.measure(1, 1)
    qc.measure(2, 2)

    shots = 12000
    job = execute(qc, Aer.get_backend('qasm_simulator'),shots=shots)
    counts = job.result().get_counts()
    test_ones.append((int)(counts['000'] < counts['001']))

print(test_ones)
```

    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1]

**Classifier Reports**

Once we have a prediction for all the test samples, we will report the confusion matrix and some metrics (accuracy, precision, recall, F1 score).

```python
ytest = np.concatenate([np.zeros(len(test_zeros)), np.ones(len(test_ones))])

preds = np.concatenate([test_zeros, test_ones])
```

```python
import sklearn.metrics as metrics

cm = metrics.confusion_matrix(ytest, preds)

cm_display = metrics.ConfusionMatrixDisplay(cm, display_labels=[0,1]).plot()
```

```python
accuracy = metrics.accuracy_score(ytest,preds)
print("Accuracy: ", accuracy)
precision, recall, f_score, _ = metrics.precision_recall_fscore_support(ytest, preds, average='binary')
print("Precision: ", precision)
print("Recall: ", recall)
print("F1: ", f_score)
```

    Accuracy:  0.847457627118644
    Precision:  0.8524590163934426
    Recall:  0.8524590163934426
    F1:  0.8524590163934426

### Mixed State Classifier

Now, we will redefine the training states using another approach: Mixed State. We will not work directly with $|\psi_0 \rangle$ and $|\psi_1 \rangle$; instead we will use their corresponding density matrices, $\rho_0$ and $\rho_1$. So we have

$$
\rho_0 = \frac{1}{n} \sum |\phi_0 \rangle \langle \phi_0 |
$$

being $|\phi_0 \rangle$ the states labeled with '0', and $n$ the amount of '0' samples.

Similarly, we define

$$
\rho_1 = \frac{1}{m} \sum |\phi_1 \rangle \langle \phi_1 |
$$

being $|\phi_1 \rangle$ the states labeled with '1', and $m$ the amount of '1' samples. To construct the density matrices, only train samples will be used.

Once we built $\rho_0$ and $\rho_1$, we apply the second circuit proposed. In this circuit, we require three ancillary qubits, two qubits to store states, and two auxiliary qubits. We will use ArbRot to define the state $|x \rangle$ we want to classify, and then we'll apply four ArbRot with three controls and one ArbRot with one control to perform the rotations indicated by $\rho_0$; after that, we'll apply other four ArbRot with three controls and one ArbRot with one control to perform the rotations indicated by $\rho_1$. Finally we'll measure the qubits, and the most significant ancilla qubit ($q_2$) will determine the prediction when the state of the other measured qubits is $|0000 \rangle$.

To obtain a more solid prediction, we will run the circuit many shots, and then we will compare the amount of $|00000 \rangle$ versus the amount of $|00001 \rangle$. If $|00000 \rangle$ is more common, the prediction will be '0'; otherwise, the prediction will be '1'.

Now, we are ready to use this new circuit for our dataset. So we will build the density matrices $\rho_0$ and $\rho_1$ using the train samples for the respective labels. Also we need to perform eigen-decompositions of the two matrices.

```python
Z_zero = np.outer(np.abs(V0[0]), np.abs(V0[0]))
for i in range(1, 121):
    Z_zero += np.outer(np.abs(V0[i]), np.abs(V0[i]))

Z_zero *= 1/120
print(Z_zero)
```

    [[0.84098733 0.11112925 0.16993777 0.18888191]
     [0.11112925 0.02429141 0.02373526 0.02690334]
     [0.16993777 0.02373526 0.0673663  0.05376573]
     [0.18888191 0.02690334 0.05376573 0.07568829]]

```python
lambda_P0_temp, U_zero = np.linalg.eigh(Z_zero)
lambda_zero = round_lambda(lambda_P0_temp)
print(lambda_zero)
print(U_zero)
```

    [0.00932379 0.01756579 0.04447589 0.92863453]
    [[-0.11660424 -0.02199912 -0.30481398 -0.94499097]
     [ 0.99051938  0.05144787  0.01083254 -0.12691389]
     [ 0.00354016 -0.70871728  0.67595449 -0.20197232]
     [-0.07254158  0.7032702   0.6708626  -0.22381269]]

```python
Z_one = np.outer(np.abs(V1[0]), np.abs(V1[0]))
for i in range(1, 121):
    Z_one += np.outer(np.abs(V1[i]), np.abs(V1[i]))

Z_one *= 1/120
print(Z_one)
```

    [[0.48870747 0.27367608 0.20872624 0.12179777]
     [0.27367608 0.27199215 0.10905759 0.06913222]
     [0.20872624 0.10905759 0.19846444 0.06883592]
     [0.12179777 0.06913222 0.06883592 0.04916928]]

```python
lambda_P1_temp, U_one = np.linalg.eigh(Z_one)
lambda_one = round_lambda(lambda_P1_temp)
print(lambda_one)
print(U_one)
```

    [0.01531458 0.0678419  0.12256667 0.79427685]
    [[-0.17464078  0.62668804  0.04404586 -0.7581706 ]
     [-0.00648166 -0.55262727 -0.67100606 -0.49427923]
     [-0.16212154 -0.54914909  0.72947362 -0.37419263]
     [ 0.97117207  0.01733416  0.12521607 -0.20210212]]

Like before, we need a different circuit for each sample $|x \rangle$ in the test, and each circuit will run many times to build a more accurate prediction.

```python
backend2 = Aer.get_backend('qasm_simulator')
```

```python
# Mixed state case
test2_zeros = []

for i in range(121, 178):

    qc = QuantumCircuit(7, 5)

    qc.h(0)
    qc.h(1)
    qc.h(2)
    qc.append(ArbRot(*(V0[i]**2)), [3, 4])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_zero), [2, 5, 6])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_one), [2, 5, 6])
    qc.cx(1, 6)
    qc.cx(0, 5)

    qc.barrier()

    qc.measure(2, 0)
    qc.measure(3, 1)
    qc.measure(4, 2)
    qc.measure(5, 3)
    qc.measure(6, 4)

    counts = execute(qc, backend2, shots=50000).result().get_counts()
    test2_zeros.append((int)(counts['00000'] < counts['00001']))

print(test2_zeros)
```

    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]

```python
# Mixed state case
test2_ones = []

for i in range(121, 182):

    qc = QuantumCircuit(7, 5)

    qc.h(0)
    qc.h(1)
    qc.h(2)
    qc.append(ArbRot(*(V1[i]**2)), [3, 4])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_zero), [2, 5, 6])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_one), [2, 5, 6])
    qc.cx(1, 6)
    qc.cx(0, 5)

    qc.barrier()

    qc.measure(2, 0)
    qc.measure(3, 1)
    qc.measure(4, 2)
    qc.measure(5, 3)
    qc.measure(6, 4)

    counts = execute(qc, backend2, shots=50000).result().get_counts()
    test2_ones.append((int)(counts['00000'] < counts['00001']))

print(test2_ones)
```

    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1]

**Classifier Reports**

Once we have a prediction for all the test samples, we will report the confusion matrix and some metrics (accuracy, precision, recall, F1 score).

```python
ytest = np.concatenate([np.zeros(len(test2_zeros)), np.ones(len(test2_ones))])

preds2 = np.concatenate([test2_zeros, test2_ones])
```

```python
import sklearn.metrics as metrics

cm2 = metrics.confusion_matrix(ytest, preds2)

cm2_display = metrics.ConfusionMatrixDisplay(cm2, display_labels=[0,1]).plot()
```

```python
accuracy = metrics.accuracy_score(ytest,preds2)
print("Accuracy: ", accuracy)
precision, recall, f_score, _ = metrics.precision_recall_fscore_support(ytest, preds2, average='binary')
print("Precision: ", precision)
print("Recall: ", recall)
print("F1: ", f_score)
```

    Accuracy:  0.8305084745762712
    Precision:  0.847457627118644
    Recall:  0.819672131147541
    F1:  0.8333333333333333

### Classifying with noisy backends

```python
from qiskit import IBMQ, transpile
from qiskit import QuantumCircuit
from qiskit.providers.aer import AerSimulator
from qiskit.tools.visualization import plot_histogram
```

```python
from qiskit.test.mock import FakeCasablanca
from qiskit.providers.aer import AerSimulator

device_backend = FakeCasablanca()
casablanca = AerSimulator.from_backend(device_backend)
```

```python
# Pure state case
test_zeros = []

for i in range(121, 178):
    qc = QuantumCircuit(3, 3)

    qc.h(0)
    qc.append(ArbRot(*(np.array(V0[i])**2)), [2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_zero**2)), [0,2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_one**2)), [0,2,1])

    qc.barrier()

    qc.measure(0, 0)
    qc.measure(1, 1)
    qc.measure(2, 2)

    shots = 12000
    job = execute(qc, backend=casablanca, shots=shots)
    counts = job.result().get_counts()
    test_zeros.append((int)(counts['000'] < counts['001']))

print(test_zeros)
```

    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]

```python
# Pure state case
test_ones = []

for i in range(121, 182):
    qc = QuantumCircuit(3, 3)

    qc.h(0)
    qc.append(ArbRot(*(np.array(V1[i])**2)), [2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_zero**2)), [0,2,1])

    qc.barrier()

    qc.x(0)
    qc.append(ArbRotControlledTranspose(*(S_one**2)), [0,2,1])

    qc.barrier()

    qc.measure(0, 0)
    qc.measure(1, 1)
    qc.measure(2, 2)

    shots = 12000
    job = execute(qc, backend=casablanca, shots=shots)
    counts = job.result().get_counts()
    test_ones.append((int)(counts['000'] < counts['001']))

print(test_ones)
```

    [0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1]

**Classifier Reports**

Once we have a prediction for all the test samples, we will report the confusion matrix and some metrics (accuracy, precision, recall, F1 score).

```python
ytest = np.concatenate([np.zeros(len(test_zeros)), np.ones(len(test_ones))])

preds = np.concatenate([test_zeros, test_ones])
```

```python
import sklearn.metrics as metrics

cm = metrics.confusion_matrix(ytest, preds)

cm_display = metrics.ConfusionMatrixDisplay(cm, display_labels=[0,1]).plot()
```

```python
accuracy = metrics.accuracy_score(ytest,preds)
print("Accuracy: ", accuracy)
precision, recall, f_score, _ = metrics.precision_recall_fscore_support(ytest, preds, average='binary')
print("Precision: ", precision)
print("Recall: ", recall)
print("F1: ", f_score)
```

    Accuracy:  0.7288135593220338
    Precision:  0.8536585365853658
    Recall:  0.5737704918032787
    F1:  0.6862745098039217

```python
# Mixed state case
test2_zeros = []

for i in range(121, 178):

    qc = QuantumCircuit(7, 5)

    qc.h(0)
    qc.h(1)
    qc.h(2)
    qc.append(ArbRot(*(V0[i]**2)), [3, 4])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_zero), [2, 5, 6])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_one), [2, 5, 6])
    qc.cx(1, 6)
    qc.cx(0, 5)

    qc.barrier()

    qc.measure(2, 0)
    qc.measure(3, 1)
    qc.measure(4, 2)
    qc.measure(5, 3)
    qc.measure(6, 4)

    counts = execute(qc, backend=casablanca, shots=50000).result().get_counts()
    test2_zeros.append((int)(counts['00000'] < counts['00001']))

print(test2_zeros)
```

    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]

```python
# Mixed state case
test2_ones = []

for i in range(121, 182):

    qc = QuantumCircuit(7, 5)

    qc.h(0)
    qc.h(1)
    qc.h(2)
    qc.append(ArbRot(*(V1[i]**2)), [3, 4])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_zero[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_zero), [2, 5, 6])

    qc.barrier()
    qc.x(2)
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 0]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 1]**2)), [0, 1, 2, 3, 4])
    qc.x(0)
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 2]**2)), [0, 1, 2, 3, 4])
    qc.x(1)
    qc.append(ArbRotToffM2Transpose(*(U_one[:, 3]**2)), [0, 1, 2, 3, 4])
    qc.append(ArbRotControlled(*lambda_one), [2, 5, 6])
    qc.cx(1, 6)
    qc.cx(0, 5)

    qc.barrier()

    qc.measure(2, 0)
    qc.measure(3, 1)
    qc.measure(4, 2)
    qc.measure(5, 3)
    qc.measure(6, 4)

    counts = execute(qc, backend=casablanca, shots=50000).result().get_counts()
    test2_ones.append((int)(counts['00000'] < counts['00001']))

print(test2_ones)
```

    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1]

**Classifier Reports**

Once we have a prediction for all the test samples, we will report the confusion matrix and some metrics (accuracy, precision, recall, F1 score).

```python
ytest = np.concatenate([np.zeros(len(test2_zeros)), np.ones(len(test2_ones))])

preds2 = np.concatenate([test2_zeros, test2_ones])
```

```python
import sklearn.metrics as metrics

cm2 = metrics.confusion_matrix(ytest, preds2)

cm2_display = metrics.ConfusionMatrixDisplay(cm2, display_labels=[0,1]).plot()
```

```python
accuracy = metrics.accuracy_score(ytest,preds2)
print("Accuracy: ", accuracy)
precision, recall, f_score, _ = metrics.precision_recall_fscore_support(ytest, preds2, average='binary')
print("Precision: ", precision)
print("Recall: ", recall)
print("F1: ", f_score)
```

    Accuracy:  0.8305084745762712
    Precision:  0.847457627118644
    Recall:  0.819672131147541
    F1:  0.8333333333333333

## Acknowledgements

The team thanks specially to our mentor Fabio González for his valuable help and support, and to Vladimir Vargas-Calderón for the useful discussions we had during the completion of this project. Also, we acknowledge the use of IBM Quantum services.

## References

[González2021] Fabio  A.  González,  Alejandro  Gallego,  Santiago  Toledo-Cortés,  and Vladimir Vargas-Calderón.  Learning with Density Matrices and Random Features.  2021.  URL http://arxiv.org/abs/2102.04394.

[QTML2021] Vladimir Vargas-Calderón, Fabio A. González, Herbert Vinck-Posada. Optimisation-Free Classification with Quantum Circuit Measurements. 2021.

[Useche2021] Diego H Useche, Andres Giraldo-Carvajal, Hernan M Zuluaga-Bucheli, Jose A Jaramillo-Villegas, and Fabio A González. Quantum measurement classification with qudits. arXiv preprint arXiv:2107.09781, 2021.


# Calculation of control fields for QFT gate on two qubits using L-BFGS-B algorithm

Alexander Pitchford (agp1@aber.ac.uk)

```python
cd /Users/benrosand/oct-qiskit-pulse
```

    /Users/benrosand/oct-qiskit-pulse
    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Imports

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import datetime
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
from qutip import Qobj, identity, sigmax, sigmay, sigmaz, tensor, hadamard_transform, basis
from qutip.qip.algorithms import qft
import qutip.logging_utils as logging
logger = logging.get_logger()
#Set this to None or logging.WARN for 'quiet' execution
log_level = logging.INFO
#QuTiP control modules
import qutip.control.pulseoptim as cpo
import qutip.control.pulsegen as pulsegen
from src.qutip_helper import convert_qutip_ham
from src.helper import *
from qiskit import IBMQ
from qutip.qip.device import Processor
from qiskit.pulse import Play, SamplePulse

from qiskit.providers.aer.pulse.system_models.hamiltonian_model import HamiltonianModel

example_name = 'QFT'
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python

```

### Defining the physics

Note here that there are two controls acting on each qubit.

```python
# subsystem_list=[0,1]
subsystem_list=[0,1]
single_q = True
IBMQ.load_account()

# provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')
provider = IBMQ.get_provider(hub='ibm-q-internal', group='deployed', project='default')
backend = provider.get_backend('ibmq_athens')

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    ibmqfactory.load_account:WARNING:2020-09-08 18:53:20,288: Credentials are already in use. The existing account in the session will be replaced.

```python
# HamiltonianModel.from_dict(backend.configuration().hamiltonian, subsystem_list=[0])._system[1][0].full()
print(backend.configuration().hamiltonian['h_latex'])

```

    \begin{align} \mathcal{H}/\hbar = & \sum_{i=0}^{4}\left(\frac{\omega_{q,i}}{2}(\mathbb{I}-\sigma_i^{z})+\frac{\Delta_{i}}{2}(O_i^2-O_i)+\Omega_{d,i}D_i(t)\sigma_i^{X}\right) \\ & + J_{1,2}(\sigma_{1}^{+}\sigma_{2}^{-}+\sigma_{1}^{-}\sigma_{2}^{+}) + J_{3,4}(\sigma_{3}^{+}\sigma_{4}^{-}+\sigma_{3}^{-}\sigma_{4}^{+}) + J_{0,1}(\sigma_{0}^{+}\sigma_{1}^{-}+\sigma_{0}^{-}\sigma_{1}^{+}) + J_{2,3}(\sigma_{2}^{+}\sigma_{3}^{-}+\sigma_{2}^{-}\sigma_{3}^{+}) \\ & + \Omega_{d,0}(U_{0}^{(0,1)}(t))\sigma_{0}^{X} + \Omega_{d,1}(U_{1}^{(1,0)}(t)+U_{2}^{(1,2)}(t))\sigma_{1}^{X} \\ & + \Omega_{d,2}(U_{3}^{(2,1)}(t)+U_{4}^{(2,3)}(t))\sigma_{2}^{X} + \Omega_{d,3}(U_{6}^{(3,4)}(t)+U_{5}^{(3,2)}(t))\sigma_{3}^{X} \\ & + \Omega_{d,4}(U_{7}^{(4,3)}(t))\sigma_{4}^{X} \\ \end{align}
    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

\begin{align} \mathcal{H}/\hbar = & \sum_{i=0}^{4}\left(\frac{\omega_{q,i}}{2}(\mathbb{I}-\sigma_i^{z})+\frac{\Delta_{i}}{2}(O_i^2-O_i)+\Omega_{d,i}D_i(t)\sigma_i^{X}\right) \\ & + J_{1,2}(\sigma_{1}^{+}\sigma_{2}^{-}+\sigma_{1}^{-}\sigma_{2}^{+}) + J_{3,4}(\sigma_{3}^{+}\sigma_{4}^{-}+\sigma_{3}^{-}\sigma_{4}^{+}) + J_{0,1}(\sigma_{0}^{+}\sigma_{1}^{-}+\sigma_{0}^{-}\sigma_{1}^{+}) + J_{2,3}(\sigma_{2}^{+}\sigma_{3}^{-}+\sigma_{2}^{-}\sigma_{3}^{+}) \\ & + \Omega_{d,0}(U_{0}^{(0,1)}(t))\sigma_{0}^{X} + \Omega_{d,1}(U_{1}^{(1,0)}(t)+U_{2}^{(1,2)}(t))\sigma_{1}^{X} \\ & + \Omega_{d,2}(U_{3}^{(2,1)}(t)+U_{4}^{(2,3)}(t))\sigma_{2}^{X} + \Omega_{d,3}(U_{6}^{(3,4)}(t)+U_{5}^{(3,2)}(t))\sigma_{3}^{X} \\ & + \Omega_{d,4}(U_{7}^{(4,3)}(t))\sigma_{4}^{X} \\ \end{align}

```python
backend.configuration().hamiltonian['vars']['wq0'] = 0
backend.configuration().hamiltonian['vars']['wq1'] = 0
ham = convert_qutip_ham(backend, subsystem_list, False)
H_d = ham['H_d']
# ham['H_c'].pop('U2')
H_c = list(ham['H_c'].values())

n_ctrls = len(H_c)
n_ctrls
ham['H_c'].keys()
```

    U0
    not using control
    U1
    not using control
    U2
    not using control
    D0
    NEED TO FINISH sigx1 or 2
    D1
    NEED TO FINISH sigx1 or 2
    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    dict_keys(['D0', 'D0y', 'D1', 'D1y'])

```python
H_d
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[9], [9]], shape = (9, 9), type = oper, isherm = True\begin{equation*}\left(\begin{array}{*{11}c}0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.010 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & -2.112 & 0.0 & 0.015 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.010 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.015 & 0.0 & 0.0 & 0.0 & 0.015 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & -2.112 & 0.0 & 0.021 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.015 & 0.0 & -2.089 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.021 & 0.0 & -2.089 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & -4.201\\\end{array}\right)\end{equation*}

### Define the 3 level operators

```python
h = hadamard_transform(1).full()

hadamard_3 = Qobj([[h[0][0],h[0][1],0],
                   [h[1][0],h[1][1],0],
                   [0,      0,      1]])
sigmax_3 =  Qobj([[0,1,0],
                  [1,0,0],
                  [0,0,1]])

# identity
# cnot = d_sum(Qobj(d_sum(identity(3), identity(3)), sigmax_3))
# cnot = d_sum(identity(6), sigmax_3)
zeros3 = Qobj(np.zeros((3,3)))
cnot = d_sum(Qobj(d_sum(identity(3), sigmax_3)), zeros3)
cnot = d_sum(Qobj(d_sum(identity(3), sigmax_3)),identity(3))
# cnot = d_sum(Qobj(d_sum(identity(3),zeros)), sigmax_3)
U_0 = tensor(identity(3), identity(3))
U_targ = Qobj(cnot)
U_targ = d_sum((d_sum(sigmax_3, identity(3))),identity(3))
# U_targ
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    <ipython-input-81-b755bf3d8457>:1: DeprecationWarning: Importing functions/classes of the qip submodule directly from the namespace qutip is deprecated. Please import them from the submodule instead, e.g.
    from qutip.qip.operations import cnot
    from qutip.qip.circuit import QubitCircuit

      h = hadamard_transform(1).full()

```python
U_targ
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[9], [9]], shape = (9, 9), type = oper, isherm = True\begin{equation*}\left(\begin{array}{*{11}c}0.0 & 1.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\1.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 1.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 1.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 1.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 1.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 1.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 1.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 1.0\\\end{array}\right)\end{equation*}

### Defining the time evolution parameters

Multiple total evolution times will be tried. Using this approach, the minimum evolution time required to achieve the target fidelity could be determined (iteratively).

Note that the timeslot duration dt is fixed, and so the number of timeslots depends on the evo_time

```python
# Duration of each timeslot
dt = backend.configuration().dt * 1e9
# dt = 0.02
# n_ts_list = list(np.arange(,1200,160))
n_ts_list = [480]
# n_ts_list = [512]#, 480, 560,640]
evo_times = [dt * n for n in n_ts_list]
# List of evolution times to try
# evo_times = [1, 3, 6]
n_evo_times = len(evo_times)
evo_time = evo_times[0]
n_ts = int(float(evo_time) / dt)
#Empty list that will hold the results for each evolution time
# results = list()
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Set the conditions which will cause the pulse optimisation to terminate

### Set the initial pulse type

Here the linear initial pulse type is used, simply because it results in smooth final pulses

```python
# pulse type alternatives: RND|ZERO|LIN|SINE|SQUARE|SAW|TRIANGLE|
p_type = 'LIN'
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Give an extension for output files

```python
#Set to None to suppress output files
# f_ext = "{}_n_ts{}_ptype{}.txt".format(example_name, n_ts, p_type)
f_ext = None
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Create the optimiser objects

Here is the main difference between this and the Hadamard example. In this case we use a different pulseoptim function that just creates the objects that can be used to set the physics and configure the optimisation algorithm. This gives greater flexibility (shown here by seting different initial pulse parameters for each control) and is also more efficient when running multiple optimisations on the same system.

```python
# Fidelity error target
fid_err_targ = 1e-30
# Maximum iterations for the optisation algorithm
max_iter = 2000
# Maximum (elapsed) time allowed in seconds
max_wall_time = 120
# Minimum gradient (sum of gradients squared)
# as this tends to 0 -> local minima has been found
min_grad = 1e-30
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
optim = cpo.create_pulse_optimizer(H_d, H_c, U_0, U_targ, n_ts, evo_time,
                amp_lbound=-.7, amp_ubound=.7,
                fid_err_targ=fid_err_targ, min_grad=min_grad,
                max_iter=max_iter, max_wall_time=max_wall_time,
                optim_method='fmin_l_bfgs_b',
                method_params={'max_metric_corr':20, 'accuracy_factor':1e8},
                dyn_type='UNIT',
                fid_params={'phase_option':'PSU'},
                init_pulse_type=p_type,
                log_level=log_level, gen_stats=True)

# **** get handles to the other objects ****
optim.test_out_files = 0
dyn = optim.dynamics
dyn.test_out_files = 0
p_gen = optim.pulse_generator

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Optimise the pulse for each of the different evolution times

Here a loop is used to perform the optimisation for each of the evo_times given in the list above. The first optimisation is completed using the timeslot parameters passed when the optimisation objects are created. For the subsequent runs, the Dynamics object 'dyn' is used to set the timeslot parameters before the initial pulses are generated and optimisation is completed. Note that using this method, the dyn.initialize_controls method must be called with an array of the initial amplitudes before the optim.run_optimization method is called.

```python
dt
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    0.2222222222222222

```python
results = []
for i in range(n_evo_times):
    # Generate the tau (duration) and time (cumulative) arrays
    # so that it can be used to create the pulse generator
    # with matching timeslots
    dyn.init_timeslots()
    if i > 0:
        # Create a new pulse generator for the new dynamics
        p_gen = pulsegen.create_pulse_gen(p_type, dyn)

    #Generate different initial pulses for each of the controls
    init_amps = np.zeros([n_ts, n_ctrls])
    if (p_gen.periodic):
        phase_diff = np.pi / n_ctrls
        for j in range(n_ctrls):
            init_amps[:, j] = p_gen.gen_pulse(start_phase=phase_diff*j)
    elif (isinstance(p_gen, pulsegen.PulseGenLinear)):
        for j in range(n_ctrls):
            p_gen.scaling = float(j) - float(n_ctrls - 1)/2
            init_amps[:, j] = p_gen.gen_pulse()
    elif (isinstance(p_gen, pulsegen.PulseGenZero)):
        for j in range(n_ctrls):
            p_gen.offset = sf = float(j) - float(n_ctrls - 1)/2
            init_amps[:, j] = p_gen.gen_pulse()
    else:
        # Should be random pulse
        for j in range(n_ctrls):
            init_amps[:, j] = p_gen.gen_pulse()

    dyn.initialize_controls(init_amps)

    # Save initial amplitudes to a text file
    if f_ext is not None:
        pulsefile = "ctrl_amps_initial_" + f_ext
        dyn.save_amps(pulsefile)
        print("Initial amplitudes output to file: " + pulsefile)

    print("***********************************")
    print("\n+++++++++++++++++++++++++++++++++++")
    print("Starting pulse optimisation for T={}".format(evo_time))
    print("+++++++++++++++++++++++++++++++++++\n")
    result = optim.run_optimization()
    # Save final amplitudes to a text file
    if f_ext is not None:
        pulsefile = "ctrl_amps_final_" + f_ext
        dyn.save_amps(pulsefile)
        print("Final amplitudes output to file: " + pulsefile)

    # Report the results
    result.stats.report()
    print("Final evolution\n{}\n".format(result.evo_full_final))
    print("********* Summary *****************")
    print("Final fidelity error {}".format(result.fid_err))
    print("Final gradient normal {}".format(result.grad_norm_final))
    print("Terminated due to {}".format(result.termination_reason))
    print("Number of iterations {}".format(result.num_iter))
    print("Completed in {} HH:MM:SS.US".format(
            datetime.timedelta(seconds=result.wall_time)))
    results.append(result)
    if i+1 < len(evo_times):
        # reconfigure the dynamics for the next evo time
        evo_time = evo_times[i+1]
        n_ts = int(float(evo_time) / dt)
        dyn.tau = None
        dyn.evo_time = evo_time
        dyn.num_tslots = n_ts
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    INFO:qutip.control.dynamics:Setting memory optimisations for level 0
    INFO:qutip.control.dynamics:Internal operator data type choosen to be <class 'numpy.ndarray'>
    INFO:qutip.control.dynamics:phased dynamics generator caching True
    INFO:qutip.control.dynamics:propagator gradient caching True
    INFO:qutip.control.dynamics:eigenvector adjoint caching True
    INFO:qutip.control.dynamics:use sparse eigen decomp False
    ***********************************

    +++++++++++++++++++++++++++++++++++
    Starting pulse optimisation for T=71.11111111111111
    +++++++++++++++++++++++++++++++++++

    INFO:qutip.control.optimizer:Optimising pulse(s) using GRAPE with 'fmin_l_bfgs_b' method

    ------------------------------------
    ---- Control optimisation stats ----
    **** Timings (HH:MM:SS.US) ****
    Total wall time elapsed during optimisation: 0:02:00.062944
    Wall time computing Hamiltonians: 0:00:03.293944 (2.74%)
    Wall time computing propagators: 0:01:47.560585 (89.59%)
    Wall time computing forward propagation: 0:00:00.375685 (0.31%)
    Wall time computing onward propagation: 0:00:00.345646 (0.29%)
    Wall time computing gradient: 0:00:07.712205 (6.42%)

    **** Iterations and function calls ****
    Number of iterations: 233
    Number of fidelity function calls: 239
    Number of times fidelity is computed: 240
    Number of gradient function calls: 239
    Number of times gradients are computed: 239
    Number of times timeslot evolution is recomputed: 240

    **** Control amplitudes ****
    Number of control amplitude updates: 239
    Mean number of updates per iteration: 1.0257510729613735
    Number of timeslot values changed: 76137
    Mean number of timeslot changes per update: 318.56485355648533
    Number of amplitude values changed: 292204
    Mean number of amplitude changes per update: 1222.6108786610878
    ------------------------------------
    Final evolution
    Quantum object: dims = [[3, 3], [3, 3]], shape = (9, 9), type = oper, isherm = False
    Qobj data =
    [[-3.46397117e-01+4.43773230e-01j -6.48565512e-01-5.08581498e-01j
       1.74967178e-02+3.36972120e-03j -6.27857709e-03-1.91440366e-02j
      -2.77062938e-02-1.12655346e-02j -3.22804073e-02-1.35655031e-02j
      -9.85650924e-03+1.21895598e-03j  1.41362385e-02-7.54270752e-03j
       2.41071082e-02+1.21446031e-03j]
     [-6.57170451e-01-4.97498388e-01j -3.39924313e-01+4.49881718e-01j
       1.93962989e-02+5.00291150e-03j  2.12061227e-02+5.42916402e-04j
       8.19994862e-04-7.30912511e-03j -3.09869440e-02-1.24922682e-02j
       7.75360572e-03+1.47845440e-02j -8.72217262e-03+1.10483367e-02j
       1.19577090e-02+6.23223429e-03j]
     [-1.86153811e-02-8.15342070e-04j -1.78554789e-02+2.12142672e-03j
      -9.93359238e-01-9.67885868e-02j -5.90965393e-03-1.68082962e-02j
      -2.15716029e-02-3.52866509e-02j -2.33902665e-03+1.98453375e-02j
       2.62372547e-03-5.09876638e-03j  5.77045921e-03+2.44639774e-02j
       4.34394359e-03-9.38665099e-03j]
     [-6.25920400e-03-2.23196643e-02j  5.39270683e-03+6.62112518e-03j
       1.76320374e-02-3.46566201e-03j -9.11342602e-01-3.26184779e-01j
       8.42991909e-02-2.28361196e-01j -3.72444674e-02-1.76712335e-03j
      -6.87573948e-04+1.21145827e-02j  3.04166652e-02-1.16016122e-02j
       1.43524951e-02-8.15617044e-03j]
     [ 2.21839543e-02-1.87839689e-02j  1.61989302e-02+1.32446812e-02j
       3.61124726e-02-1.73012467e-02j  8.81760736e-02-2.25241943e-01j
      -8.98427075e-01-3.59009694e-01j  1.19502604e-02-3.21411133e-02j
       3.16342546e-02-3.84659415e-03j -8.06945591e-03+3.58469003e-03j
       1.66062852e-02+7.24203145e-04j]
     [ 3.29592567e-02+1.06927958e-02j  3.24092435e-02+9.92935297e-03j
      -1.28562320e-02+1.80476665e-02j  2.76617370e-02+3.32233589e-02j
       1.33175515e-02-1.99872322e-02j -8.18762360e-01-5.68434534e-01j
       1.75901086e-02+4.08491617e-04j -2.79713420e-02-4.59368000e-03j
       1.04807654e-02-1.02202666e-03j]
     [-1.00231660e-02-6.04546024e-03j  8.27762194e-03+1.85202679e-02j
      -4.66128938e-03-1.01359197e-02j  8.67943839e-03-5.25841369e-04j
      -2.12053170e-02-2.17149003e-02j -1.52232068e-02-7.62782667e-03j
      -8.83739173e-01-3.45944900e-01j  1.10098223e-01-2.90136298e-01j
       2.96405454e-02-1.37190626e-02j]
     [-1.28462085e-02+8.97422460e-03j -1.04100147e-03-8.24927633e-03j
      -1.20091413e-02+1.88075674e-02j -2.05050788e-02-2.67361246e-02j
       1.55075629e-02-3.56088082e-03j  2.23733027e-02+1.47912598e-02j
       9.71103389e-02-2.94775538e-01j -9.01754472e-01-2.94012854e-01j
      -2.74759557e-02+1.99228807e-02j]
     [-1.92445484e-02-3.69156183e-03j -1.23245420e-02-1.22463225e-02j
       5.04836745e-03-9.24224369e-03j  1.98506797e-03-1.70896311e-02j
      -4.97070981e-03-1.58300714e-02j -3.15664384e-03-1.35425785e-02j
      -1.45729743e-02-3.01535711e-02j  1.02256848e-02+3.10189041e-02j
      -8.10246464e-01-5.82881556e-01j]]

    ********* Summary *****************
    Final fidelity error 0.07428567394240282
    Final gradient normal 0.006392721977339219
    Terminated due to Max wall time exceeded
    Number of iterations 234
    Completed in 0:02:00.062944 HH:MM:SS.US

```python
evo = results[0].evo_full_final
evo.tidyup(1e-3)
# len(H_c)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[3, 3], [3, 3]], shape = (9, 9), type = oper, isherm = False\begin{equation*}\left(\begin{array}{*{11}c}(-0.346+0.444j) & (-0.649-0.509j) & (0.017+0.003j) & (-0.006-0.019j) & (-0.028-0.011j) & (-0.032-0.014j) & (-0.010+0.001j) & (0.014-0.008j) & (0.024+0.001j)\\(-0.657-0.497j) & (-0.340+0.450j) & (0.019+0.005j) & 0.021 & -0.007j & (-0.031-0.012j) & (0.008+0.015j) & (-0.009+0.011j) & (0.012+0.006j)\\-0.019 & (-0.018+0.002j) & (-0.993-0.097j) & (-0.006-0.017j) & (-0.022-0.035j) & (-0.002+0.020j) & (0.003-0.005j) & (0.006+0.024j) & (0.004-0.009j)\\(-0.006-0.022j) & (0.005+0.007j) & (0.018-0.003j) & (-0.911-0.326j) & (0.084-0.228j) & (-0.037-0.002j) & 0.012j & (0.030-0.012j) & (0.014-0.008j)\\(0.022-0.019j) & (0.016+0.013j) & (0.036-0.017j) & (0.088-0.225j) & (-0.898-0.359j) & (0.012-0.032j) & (0.032-0.004j) & (-0.008+0.004j) & 0.017\\(0.033+0.011j) & (0.032+0.010j) & (-0.013+0.018j) & (0.028+0.033j) & (0.013-0.020j) & (-0.819-0.568j) & 0.018 & (-0.028-0.005j) & (0.010-0.001j)\\(-0.010-0.006j) & (0.008+0.019j) & (-0.005-0.010j) & 0.009 & (-0.021-0.022j) & (-0.015-0.008j) & (-0.884-0.346j) & (0.110-0.290j) & (0.030-0.014j)\\(-0.013+0.009j) & (-0.001-0.008j) & (-0.012+0.019j) & (-0.021-0.027j) & (0.016-0.004j) & (0.022+0.015j) & (0.097-0.295j) & (-0.902-0.294j) & (-0.027+0.020j)\\(-0.019-0.004j) & (-0.012-0.012j) & (0.005-0.009j) & (0.002-0.017j) & (-0.005-0.016j) & (-0.003-0.014j) & (-0.015-0.030j) & (0.010+0.031j) & (-0.810-0.583j)\\\end{array}\right)\end{equation*}

### Plot the initial and final amplitudes

```python
fig1 = plt.figure(figsize=(20,14))
for i in range(n_evo_times):
    #Initial amps
    ax1 = fig1.add_subplot(2, n_evo_times, i+1)
    ax1.set_title("Init amps T={}".format(evo_times[i]))
    # ax1.set_xlabel("Time")
    ax1.get_xaxis().set_visible(False)
    if i == 0:
        ax1.set_ylabel("Control amplitude")
    for j in range(n_ctrls):
        ax1.step(results[i].time,
             np.hstack((results[i].initial_amps[:, j],
                        results[i].initial_amps[-1, j])),
                 where='post')

    ax2 = fig1.add_subplot(2, n_evo_times, i+n_evo_times+1)
    ax2.set_title("Final amps T={}".format(evo_times[i]))
    ax2.set_xlabel("Time")
    #Optimised amps
    if i == 0:
        ax2.set_ylabel("Control amplitude")
    for j in range(n_ctrls):
        ax2.step(results[i].time,
             np.hstack((results[i].final_amps[:, j],
                        results[i].final_amps[-1, j])),
                 where='post')

plt.tight_layout()
plt.show()
```

### Versions

```python
pulse_seq = results[0].final_amps
pulse_seq
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    array([[ 0.46738098, -0.63624992, -0.7       , -0.63358358],
           [ 0.61517761, -0.40185939, -0.7       , -0.68343596],
           [ 0.69388629, -0.69994712, -0.7       , -0.68460209],
           ...,
           [-0.22327775,  0.7       ,  0.7       ,  0.68049038],
           [-0.16605512,  0.7       ,  0.69387955,  0.7       ],
           [-0.14378253,  0.7       ,  0.69300973,  0.6789739 ]])

### Run QuTip pulse simulation

```python
processor = Processor(N=1, dims=[3 ** len(subsystem_list)])

dt = backend.configuration().dt * 1e9

# H_d = wq0 * (1 - sigmaz()) / 2
tlist = np.array([dt * i for i in range(len(pulse_seq) + 1)])
for i,control in enumerate(H_c):
    processor.add_control(control, targets=0)
    coef = [a[i] for a in pulse_seq]

    processor.pulses[i].coeff =  coef
    processor.pulses[i].tlist = tlist

# processor.pulses[0].coeff = seq_x
# processor.pulses[0].tlist = np.array([dt * i for i in range(len(seq_x)+1)])
# processor.add_control(H_c[0], targets=[0], label='sigmax')
# processor.add_control(H_d, targets=[0], label="drift")
processor.add_drift(H_d, targets=[0])

basis0 = basis(3 ** len(subsystem_list), 0)
# basis0 = basis(4,0) + basis(4,2)
result = processor.run_state(init_state=basis0)

result.states[-1].tidyup(1.e-3)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[9], [1]], shape = (9, 1), type = ket\begin{equation*}\left(\begin{array}{*{11}c}(-0.346+0.444j)\\(-0.657-0.497j)\\-0.019\\(-0.006-0.022j)\\(0.022-0.019j)\\(0.033+0.011j)\\(-0.010-0.006j)\\(-0.013+0.009j)\\(-0.019-0.004j)\\\end{array}\right)\end{equation*}

### Create Channels and convert qoc pulses to Qiskit pulses

```python
q1=0
q2=1

drive_chan1 = pulse.DriveChannel(q1)
meas_chan1 = pulse.MeasureChannel(q1)
acq_chan1 = pulse.AcquireChannel(q1)
con_chan1 = pulse.ControlChannel(q1)

drive_chan2 = pulse.DriveChannel(q2)
meas_chan2 = pulse.MeasureChannel(q2)
acq_chan2 = pulse.AcquireChannel(q2)
con_chan2 = pulse.ControlChannel(q2)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
pulse_seqs = []
for i,control in enumerate(H_c):
    coef = [a[i] for a in pulse_seq]
    pulse_seqs.append(coef)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
pulse_seq
D0 = []
D1 = []
for a in pulse_seq:
    # amp1 = derotate(a[0])
    # amp2 = derotate(a[1])
    D0.append(complex(a[0], a[1]))
    # D0.append(amp1, amp2)
    D1.append(complex(a[2], a[3]))

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
schedule = pulse.Schedule(name='Frequency sweep')

# schedule += init_x

# later = schedule.duration

# schedule += def_cx << later
schedule += Play(SamplePulse(D0),drive_chan1) #<< schedule.duration
# schedule += Play(SamplePulse(D1),drive_chan2) #<< schedule.duration

from qiskit.pulse.macros import measure_all
schedule += measure_all(backend) << schedule.duration
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    /Users/benrosand/oct-qiskit-pulse/qiskit-terra/qiskit/pulse/library/sample_pulse.py:40: DeprecationWarning: SamplePulse has been renamed to Waveform and is deprecated. Please replace SamplePulse(samples, channel) with Waveform(samples, channel).
      warnings.warn("SamplePulse has been renamed to Waveform and is deprecated. " +

### Run the assembled pulse schedule

```python
from qiskit import assemble

num_shots_per_frequency = 1024
frequency_sweep_program = assemble(schedule,
                                   backend=backend,
                                   meas_level=2,
                                   meas_return='single',
                                #    qubit_lo_freq=backend.defaults().qubit_freq_est,
                                   shots=num_shots_per_frequency,
                                   )
                                #    schedule_los=schedule_frequencies)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
job = backend.run(frequency_sweep_program)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
from qiskit.tools.monitor import job_monitor
job_monitor(job)

# job.result().get_counts()
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    Job Status: job has successfully run

```python
from src.helper import qubit_distribution
qubit_distribution(job.result().get_counts())
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    {0: {0: 1011, 1: 13},
     1: {0: 1020, 1: 4},
     2: {0: 1019, 1: 5},
     3: {0: 957, 1: 67},
     4: {0: 307, 1: 717}}

```python
from qiskit import QuantumCircuit
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
circ = QuantumCircuit(2)
circ.cnot(0,1)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python

transp_circ = qiskit.transpile(circ, backend=backend)
transp_circ.data[0][0]
```

```python
processor = Processor(N=1, dims=[9])

dt = backend.configuration().dt * 1e9

# H_d = wq0 * (1 - sigmaz()) / 2
tlist = np.array([dt * i for i in range(len(def_seq[0]) + 1)])
for i,control in enumerate(H_c):
    processor.add_control(control, targets=0)
    # coef = [a[i] for a in pulse_seq]

    processor.pulses[i].coeff = def_seq[i]
    processor.pulses[i].tlist = tlist

# processor.pulses[0].coeff = seq_x
# processor.pulses[0].tlist = np.array([dt * i for i in range(len(seq_x)+1)])
# processor.add_control(H_c[0], targets=[0], label='sigmax')
# processor.add_control(H_d, targets=[0], label="drift")
processor.add_drift(H_d, targets=[0])

basis0 = basis(9, 0)
# basis0 = basis(4,0) + basis(4,2)
result = processor.run_state(init_state=basis0)

result.states[-1].tidyup(1.e-3)
```