#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]

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

for i in range(N):
    qc.h(q[i])
oq.Wavefunction(qc);

N = 3
q = QuantumRegister(N, name='q')
c = ClassicalRegister(N, name = 'c')
qc = QuantumCircuit(q,c,name='qc')

for i in range(N):
    qc.h(q[i])

qc.measure(q,c)
oq.Measurement(qc,shots=1000)

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

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.cz(q[0],q[1])

print('___________Initial State____________')
oq.Wavefunction(G_qc)

print('\nX_Transformation: |00>  <----> |11>')
oq.X_Transformation(G_qc, q, [0,0])

print('\n__________ After X(0) + X(1) ______')
oq.Wavefunction(G_qc);

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

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.cz(q[0],q[1])
G_qc.x(q[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc)

oq.X_Transformation(G_qc, q, marked)

print('\n__________ X(0) ______')
oq.Wavefunction(G_qc)

oq.X_Transformation(G_qc,q, marked)

print('\n__________ X(0) ______')
oq.Wavefunction(G_qc);


q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
n_anc = QuantumRegister(1, name='nanc')
G_qc = QuantumCircuit(q, anc, n_anc,name='qc')
marked = [0,1,0]

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.h(q[2])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,False,False])

G_qc.h(anc[0])
oq.X_Transformation(G_qc,q,marked)
print('\n___________H(q[3]) + X_Transformation____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,True,False])

oq.n_NOT(G_qc,q, anc[0],n_anc)
print('\n___________n_NOT____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,True,False])

oq.X_Transformation(G_qc,q,marked)
G_qc.h(anc[0])

print('\n___________X_Transformation + H(q[3])____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,True,False]);


q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
n_anc = QuantumRegister(1, name='nanc')
G_qc = QuantumCircuit(q, anc, n_anc,name='qc')
marked = [0,1,0]

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.h(q[2])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,False,False])

oq.Grover_Oracle(marked, G_qc, q, anc, n_anc)

print('\n___________Final State____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,True,False]);

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

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

oq.Grover_Oracle(marked, G_qc, q, anc, n_anc)

print('\n___________Grover Oracle: ',marked,'____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])
oq.Grover_Oracle([0,0], G_qc, q, anc, n_anc)
G_qc.h(q[0])
G_qc.h(q[1])

print('\n___________After Grover Diffusion____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False]);

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

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

oq.Grover_Oracle(marked, G_qc, q, anc, n_anc)

print('\n___________Grover Oracle: ',marked,'____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])
print('\n___________H^2 transformation____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

oq.Grover_Oracle([0,0], G_qc, q, anc, n_anc)
print('\n___________Grover Oracle: [0, 0]____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])

print('\n___________H^2 transformation____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False]);

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

G_qc.h(q[0])
G_qc.id(q[1])
G_qc.x(anc[0])
print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])
oq.Grover_Oracle([0,0], G_qc, q, anc, n_anc)
G_qc.h(q[0])
G_qc.h(q[1])

print('\n___________After Grover____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False]);

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

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

oq.Grover_Oracle(marked, G_qc, q, anc, n_anc)

print('\n___________Grover Oracle: |01>____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])
print('\n___________H^2 transformation____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

oq.Grover_Oracle([0,1], G_qc, q, anc, n_anc)
oq.Grover_Oracle([1,0], G_qc, q, anc, n_anc)
oq.Grover_Oracle([1,1], G_qc, q, anc, n_anc)
print('\n___________Flipping the sign on: |01> |10> |11>____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False])

G_qc.h(q[0])
G_qc.h(q[1])

print('\n___________H^2 transformation____________')
oq.Wavefunction(G_qc, systems=[2,1], show_systems=[True,False]);

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
n_anc = QuantumRegister(1, name='nanc')
G_qc = QuantumCircuit(q, anc, n_anc,name='qc')
marked = [1,1,0]

G_qc.h(q[0])
G_qc.h(q[1])
G_qc.h(q[2])
G_qc.x(anc[0])

print('___________Initial State____________')
oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,False,False])

iterations = 3

for i in range(iterations):
    oq.Grover_Oracle(marked, G_qc, q, anc, n_anc)
    oq.Grover_Diffusion(marked, G_qc, q, anc, n_anc)
    print('\n___________',i+1,' Grover iteration____________')
    oq.Wavefunction(G_qc, systems=[3,1,1], show_systems=[True,False,False])

Q = 4
marked = [0,1,1,0]

G_qc, q, an1, an2, c = oq.Grover(Q, marked)

oq.Wavefunction(G_qc, systems=[Q,1,Q-2], show_systems=[True,False,False], column = True)
print(' ')
G_qc.measure(q,c)
print('\n_________Measurement Results_________')
oq.Measurement(G_qc, shots = 100);

