!pip install pennylane

!pip install qiskit

import pennylane as qml
from pennylane.templates import AmplitudeEmbedding,BasisEmbedding,AngleEmbedding
import tensorflow as tf
from tensorflow import keras

device = qml.device('default.qubit', wires=2)

@qml.qnode(device)
def amplitude_circuit(inputs=None):
  AmplitudeEmbedding(features=inputs, wires=range(2),normalize=True,pad_with=0.)
  return qml.expval(qml.PauliZ(0))

@qml.qnode(device)
def basis_circuit(inputs=None):
  BasisEmbedding(features=inputs, wires=range(2))
  return qml.expval(qml.PauliZ(0))

@qml.qnode(device)
def angle_circuit(inputs=None):
  AngleEmbedding(features=inputs, wires=range(2))
  return qml.expval(qml.PauliZ(0))

inputs=[0.05,0.43]
z_a=amplitude_circuit(inputs)
z_an=angle_circuit(inputs)
print(f"Amplitude Embeddings: {z_a}")
print(f"Amplitude Embeddings: {z_an}")


from qiskit.visualization import plot_bloch_vector
%matplotlib inline
rot_1=[0,0,z_a]
rot_2=[0,0,z_an]
print("Bloch sphere for Amplitude Embedding")
plot_bloch_vector([rot_1], title="Bloch Sphere")


print("Bloch sphere for Angle Embedding")
plot_bloch_vector([rot_2], title="Bloch Sphere")

print("For Basis Embeddings, we need to encode n  binary features to a basis state of n qubits.")
input_basis=[1,0]
z_b=basis_circuit(input_basis)
print(z_b)
print("Bloch sphere for Basis Embedding")
plot_bloch_vector([0,0,z_b], title="Bloch Sphere")

import tensorflow as tf
from tensorflow import keras
import os
from datetime import datetime

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
assert x_train.shape == (60000, 28, 28)
assert x_test.shape == (10000, 28, 28)
assert y_train.shape == (60000,)
assert y_test.shape == (10000,)

%load_ext tensorboard
%tensorboard --logdir logs/scalars/

tensorboard_callback = keras.callbacks.TensorBoard(
    log_dir= "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S"),
    histogram_freq=0,
    write_graph=True,
    write_grads=True
    )
'''Cerate 2 qubit node circuit with angle embedding and entangled layers'''
n_qubits = 2
@qml.qnode(device)
def qnode(inputs, weights):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]

def create_CQC_model(x_train,y_train,x_test,y_test):
  '''Convert the q node circuit into a keras layer with a weight mapping'''
  n_layers = 6
  weight_shapes = {"weights": (n_layers, n_qubits)}
  qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
  '''Initial layers for flattening the Image inputs and compress the features to pass it through the 2-bit node '''
  clayer_0 = tf.keras.layers.Flatten(input_shape=(28,28))
  clayer_1=tf.keras.layers.Dense(2)
  '''Terminal layer with softmax activation for classification'''
  clayer_2 = tf.keras.layers.Dense(10, activation="softmax")
  model = tf.keras.models.Sequential([clayer_0,clayer_1,qlayer, clayer_2])

  opt = tf.keras.optimizers.SGD(learning_rate=0.2)
  model.compile(opt, loss="mae", metrics=["accuracy"])
  model.fit(x_train,y_train,batch_size=128,epochs=10,verbose=2,validation_data=(x_test,y_test),callbacks=[tensorboard_callback])
  model.summary()

'''Testing circuit on a small batch '''
x_train_n,y_train_n,x_test_n,y_test_n=x_train[:1000],y_train[:1000],x_test[:1000],y_test[:1000]
create_CQC_model(x_train_n,y_train_n,x_test_n,y_test_n)

import random
%load_ext tensorboard
%tensorboard --logdir logs/scalars/

tensorboard_callback = keras.callbacks.TensorBoard(
    log_dir= "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S"),
    histogram_freq=0,
    write_graph=True,
    write_grads=True
    )

n_qubits = 9
n_layer_steps = 3
n_layers_to_add = 2
batch_size = 128
epochs = 5

device = qml.device("default.qubit", wires=n_qubits)
def random_gates(n_qubits):
  gates = [qml.RX, qml.RY, qml.RZ]
  chosen_gates = []
  for i in range(n_qubits):
      chosen_gate = random.choice(gates)
      chosen_gates.append(chosen_gate)
  return chosen_gates

def apply_layer(gates, weights):
  for i in range(n_qubits): 
    gates[i](weights[i], wires = i)
  for i in range(n_qubits-1):
    qml.CZ(wires=[i, i+1])

def apply_frozen_layers(frozen_layer_gates, frozen_layer_weights):
  for i in range(len(frozen_layer_gates)):
    apply_layer(frozen_layer_gates[i], frozen_layer_weights[i])

@qml.qnode(device,interface='tf')
def qnode(inputs,weights):
  layer_gates = []
  layer_weights = []
  AngleEmbedding(inputs, wires=range(n_qubits))
  # Apply frozen layers
  new_gates = [random_gates(n_qubits) for i in range(n_layers_to_add)]
  apply_frozen_layers(layer_gates, layer_weights)
  # Apply layers with trainable parameters
  for i in range(n_layers_to_add):
      apply_layer(new_gates[i], weights[i])
        
  # Expectation value of the last qubit
  return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]

def create_CQC_frozen_model(x_train,y_train):
  '''Convert the q node circuit into a keras layer with a weight mapping'''
  # n_layers_to_add = 10
  # Define shape of the weights
  weight_shapes = {"weights": (n_layers_to_add, n_qubits)}
  qlayer = qml.qnn.KerasLayer(qnode, weight_shapes,output_dim=n_qubits)
  '''Initial layers for flattening the Image inputs and compress the features to pass it through the 2-bit node '''
  clayer_0 = tf.keras.layers.Flatten(input_shape=(28,28))
  clayer_1=tf.keras.layers.Dense(2)
  '''Terminal layer with softmax activation for classification'''
  clayer_2 = tf.keras.layers.Dense(2, activation="sigmoid")
  model = tf.keras.models.Sequential([clayer_0,clayer_1,qlayer, clayer_2])

  opt = tf.keras.optimizers.SGD(learning_rate=0.2)
  model.compile(opt, loss="mae", metrics=["accuracy"])
  print(model.summary())
  model.fit(x_train,y_train,batch_size=128,epochs=10,verbose=2,callbacks=[tensorboard_callback])
  model.summary()

'''Testing circuit on a small batch '''
#x_train_n,y_train_n,x_test_n,y_test_n=x_train[:1000],y_train[:1000],x_test[:1000],y_test[:1000]
create_CQC_frozen_model(modif_x_train,modif_y_train)

import numpy as np
import tensorflow as tf
from tensorflow import keras
import os
from datetime import datetime

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
assert x_train.shape == (60000, 28, 28)
assert x_test.shape == (10000, 28, 28)
assert y_train.shape == (60000,)
assert y_test.shape == (10000,)

modif_x_train=[]
modif_y_train=[]
for i in range(len(y_train)):
  if y_train[i]==4:
    modif_y_train.append(0)
    modif_x_train.append(x_train[i])
  elif y_train[i]==8:
    modif_y_train.append(0)
    modif_x_train.append(x_train[i])
modif_x_train,modif_y_train=np.asarray(modif_x_train),np.asarray(modif_y_train)
modif_x_train.shape,modif_y_train.shape,y_train.shape,x_train.shape