#Libraries needed to implement and simulate quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile, qasm2, qasm3
from qiskit_aer import Aer
from qiskit.primitives import BackendSampler
#Custem functions to simplify answers
import Our_Qiskit_Functions as oq #a part of the library presented in arXiv:1903.04359v1.

import numpy as np
import math as m
import scipy as sci

#Initialize backends simulators to visualize circuits
S_simulator = Aer.backends(name='statevector_simulator')[0]
M_simulator = Aer.backends(name='qasm_simulator')[0]

q = QuantumRegister(4,name='q')
H_qc = QuantumCircuit(q,name='qc')

H_qc.x(q[0])
H_qc.id(q[1])
H_qc.x(q[2])

print('_____Initial State_________')
oq.Wavefunction(H_qc)

H_qc.h(q[0])
H_qc.x(q[1])
H_qc.y(q[2])
H_qc.z(q[3])

print('\n_____Operator H + X + Y + Z_________')
oq.Wavefunction(H_qc)

H_qc.h(q[0])
H_qc.x(q[1])
H_qc.y(q[2])
H_qc.z(q[3])

print('\n_____Operator H + X + Y + Z_________')
oq.Wavefunction(H_qc);

q = QuantumRegister(1,name='q')
XZ_qc = QuantumCircuit(q,name='qc')

XZ_qc.id(q[0])

print('_____Initial State_________')
oq.Wavefunction(XZ_qc)

XZ_qc.x(q[0])
XZ_qc.z(q[0])

print('\n_____Operator XZ_________')
oq.Wavefunction(XZ_qc)

XZ_qc.x(q[0])
XZ_qc.z(q[0])

print('\n_____Operator XZ_________')
oq.Wavefunction(XZ_qc);

q = QuantumRegister(1,name='q')
XZ_qc = QuantumCircuit(q,name='qc')

XZ_qc.id(q[0])

print('_____Initial State_________')
oq.Wavefunction(XZ_qc)

XZ_qc.x(q[0])
XZ_qc.z(q[0])

print('\n_____Operator XZ_________')
oq.Wavefunction(XZ_qc)

XZ_qc.z(q[0])
XZ_qc.x(q[0])

print('\n_____Operator ZX_________')
oq.Wavefunction(XZ_qc);

q = QuantumRegister(2,name='q')
F_qc = QuantumCircuit(q,name='qc')

F_qc.x(q[0])
F_qc.h(q[0])
F_qc.x(q[1])
F_qc.h(q[1])

print('_____Initial State_________')
oq.Wavefunction(F_qc)

F_qc.h(q[0])
F_qc.cp(m.pi/2,q[1],q[0])
F_qc.h(q[1])

print('\n_____After QFT_________')
oq.Wavefunction(F_qc);


q = QuantumRegister(3,name='q')
F_qc = QuantumCircuit(q,name='qc')

F_qc.x(q[2])

print('_____Initial State_________')
oq.Wavefunction(F_qc)

#---------------------------- qubit 0
F_qc.h(q[0])
F_qc.cp(m.pi/2,q[1],q[0])
F_qc.cp(m.pi/4,q[2],q[0])
#---------------------------- qubit 1
F_qc.h(q[1])
F_qc.cp(m.pi/4,q[2],q[1])
#---------------------------- qubit 2
F_qc.h(q[2])


print('\n_____After QFT_________')
oq.Wavefunction(F_qc);


q = QuantumRegister(2,name='q')
F_qc = QuantumCircuit(q,name='qc')

F_qc.x(q[0])
F_qc.h(q[0])
F_qc.x(q[1])
F_qc.h(q[1])

print('_____Initial State_________')
oq.Wavefunction(F_qc)

oq.QFT(F_qc,q,2)

print('\n_____First QFT_________')
oq.Wavefunction(F_qc)

oq.QFT(F_qc,q,2)

print('\n_____Second QFT_________')
oq.Wavefunction(F_qc);


q = QuantumRegister(2,name='q')
F_qc = QuantumCircuit(q,name='qc')

F_qc.x(q[0])
F_qc.h(q[0])
F_qc.x(q[1])
F_qc.h(q[1])

print('_____Initial State_________')
oq.Wavefunction(F_qc)
print(' ')

F_qc.h(q[0])
F_qc.cp(m.pi/2,q[1],q[0])
F_qc.h(q[1])

print('_____QFT_________')
oq.Wavefunction(F_qc)
print(' ')

F_qc.h(q[1])
F_qc.cp(-m.pi/2,q[1],q[0])
F_qc.h(q[0])

print('_____Inverse QFT_________')
oq.Wavefunction(F_qc);

q = QuantumRegister(2,name='q')
F_qc = QuantumCircuit(q,name='qc')

F_qc.x(q[0])
F_qc.h(q[0])
F_qc.x(q[1])
F_qc.h(q[1])

print('_____Initial State_________')
oq.Wavefunction(F_qc)

oq.QFT(F_qc,q,2)

print('\n_____QFT_________')
oq.Wavefunction(F_qc)

oq.QFT_dgr(F_qc,q,2)

print('\n_____Inverse QFT_________')
oq.Wavefunction(F_qc);

X = [0,1/m.sqrt(8),0,0,0,0,0,0]
FX = oq.DFT(X, inverse = True)

print('______DFT\u2020_________')
for i in range(len(FX)):
    print ('State: ',oq.Binary(i,2**3), '   Amplitude: ', FX[i])

#===================================================================

q = QuantumRegister(3, name='q')
qc = QuantumCircuit(q, name='qc')
qc.x(q[2])
oq.QFT(qc,q,3)
print('\n_____QFT________')
oq.Wavefunction(qc);

X = [0,1/m.sqrt(8),0,0,0,0,0,0]
FX = oq.DFT(X, inverse = True)

print('______DFT\u2020_________')
for i in range(len(FX)):
    print ('State: ',oq.Binary(i,2**3), '   Amplitude: ', FX[i])

#===================================================================

q = QuantumRegister(3, name='q')
qc = QuantumCircuit(q, name='qc')
qc.x(q[2])
oq.QFT(qc,q,3)
qc.swap(q[0],q[2])
print('\n_____QFT________')
oq.Wavefunction(qc);

q = QuantumRegister(2, name='q')
anc = QuantumRegister(1, name='anc')
FG_qc = QuantumCircuit(q, anc, name='qc')
marked = [1,0]

FG_qc.x(anc[0])

print('marked state: ',marked)
print(' ')

oq.QFT(FG_qc,q,2)

print('_____Initial State (QFT)_________')
oq.Wavefunction(FG_qc, systems=[2,1], show_systems=[True,False])
print(' ')

oq.X_Transformation(FG_qc, q, marked)
FG_qc.h(anc[0])
FG_qc.ccx(q[0],q[1],anc[0])
oq.X_Transformation(FG_qc,q,marked)

FG_qc.h(anc[0])
print('__________Flip the marked state_____________')
oq.Wavefunction(FG_qc, systems=[2,1], show_systems=[True,False])
print(' ')

oq.QFT(FG_qc,q,2)

print('_____QFT_________')
oq.Wavefunction(FG_qc, systems=[2,1], show_systems=[True,False])
print(' ')
FG_qc.h(anc[0])

oq.X_Transformation(FG_qc, q, [0,0])
FG_qc.ccx(q[0],q[1],anc[0])
FG_qc.h(anc[0])
oq.X_Transformation(FG_qc,q,[0,0])

print('__________Flip the |00> state_____________')
oq.Wavefunction(FG_qc, systems=[2,1], show_systems=[True,False])
print(' ')

oq.QFT_dgr(FG_qc,q,2)

print('_____QFT_dgr_________')
oq.Wavefunction(FG_qc, systems=[2,1], show_systems=[True,False]);

X = [0,1/m.sqrt(8),0,0,0,0,0,0]
FX = oq.DFT(X, inverse = True)

print('______DFT\u2020_________')
for i in range(len(FX)):
    print ('State: ',oq.Binary(i,2**3), '   Amplitude: ', FX[i])

#===================================================================

q = QuantumRegister(3, name='q')
qc = QuantumCircuit(q, name='qc')
qc.x(q[2])
oq.QFT(qc,q,3,swap=True)
print('\n_____QFT________')
oq.Wavefunction(qc);

qc.draw(output='mpl')

