#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

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

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

qc.id(q[0])
qc.h(q[1])
qc.z(q[1])

print('_______statevector______________')
print(oq.execute(qc, S_simulator).result().get_statevector())

print('\n__________wavefunction____________')
oq.Wavefunction(qc);

oq.Wavefunction(qc, precision = 8);

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

qc.h(q[0])
qc.h(q[1])
qc.z(q[1])
qc.rx(m.pi/3,q[1])
qc.h(q[2])
qc.ry(m.pi/5,q[2])
qc.h(q[3])

oq.Wavefunction(qc)

print('\n__________column____________')
oq.Wavefunction(qc, column = True);

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

qc.h(q[0])
qc.id(q[1])
qc.h(q[2])

oq.Wavefunction(qc, systems=[2,1]);


q = QuantumRegister(6, name='q')
qc = QuantumCircuit(q, name = 'qc')

qc.h(q[0])
qc.id(q[1])
qc.h(q[2])

oq.Wavefunction(qc, systems=[2,1,3]);

print('\n________show_systems______')
oq.Wavefunction(qc, systems=[2,1,3], show_systems=[True,True,False]);


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

qc.h(q[0])
qc.id(q[1])
qc.x(q[2])
qc.h(q[3])

oq.Wavefunction(qc, systems=[3,1]);

print('\n________show_systems______')
oq.Wavefunction(qc, systems=[3,1], show_systems=[True,False]);

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

qc.h(q[0])
qc.id(q[1])
qc.x(q[2])
qc.measure(q,c)

oq.Measurement(qc);

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

qc.h(q[0])
qc.id(q[1])
qc.x(q[2])
qc.measure(q,c)

oq.Measurement(qc, shots = 100);

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

qc.h(q[0])
qc.id(q[1])
qc.x(q[2])
qc.measure(q,c)

oq.Measurement(qc, print_M=False);

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

qc.h(q[0])
qc.id(q[1])
qc.x(q[2])
qc.measure(q,c)

M = oq.Measurement(qc, shots = 20, print_M=False, return_M = True)
print(M)

q = QuantumRegister(7, name='q')
qc = QuantumCircuit(q, name = 'qc')

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])
qc.h(q[3])
qc.id(q[4])
qc.id(q[5])
qc.id(q[6])

print('___________Initial state____________')
oq.Wavefunction(qc, systems=[4,3], column=True)

qc.ccx(q[0],q[1],q[4])
qc.ccx(q[2],q[3],q[5])
qc.ccx(q[4],q[5],q[6])
qc.cz(q[6],q[0])

print('\n____After CCCZ_______')
oq.Wavefunction(qc, systems=[4,3], column=True);

q = QuantumRegister(7, name='q')
qc = QuantumCircuit(q, name = 'qc')

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])
qc.h(q[3])
qc.id(q[4])
qc.id(q[5])
qc.id(q[6])

print('___________Initial state____________')
oq.Wavefunction(qc, systems=[4,3])

qc.ccx(q[0],q[1],q[4])
qc.ccx(q[2],q[3],q[5])
qc.ccx(q[4],q[5],q[6])
qc.cz(q[6],q[0])

print('\n____After CCCZ_______')
oq.Wavefunction(qc, systems=[4,3])

qc.ccx(q[4],q[5],q[6])
qc.ccx(q[2],q[3],q[5])
qc.ccx(q[0],q[1],q[4])

print('\n____Reverse all CCNOTs______')
oq.Wavefunction(qc, systems=[4,3], column=True);

q = QuantumRegister(5, name='q')
qc = QuantumCircuit(q, name = 'qc')

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])
qc.id(q[3])
qc.id(q[4])

print('______Initial state__________')
oq.Wavefunction(qc,systems=[3,1,1],show_systems=[True,True,False])

oq.n_NOT(qc, [q[0], q[1], q[2]], q[3], [q[4]])

print('\n___________________n_NOT___________________')
oq.Wavefunction(qc,systems=[3,1,1],show_systems=[True,True,False]);

q = QuantumRegister(6, name='q')
qc = QuantumCircuit(q, name = 'qc')

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])

qc.x(q[3])

qc.id(q[4])
qc.id(q[5])

print('______Initial state__________')
oq.Wavefunction(qc,systems=[3,1,2],show_systems=[True,True,False])

oq.n_Control_U(qc, [q[0], q[1], q[2]], [q[4],q[5]], [('Z',q[3]),('X',q[3])])

print('\n___________________n_Control_U___________________')
oq.Wavefunction(qc,systems=[3,1,2],show_systems=[True,True,False]);

q = QuantumRegister(3, name='q')
tgt = QuantumRegister(1,name='t')
anc = QuantumRegister(2, name = 'a')
qc = QuantumCircuit(q, tgt, anc, name = 'qc')

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])

qc.x(tgt[0])

qc.id(anc[0])
qc.id(anc[1])

print('______Initial state__________')
oq.Wavefunction(qc,systems=[3,1,2],show_systems=[True,True,False])

oq.n_Control_U(qc, q, anc, [('Z',tgt[0]),('X',tgt[0])])

print('\n___________________n_Control_U___________________')
oq.Wavefunction(qc,systems=[3,1,2],show_systems=[True,True,False]);

