from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import IBMQ, Aer, execute

IBMQ.load_account()

q = QuantumRegister(3)
qc = QuantumCircuit(q)

#cutedge checker
def ccheck(a, b, c): 
    qc.cx(q[a], q[c]) 
    qc.cx(q[b], q[c])

qc.h(q[0])
qc.h(q[1])
ccheck(0,1,2)
qc.draw(output='mpl')

q = QuantumRegister(8)
qc = QuantumCircuit(q)

#half adder
#inputs: a,b 
#outputs: the sum output s and carry output c
def hadder(a,b,s,c):
    #XOR
    qc.cx(q[b], q[s])
    qc.cx(q[a], q[s])
    #AND
    qc.ccx(q[a], q[b], q[c])

qc.h(q[0:3])
ccheck(0,1,3)
ccheck(1,2,4)
hadder(3,4,6,5)

qc.draw(output='mpl')

#create an oracle
q = QuantumRegister(8)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)

qc.h(q[0:3])
ccheck(0,1,3)
ccheck(1,2,4)
hadder(3,4,6,5)
qc.cx(q[5],q[7])
qc.measure(q[7], c[0])

backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=100000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')

q = QuantumRegister(8)
c = ClassicalRegister(7)
qc = QuantumCircuit(q,c)

#inverse operations
def iccheck(a, b, c): 
    qc.cx(q[b], q[c])
    qc.cx(q[a], q[c]) 
def ihadder(a,b,s,c):
    qc.ccx(q[a], q[b], q[c])
    qc.cx(q[a], q[s])
    qc.cx(q[b], q[s])

qc.h(q[0:3])
qc.x(q[7])
qc.h(q[7])

ccheck(0,1,3)
ccheck(1,2,4)
hadder(3,4,6,5)
qc.cx(q[5],q[7])
ihadder(3,4,6,5)
iccheck(1,2,4)
iccheck(0,1,3)
qc.barrier()
qc.measure(q[0:7], c[0:7])

backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=1000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')

q = QuantumRegister(8)
c = ClassicalRegister(3)
qc = QuantumCircuit(q,c)

#diffusion operations
def diffusion(a,b,c):
    qc.h(q[a])
    qc.h(q[b])
    qc.h(q[c])    
    qc.x(q[a])
    qc.x(q[b])
    qc.x(q[c])
    qc.h(q[c])  
    qc.ccx(q[a],q[b], q[c])
    qc.h(q[c])  
    qc.x(q[a])
    qc.x(q[b])
    qc.x(q[c])
    qc.h(q[a])
    qc.h(q[b])
    qc.h(q[c])

#initialization
qc.h(q[0:3])
qc.x(q[7])
qc.h(q[7])
qc.barrier()

#oracle part
ccheck(0,1,3)
ccheck(1,2,4)
hadder(3,4,6,5)
qc.cx(q[5],q[7])
ihadder(3,4,6,5)
iccheck(1,2,4)
iccheck(0,1,3)
qc.barrier()

#diffusion part
diffusion(0,1,2)
qc.barrier()
qc.measure(q[0:3], c[0:3])

backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=10000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')

#from qiskit.aqua.circuits.gates import mct
q = QuantumRegister(11)
c = ClassicalRegister(4)
qc = QuantumCircuit(q,c)
ite = 2 #number of iteration

#initialization
qc.h(q[0:4])
qc.x(q[8])
qc.h(q[8])
qc.barrier()

for i in range(ite):
#oracle part
    ccheck(0,1,4)
    ccheck(0,2,5)
    ccheck(0,3,6) 
    qc.mct([q[4],q[5],q[6]], q[8] , [q[7]], mode='basic') 
    iccheck(0,3,6)
    iccheck(0,2,5)
    iccheck(0,1,4)
    qc.barrier()
#diffusion part
    qc.h(q[0:4])
    qc.barrier()
    qc.x(q[0:4])
    qc.barrier()
    qc.h(q[3])
    qc.mct([q[0],q[1],q[2]], q[3] , [q[7]], mode='basic')
    qc.h(q[3])
    qc.barrier()
    qc.x(q[0:4])
    qc.barrier()
    qc.h(q[0:4])
    qc.barrier()

qc.measure(q[0:4], c[0:4])

backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=10000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')

# the measurement results for each quantum state will be shown in reverse order. (i.e. 1000 -> 0001, 0111-> 1110)

#Decompose the circuit by using the Unroller
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import Unroller
pass_ = Unroller(['u3', 'cx'])
pm = PassManager(pass_)
new_circuit = pm.run(qc) 
new_circuit.draw(output='mpl')

#show elementary gate counts
new_circuit.count_ops()

#create output text file with the gate counts
import json
dct = new_circuit.count_ops()
with open('wk3_output.txt', 'w') as f:
    f.write(json.dumps(dct))