# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     name: python3
# ---

# + [markdown] id="_IbXAq46NDHo"
# # Quantum Kernel Methods for IRIS dataset classification with [TorchQuantum](https://github.com/mit-han-lab/torchquantum).
# <p align="left">
# <img src="https://github.com/mit-han-lab/torchquantum/blob/master/torchquantum_logo.jpg?raw=true" alt="torchquantum Logo" width="250">
# </p>
#
# Tutorial Author: <NAME>, <NAME>
#

# + [markdown] id="01AwyLuHUFbX"
# ###Outline
# 1. Introduction to Quantum Kernel Methods.
# 2. Build and train an SVM using Quantum Kernel Methods.
#
# In this tutorial, we use `tq.op_name_dict`, `tq.functional.func_name_dict` and `tq.QuantumDevice` from TorchQuantum.
#
# You can learn how to build a Quantum kernel function and train an SVM with the quantum kernel from this tutorial.
#

# + [markdown] id="slU3ib_mURDX"
# ##Introduction to Quantum Kernel Methods.
#

# + [markdown] id="qJv0wED75YTq"
# ###Kernel Methods
# Kernels or kernel methods (also called Kernel functions) are sets of different types of algorithms that are being used for pattern analysis. They are used to solve a non-linear problem by a linear classifier. Kernels Methods are employed in SVM (Support Vector Machines) which are often used in classification and regression problems. The SVM uses what is called a “Kernel Trick” where the data is transformed and an optimal boundary is found for the possible outputs.
#
#
# ####Quantum Kernel
# Quantum circuit can transfer the data to a high dimension Hilbert space which is hard to simulate on classical computer. Using kernel methods based on this Hilbert space can achieve unexpected performance.

# + [markdown] id="d4zraAesQ2vr"
# ###How to evaluate the distance in Hilbert space?
# Assume S(x) is the unitary that transfer data x to the state in Hilbert space. To evaluate the inner product between S(x) and S(y), we add a Transpose Conjugation of S(y) behind S(x) and measure the probability that the state falls on $|00\cdots0\rangle$

# + [markdown] id="ECUDI-rESEkj"
#
# <div align="center">
# <img src="https://github.com/mit-han-lab/torchquantum/blob/master/figs/kernel.png?raw=true" alt="conv-full-layer" width="600">
# </div>

# + [markdown] id="tOv-XfNjW5XH"
# ##Build and train an SVM using Quantum Kernel Methods.

# + [markdown] id="pfwd2SNaOA4z"
# ###Installation

# + colab={"base_uri": "https://localhost:8080/"} id="azb47tvSiaBp" outputId="d21454a9-cb10-457e-cf24-d52b00c00077"
# !pip install qiskit==0.32.1

# + [markdown] id="F2-0UpluOIQl"
# Download and cd to the repo.

# + colab={"base_uri": "https://localhost:8080/"} id="6sNqLl9tjjAf" outputId="90867afc-49c0-4668-d07e-889f5f8ba3f3"
# !git clone https://github.com/mit-han-lab/pytorch-quantum.git

# + colab={"base_uri": "https://localhost:8080/"} id="0c2MCqFXjxkD" outputId="44c664ed-a0fe-42a1-ee78-8412111d3d30"
# %cd pytorch-quantum

# + [markdown] id="PO1xLbaOOxWk"
# Install torch-quantum.

# + colab={"base_uri": "https://localhost:8080/"} id="3c6nfq3KkVXG" outputId="80a4f321-ebb0-4923-8dab-a05ef1719fb9"
# !pip install --editable .

# + [markdown] id="18tqleq4O3cX"
# Change PYTHONPATH and install other packages.

# + colab={"base_uri": "https://localhost:8080/"} id="d7ZPp7FikrZz" outputId="6b66fd65-c75d-4ff6-94a7-6c17210c05e4"
# %env PYTHONPATH=.

# + [markdown] id="rhsBhV23PD4g"
# Run the following code to store a qiskit token. You can replace it with your own token from your IBMQ account if you like.
#
#

# + id="Iw9rQ6dcnrhe"
from qiskit import IBMQ
IBMQ.save_account('0238b0afc0dc515fe7987b02706791d1719cb89b68befedc125eded0607e6e9e9f26d3eed482f66fdc45fdfceca3aab2edb9519d96b39e9c78040194b86e7858', overwrite=True)

# + [markdown] id="-42mdL8CG5Vi"
# ###Import the module
# `SVC` is support vector classification. We use this module to call the support vector machine algorithm.
#
# `load_iris` is to load the famous iris dataset.
#
# `StandardScaler` is to help scale the data by removing the mean and scaling to unit variance.
#
# `train_test_split` is a tool to split the dataset.
#
# `accuracy_score` can check how many samples are correctly predicted and give us the accuracy.
#
# `func_name_dict` is a very important dict under `torchquantum.functional`. If we feed the name of the gates we want, like ‘rx’, ‘ry’, or ‘rzz’, the dict will give us a function. The function plays a central role in our quantum model. It performs the specified unitary operations on a specified quantum state on a specified wire. These three specified things are the three parameters we need to pass to it. You can see that later.
#

# + id="N5acspJ8G1n3"
import numpy as np
import torch

from sklearn.svm import SVC
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from torchquantum.functional import func_name_dict
import torchquantum as tq

# + [markdown] id="QiOV-xIGKXVK"
# ###Prepare dataset
# We use the front 100 samples of IRIS dataset.
#
# Since the phase in quantum gates is 2π-periodic, it is necessary to scale the data in a range from -π to π.
#
# And we change the label from 0 and 1 to -1 and 1.
#
# Split the dataset on a 3-to-1 ratio.
#

# + id="iBnWI5yqKfMB"
X, y = load_iris(return_X_y=True)

X = X[:100]
y = y[:100]

scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)
y_scaled = 2 * (y - 0.5)

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y_scaled)


# + [markdown] id="KpVfJUezK7ny"
# ### Build the Ansatz, consist of a unitary and its transpose conjugation.
# When initializing the `KernelAnsatz`, we only need to pass a `func_list` and the KernelAnsatz will record the `func_list`. Each entry in the function is a dict, containing 'input_idx', 'func', and 'wires'.
#
# When executing the `KernelAnsatz`, three parameters are passed from outside, `q_device`, `x`, and `y`. `q_device` stores the state vector. We reset the state vector to the $|00\cdots0⟩$. And if you didn’t forget the figure, we will act the S(x) and the transpose conjugation of S(y) to the `q_device`.
#
# Here the gates in the `func_list` with data `x` form the unitary S(x). S(y)'s transpose conjugation is S(y)’s inverse matrix. From the perspective of inverse, we can build the S(y)'s transpose conjugation by inverting the function list with data `y`. So, how to invert a list of gates executed from head to tail? You only need to counteract the list of gates from tail to head one by one. So here, we simply reverse the sequence of function list and flip the phase from positive to negative or from negative to positive.
#
# And in each iteration is to act the unitary gate on the quantum state. We look up the `func_name_dict` with the function name. Here the function name is the gate name, like ‘ry’, ‘rz’ and so so. The dict returns a function. We pass the three parameters to the function and the function will act the gate on the state vector(`self.q_device`), on the `wires`, with the phase, here the `params` mean phase.
#

# + id="UbhzYiGcK9xk"
class KernalAnsatz(tq.QuantumModule):
    def __init__(self, func_list):
        super().__init__()
        self.func_list = func_list
    
    @tq.static_support
    def forward(self, q_device: tq.QuantumDevice, x, y):
        self.q_device = q_device
        self.q_device.reset_states(x.shape[0])
        for info in self.func_list:
            if tq.op_name_dict[info['func']].num_params > 0:
                params = x[:, info['input_idx']]
            else:
                params = None
            func_name_dict[info['func']](
                self.q_device,
                wires=info['wires'],
                params=params,
            )
        for info in reversed(self.func_list):
            if tq.op_name_dict[info['func']].num_params > 0:
                params = -y[:, info['input_idx']]
            else:
                params = None
            func_name_dict[info['func']](
                self.q_device,
                wires=info['wires'],
                params=params,
            )


# + [markdown] id="AXwEp5SILF_Z"
# ### Build the whole quantum circuit
#
# The whole model initialization is a 4-wire quantum state, the `tq.QuantumDevice` module can store the state vector and a `KernelAnsatz` we just mentioned.
#
# When executing the whole model, as there’s a concept of batch in torchquantum’s model, we set the batch size is 1. After executing the `KernelAnsatz`, we measure the probability that the quantum state falls on the $|00\cdots0\rangle$ as the result. We get the state vector, flatten it, get the first amplitude, which is also the amplitude of the $|00\cdots0\rangle$ state, calculate the absolute value of the amplitude, and get the probability that the quantum state falls on the $|00\cdots0\rangle$ state.
#

# + id="taGjJMVcLN2L"
class Kernel(tq.QuantumModule):
    def __init__(self):
        super().__init__()
        self.n_wires = 4
        self.q_device = tq.QuantumDevice(n_wires=self.n_wires)
        self.ansatz = KernalAnsatz(
        [   {'input_idx': [0], 'func': 'ry', 'wires': [0]},
            {'input_idx': [1], 'func': 'ry', 'wires': [1]},
            {'input_idx': [2], 'func': 'ry', 'wires': [2]},
            {'input_idx': [3], 'func': 'ry', 'wires': [3]},])

    def forward(self, x, y, use_qiskit=False):
        # bsz=1
        x = x.reshape(1, -1)
        y = y.reshape(1, -1)
        self.ansatz(self.q_device, x, y)
        result = torch.abs(self.q_device.states.view(-1)[0])
        return result


# + [markdown] id="ckdg6zccLwF3"
# ###Train the svm model from sklearn based on our quantum kernel.
#
# Define a kernel matrix function.
#
# Pass the kernel matrix function to SVC, call `.fit(X_train, y_train)` and the SVC object can start training.
#
# Predict and see the accuracy. The accuracy looks pretty well.
#

# + colab={"base_uri": "https://localhost:8080/"} id="xS7zGXVDLwuY" outputId="78ef7a90-f2fe-4458-b911-2667209792cd"
kernel_function = Kernel()
def kernel_matrix(A, B):
    return np.array([[kernel_function(a, b) for b in B] for a in A])

svm = SVC(kernel=kernel_matrix).fit(X_train, y_train)
predictions = svm.predict(X_test)
print(accuracy_score(predictions, y_test))

StarcoderdataJupyterScriptsDedupFiltered