from IPython.display import Image, display
Image("ryoko.png", width="70")

from IPython.display import Image, display
Image('4lightsout_ex.png')

from qiskit import *
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import IBMQ, Aer, execute
provider = IBMQ.load_account()

address = QuantumRegister(2)
data = QuantumRegister(3)
c = ClassicalRegister(5)
qc = QuantumCircuit(address,data,c)

# address preparation
qc.h([address[0],address[1]])
qc.barrier()
# address 0 -> data = 1
qc.x([address[0],address[1]])
qc.ccx(address[0],address[1],data[2])
qc.x([address[0],address[1]])
qc.barrier()
# address 1 -> data = 2
qc.x(address[0])
qc.ccx(address[0],address[1],data[1])
qc.x(address[0])
qc.barrier()
# address 2 -> data = 5
qc.x(address[1])
qc.ccx(address[0],address[1],data[2])
qc.ccx(address[0],address[1],data[0])
qc.x(address[1])
qc.barrier()
# address 3 -> data = 7
qc.ccx(address[0],address[1],data[2])
qc.ccx(address[0],address[1],data[1])
qc.ccx(address[0],address[1],data[0])
qc.barrier()


#Check the qRAM　status
qc.measure(address[0:2], c[0:2])
qc.measure(data[0:3], c[2:5])
 
# Reverse the output string.
qc = qc.reverse_bits()

#backend = provider.get_backend('ibmq_qasm_simulator')
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend=backend, shots=8000, seed_simulator=12345, backend_options={"fusion_enable":True})
#job = execute(qc, backend=backend, shots=8192)
result = job.result()
count =result.get_counts()
print(count)

qc.draw(output='mpl')

Image('circuit_ex.png')

Image('gatesynthesis_ex.png')

Image('4lightsout_pr.png')

lightsout4=[[1, 1, 1, 0, 0, 0, 1, 0, 0],[1, 0, 1, 0, 0, 0, 1, 1, 0],[1, 0, 1, 1, 1, 1, 0, 0, 1],[1, 0, 0, 0, 0, 0, 1, 0, 0]]

import numpy as np

# import qiskit libraries
from qiskit import IBMQ, Aer,QuantumRegister, ClassicalRegister, QuantumCircuit, execute
from qiskit.tools.jupyter import *

provider = IBMQ.load_account()

#import functions to plot
from qiskit.visualization import plot_histogram

def week2b_ans_func(lightout4):
    ##### Build your cirucuit here
    def qRAM(qc, add, board, lightout4):
        # Board 1
        qc.x(add[0])
        qc.x(add[1])

        for i, curr_light in enumerate(lightout4[0]):
            if curr_light == 1:
                qc.ccx(add[0], add[1], board[i])
                
                #qc.ch(q[0], q[3])
                #qc.cz(q[2], q[3])
                #qc.ch(q[0], q[3])
                
                
        qc.x(add[0])
        qc.x(add[1])
        qc.barrier()

        # Board 2
        qc.x(add[0])

        for i, curr_light in enumerate(lightout4[1]):
            if curr_light == 1:
                qc.ccx(add[0], add[1], board[i])
        qc.x(add[0])        
        qc.barrier()

        # Board 3
        qc.x(add[1])

        for i, curr_light in enumerate(lightout4[2]):
            if curr_light == 1:
                qc.ccx(add[0], add[1], board[i])
        qc.x(add[1])        
        qc.barrier()

        # Board 4

        for i, curr_light in enumerate(lightout4[3]):
            if curr_light == 1:
                qc.ccx(add[0], add[1], board[i])

        qc.barrier()
        
    def diffuser(nqubits):

        qc_diff = QuantumCircuit(nqubits)

        for qubit in range(nqubits):
            qc_diff.h(qubit)

        for qubit in range(nqubits):
            qc_diff.x(qubit)

        qc_diff.h(nqubits-1)
        qc_diff.mct(list(range(nqubits-1)), nqubits-1)  # multi-controlled-toffoli
        qc_diff.h(nqubits-1)

        for qubit in range(nqubits):
            qc_diff.x(qubit)

        for qubit in range(nqubits):
            qc_diff.h(qubit)

        U_f0 = qc_diff.to_gate()
        U_f0.name = "V"

        return U_f0    

    
    def lights_oracle(sol, board, anc, orc_out_2a):

        #board_oracle = QuantumRegister(n, 'board')
        #sol = QuantumRegister(n, 'solution')

        #orc_out_2a = QuantumRegister(1, 'oracle_out_2a') #this has to be phase flipped

        qc_1 = QuantumCircuit(sol, board, anc, orc_out_2a)

        flip_matrix = [[1,1,0,1,0,0,0,0,0],
                       [1,1,1,0,1,0,0,0,0],
                       [0,1,1,0,0,1,0,0,0],
                       [1,0,0,1,1,0,1,0,0],
                       [0,1,0,1,1,1,0,1,0],
                       [0,0,1,0,1,1,0,0,1],
                       [0,0,0,1,0,0,1,1,0],
                       [0,0,0,0,1,0,1,1,1],
                       [0,0,0,0,0,1,0,1,1]]

        qc_1.barrier()           
        for sol_bit in range(len(flip_matrix)):
            for board_bit in range(len(flip_matrix[sol_bit])):
                if flip_matrix[sol_bit][board_bit] == 1:

                    qc_1.cx(sol[sol_bit], board[board_bit])

        qc_1.barrier()  
        for i in range(len(flip_matrix)):
            qc_1.x(board[i])

        qc_1.barrier()  
        
        qc_1.ccx(board[0], board[1], anc[0])
        qc_1.ccx(board[2], board[3], anc[1])
        qc_1.ccx(anc[0], anc[1], anc[2])

        qc_1.ccx(board[4], board[5], anc[3])
        qc_1.ccx(board[6], board[7], anc[4])
        qc_1.ccx(anc[3], anc[4], anc[5])

        qc_1.mct([board[8], anc[2], anc[5]], orc_out_2a, mode = 'noancilla')

        qc_1.ccx(anc[3], anc[4], anc[5])
        qc_1.ccx(board[6], board[7], anc[4])
        qc_1.ccx(board[4], board[5], anc[3])

        qc_1.ccx(anc[0], anc[1], anc[2])
        qc_1.ccx(board[2], board[3], anc[1])
        qc_1.ccx(board[0], board[1], anc[0])        
        

        #qc_1.mct([board[0], board[1],board[2],board[3],board[4],board[5],board[6],board[7],board[8]],
        #       orc_out_2a, 
               #[anc[0], anc[1],anc[2],anc[3],anc[4],anc[5],anc[6]], 
        #       mode='noancilla') 


        qc_1.barrier()  
        for i in range(len(flip_matrix)):
            qc_1.x(board[i])

        qc_1.barrier()  
        for sol_bit in range(len(flip_matrix)):
            for board_bit in range(len(flip_matrix[sol_bit])):
                if flip_matrix[sol_bit][board_bit] == 1:

                    qc_1.cx(sol[sol_bit], board[board_bit])


        return qc_1

    def new_u2a(qc, sol, board, anc, orc_out_2a):

        r_2a = 1

        flip_matrix = [[1,1,0,1,0,0,0,0,0],
                       [1,1,1,0,1,0,0,0,0],
                       [0,1,1,0,0,1,0,0,0],
                       [1,0,0,1,1,0,1,0,0],
                       [0,1,0,1,1,1,0,1,0],
                       [0,0,1,0,1,1,0,0,1],
                       [0,0,0,1,0,0,1,1,0],
                       [0,0,0,0,1,0,1,1,1],
                       [0,0,0,0,0,1,0,1,1]]

        n  =len(flip_matrix)

        for i_2a in range(r_2a):

            qc.extend(lights_oracle(sol, board, anc, orc_out_2a))
            qc.barrier()
            qc.append(diffuser(n), sol)
            qc.barrier()    
            
    def new_u2a_dagger(qc, sol, board, anc, orc_out_2a):

        r_2a = 1

        flip_matrix = [[1,1,0,1,0,0,0,0,0],
                       [1,1,1,0,1,0,0,0,0],
                       [0,1,1,0,0,1,0,0,0],
                       [1,0,0,1,1,0,1,0,0],
                       [0,1,0,1,1,1,0,1,0],
                       [0,0,1,0,1,1,0,0,1],
                       [0,0,0,1,0,0,1,1,0],
                       [0,0,0,0,1,0,1,1,1],
                       [0,0,0,0,0,1,0,1,1]]

        n  =len(flip_matrix)

        for i_2a in range(r_2a):

            qc.barrier()
            qc.append(diffuser(n), sol)
            qc.barrier() 

            orc_org =  lights_oracle(sol, board, anc, orc_out_2a)
            qc.extend(orc_org.inverse())

    def bitcounter(qc, sol, anc):

        for i in range(len(sol)):

            qc.mct([sol[i], anc[0], anc[1], anc[2]], anc[3], mode = 'noancilla')
            qc.mct([sol[i], anc[0], anc[1]], anc[2], mode = 'noancilla')
            qc.ccx(sol[i], anc[0], anc[1])
            qc.cx(sol[i], anc[0])

    def bitcounter_uncompute(qc, sol, anc):

        for i in range(len(sol)):
            qc.cx(sol[i], anc[0])
            qc.ccx(sol[i], anc[0], anc[1])
            qc.mct([sol[i], anc[0], anc[1]], anc[2], mode = 'noancilla')
            qc.mct([sol[i], anc[0], anc[1], anc[2]], anc[3], mode = 'noancilla')
        

    def oracle_2b(qc, add, board, sol, anc, orc_out_2a, orc_out_2b, lightout4):

        #apply QRAM
        qRAM(qc, add, board, lightout4)

        #apply U_2A
        new_u2a(qc, sol, board, anc, orc_out_2a)

        #counter
        #qc.append(WeightedAdder(9), sol+anc)
        bitcounter(qc, sol, anc)

        #verifying that the count is less than 3

        qc.x(anc[2])
        qc.x(anc[3])
        qc.ccx(anc[2], anc[3], orc_out_2b)
        qc.x(anc[2])
        qc.x(anc[3])    

        bitcounter_uncompute(qc, sol, anc)

        #apply U_2a_dagger
        new_u2a_dagger(qc, sol, board, anc, orc_out_2a)

        #apply qRAM dagger - same as qRAM
        qRAM(qc, add, board, lightout4)
    
    # start building the circuit
    n = len(lightout4[0])

    add = QuantumRegister(2, 'address')

    board = QuantumRegister(n, 'board')
    sol = QuantumRegister(n, 'solution')
    anc = QuantumRegister(6, 'anc')
    orc_out_2a = QuantumRegister(1, 'oracle_out_2a') 
    orc_out_2b = QuantumRegister(1, 'oracle_out_2b')

    c_out = ClassicalRegister(2, 'classical_bits')

    qc = QuantumCircuit(add, board, sol, orc_out_2a, anc, orc_out_2b, c_out)

    qc.h(add)
    qc.h(sol)

    qc.x(orc_out_2a)     
    qc.h(orc_out_2a)

    qc.x(orc_out_2b)     
    qc.h(orc_out_2b)
    qc.barrier() 

    r_2b = 2

    for i_2b in range(r_2b):

        oracle_2b(qc, add, board, sol, anc, orc_out_2a, orc_out_2b, lightout4)

        qc.barrier()
        qc.append(diffuser(2), add)
        qc.barrier()           


    qc.measure(add, c_out)

    qc = qc.reverse_bits()            
            
    ####  In addition, please make sure your function can solve the problem with different inputs (lightout4). We will cross validate with different inputs.
    
    return qc

# Submission code
from qc_grader import prepare_ex2b, grade_ex2b, submit_ex2b

# Execute your circuit with following prepare_ex2b() function.
# The prepare_ex2b() function works like the execute() function with only QuantumCircuit as an argument.
job  =  prepare_ex2b(week2b_ans_func)

result = job.result()
count = result.get_counts()
original_problem_set_counts = count[0]

original_problem_set_counts
# The bit string with the highest number of observations is treated as the solution.

# Check your answer by executing following code.
# The quantum cost of the QuantumCircuit is obtained as the score. The quantum cost is related to rank only in the third week.
grade_ex2b(job)

# Submit your results by executing following code. You can submit as many times as you like during the period. 
submit_ex2b(job)

