import numpy as np

# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile, Aer, IBMQ, QuantumRegister, ClassicalRegister
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from ibm_quantum_widgets import *
from qiskit.providers.aer import QasmSimulator
import qiskit
import copy
import matplotlib.pyplot as plt

# Loading your IBM Quantum account(s)
provider = IBMQ.load_account()

from sklearn import model_selection, datasets, svm

linnerrud = datasets.load_linnerrud()
X = linnerrud.data[0:100]
Y = linnerrud.target[0:100]
X_train, X_test, Y_train, Y_test = model_selection.train_test_split(X, Y, test_size = 0.33, random_state = 42)

print(Y_train)

print(X_train[0])

N = 4

def feature_map(X):
    q = QuantumRegister(N)
    c = ClassicalRegister(1)
    qc = QuantumCircuit(q,c)

    for i, x in enumerate(X_train[0]):
        qc.rx(x,i)
    
    return qc,c

def variational_circuit(qc, theta):
    for i in range(N-1):
        qc.cnot(i, i+1)
    qc.cnot(N-1, 0)
    for i in range(N):
        qc.ry(theta[i], i)
        
    return qc

def quantum_nn(X, theta, simulator = True):
    qc,c = feature_map(X)

    qc = variational_circuit(qc, theta)

    qc.measure(0,c)

    shots = 1E4
    backend = Aer.get_backend('qasm_simulator')
    
    if simulator == False:
        shots = 5000
        provider = IBMQ.load_account()
        backend = provider.get_backend('ibmq_athens')
        
    job = qiskit.execute(qc, backend, shots = shots)
    result = job.result()
    counts = result.get_counts(qc)
    print(counts)
    
    return(counts['1']/shots)

def loss(prediction, target):
    return (prediction-target)**2

def gradient(X, Y, theta):
    
    delta = 0.01
    grad = []
    for i in range(len(theta)):
        dtheta = copy.copy(theta)
        dtheta += delta
        
        pred_1 = quantum_nn(X, dtheta)
        pred_2 = quantum_nn(X, theta)
        
        grad.append((loss(pred_1, Y) - loss(pred_2, Y))/delta)
        
    return np.array(grad)

def accuracy(X, Y, theta):
    
    counter = 0
    for X_i, Y_i in zip(X, Y):
        prediction = quantum_nn(X_i, theta)
        
        if prediction < 0.5 and Y_i == 0:
            counter += 1
        if prediction >= 0.5 and Y_i == 1:
            counter += 1
            
        return counter/len(Y)

eta = 0.05

loss_list = []
theta = np.ones(N)

print('Epoch\t Loss\t Training Accuracy')

for i in range(20):
    loss_tmp = []
    
    for X_i, Y_i in zip(X_train, Y_train):
    
        prediction = quantum_nn(X_i, theta)
        loss_tmp.append(loss(prediction, Y_i))
        theta = theta - eta * gradient(X_i, Y_i, theta)
    
    loss_list.append(np.mean(loss_tmp))
    acc = accuracy(X_train, Y_train, theta)
    
    print(f'{i} \t {loss_list[-1]:0.3f} \t {acc:0.3f}')

plt.plot(loss_list)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show

accuracy(X_test, Y_test, theta)

clf = svm.SVC()
clf.fit(X_train, Y_train)

print(clf.predict(X_test))
print(Y_test)

quantum_nn(X_test[6], theta, simulator = False)

quantum_nn(X_test[6], theta)

Y_test[6]