!pip install pennylane

!pip install qiskit

import pennylane as qml
from pennylane import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

device=qml.device("default.qubit",wires=2)
@qml.qnode(device,interface='tf')
def create_circuit(inputs):
  qml.RX(inputs[0],wires=0)
  qml.RY(inputs[1],wires=1)
  qml.CNOT(wires=[0, 1])
  return qml.expval(qml.PauliZ(0))
  # return qml.expval(qml.PauliZ(0)@qml.PauliZ(1))
@tf.function
def cost_function(x,step):
  val=-(-1)**(step//100)
  #delta=tf.abs(tf.square(create_circuit(x)-val))
  delta= tf.reduce_mean(tf.square(create_circuit(x)-val))
  return delta

def initialize_tensor(angle_1,angle_2):
  tensors=[]
  phi=tf.Variable(angle_1)
  theta=tf.Variable(angle_2)
  tensors.append(phi)
  tensors.append(theta)
  return tensors

def optimize_circuit(tensors,steps):
  g0,g1,g2,g3=[],[],[],[]
  opt = tf.keras.optimizers.SGD(learning_rate=0.1)
  for i in range(steps):
    with tf.GradientTape() as tape:
      loss=cost_function(tensors,i)
    grads=tape.gradient(loss,tensors)
    opt.apply_gradients(zip(grads, tensors))
    
    g0.append(grads[0].numpy()[0])
    g1.append(grads[0].numpy()[1])
    g2.append(grads[1].numpy()[0])
    g3.append(grads[1].numpy()[1])
    if (i+1)%5==0:
      print(f"Gradients after {i} iterations:")
      print(f"Gradient 1:{grads[0]} ,Gradient 2:{grads[1]} For the provided input tensor")
  plt.plot(g0)
  #plt.plot(g1)
  plt.plot(g2)
  #plt.plot(g3)
  plt.show()
  return g0,g1,g2,g3    
    
phi=[0.05,0.87]
theta=[0.43,0.034]
tf_tensors=initialize_tensor(phi,theta)
conj_angles=[phi,theta]
#create_circuit(conj_angles)
steps=200
g0,g1,g2,g3=optimize_circuit(tf_tensors,steps)




from qiskit.visualization import plot_bloch_vector
%matplotlib inline
final_anglex=g0[-1]
final_angley=g1[-1]
final_anglex1=g2[-1]
final_angley1=g3[-1]

rot_1=[final_anglex,final_angley,0]
rot_2=[final_anglex1,final_angley1,0]
plot_bloch_vector([rot_1], title="Bloch Sphere")
