# import all necessary objects and methods for quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
from qiskit.tools.visualization import matplotlib_circuit_drawer as drawer

qreg = QuantumRegister(4) # quantum register with 4 qubits
creg = ClassicalRegister(4) # classical register with 4 bits
mycircuit = QuantumCircuit(qreg,creg) # quantum circuit with quantum and classical registers

# apply h-gate (Hadamard) to each qubit
for i in range(4):
    mycircuit.h(qreg[i])

# measure both qubits
mycircuit.measure(qreg,creg)
    
# execute the circuit 1600 times, and print the outcomes
job = execute(mycircuit,Aer.get_backend('qasm_simulator'),shots=1600)
counts = job.result().get_counts(mycircuit)
for outcome in counts:
    reverse_outcome = ''
    for i in outcome:
        reverse_outcome = i + reverse_outcome
    print(reverse_outcome,"is observed",counts[outcome],"times")

# find the angle theta

u2 = [(13/16)**0.5,(3/16)**0.5]
print(u2)

from math import acos # acos is the inverse of function cosine
from math import pi 

def angle_between_two_quantum_states(quantum_state1,quantum_state2):
    inner_product = quantum_state1[0] * quantum_state2[0] + quantum_state1[1] * quantum_state2[1]
    return acos(inner_product) 

# angle between |u> and |0> 
theta2 = angle_between_two_quantum_states(u2,[1,0])
print(theta2)

# COPY-PASTE the functions from the notebook "B80_Reflections"

def amplitudes_of_a_quantum_state(quantum_circuit):
    # import all necessary objects and methods for quantum circuits
    from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
    
     # the following code is used to get the quantum state of a quantum circuit
    job = execute(quantum_circuit,Aer.get_backend('statevector_simulator'))
    current_quantum_state=job.result().get_statevector(quantum_circuit) 
    
    # now we read the real parts of the amplitudes
    the_first_amplitude = current_quantum_state[0].real # amplitude of |0>
    the_second_amplitude = current_quantum_state[1].real # amplitude of |1>
    
    return[the_first_amplitude,the_second_amplitude]
# end of function

def visualize_quantum_states(quantum_states):
    # import the useful tool for drawing figures in pythpn
    from matplotlib.pyplot import plot, show, figure, Circle, axis, gca, annotate, arrow, text
    # import the constant pi
    from math import pi
    
    figure(figsize=(6,6), dpi=80) # size of the figure
    gca().add_patch( Circle((0,0),1,color='black',fill=False) ) # draw the circle
    # auxiliary points
    plot(-1.3,0)
    plot(1.3,0)
    plot(0,1.3)
    plot(0,-1.3)
    # axes
    arrow(0,0,1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,-1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,0,-1.1,head_width=0.04, head_length=0.08)
    arrow(0,0,0,1.1,head_width=0.04, head_length=0.08)
    
    # draw all quantum states

    for quantum_state in quantum_states:
        # show the quantum state as an arrow on the diagram
        state_name = quantum_state[0] # label of the quantum state 
        x_value = quantum_state[1] # amplitude of |0>
        y_value = quantum_state[2] # amplitude of |1>
        # draw the arrow
        arrow(0,0,x_value,y_value,head_width=0.04, head_length=0.04,color='blue')

         # the following code is used to write the name of quantum states
        if x_value<0: text_x_value=x_value-0.1
        else: text_x_value=x_value+0.05
        if y_value<0: text_y_value=y_value-0.1
        else: text_y_value=y_value+0.05       
        text(text_x_value,text_y_value,state_name)

    show() # show the diagram
# end of function

# import all necessary objects and methods for quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
from qiskit.tools.visualization import matplotlib_circuit_drawer as drawer


all_visited_quantum_states2 =[]


qreg2 = QuantumRegister(1) # quantum register with 1 qubit
creg2 = ClassicalRegister(1) # classical register with 1 bit
mycircuit2 = QuantumCircuit(qreg2,creg2) # quantum circuit with quantum and classical registers


# set the qubit to |u>
# rotate by theta2
# do not forget to multiply it by 2
mycircuit2.ry(2*theta2,qreg2[0])

# read and store the current quantum state
[x,y] = amplitudes_of_a_quantum_state(mycircuit2)
all_visited_quantum_states2.append(['u',x,y])


# this is (-2*theta2) in the first iteration
rotation_angle_for_the_first_reflection2 = -2 * theta2
# this is always (2*theta2) more than (-1*rotation_angle_for_the_first_reflection2)
rotation_angle_for_the_second_reflection2 = (2*theta2) + (-1*rotation_angle_for_the_first_reflection2)

# the first reflection: rotate by rotation_angle_for_the_first_reflection2
mycircuit2.ry(2*rotation_angle_for_the_first_reflection2,qreg2[0])
# read and store the current quantum state
[x,y] = amplitudes_of_a_quantum_state(mycircuit2)
all_visited_quantum_states2.append(['r',x,y]) # the label is r (reflected state)

# the second reflection: rotate by rotation_angle_for_the_second_reflection2
mycircuit2.ry(2*rotation_angle_for_the_second_reflection2,qreg2[0])
# read and store the current quantum state
[x,y] = amplitudes_of_a_quantum_state(mycircuit2)
all_visited_quantum_states2.append(['n',x,y]) # the label is n (new state)
    
    
visualize_quantum_states(all_visited_quantum_states2)
    
    
# measure both qubits
mycircuit2.measure(qreg2,creg2)
    
    
# execute the circuit 100 times, and print the outcomes
job = execute(mycircuit2,Aer.get_backend('qasm_simulator'),shots=100)
counts2 = job.result().get_counts(mycircuit2)
for outcome in counts2:
    reverse_outcome = ''
    for i in outcome:
        reverse_outcome = i + reverse_outcome
    print(reverse_outcome,"is observed",counts2[outcome],"times")

print(all_visited_quantum_states2)

# find the angle theta

u3 = [(63/64)**0.5,(1/64)**0.5]
print(u3)

from math import acos # acos is the inverse of function cosine
from math import pi 

def angle_between_two_quantum_states(quantum_state1,quantum_state2):
    inner_product = quantum_state1[0] * quantum_state2[0] + quantum_state1[1] * quantum_state2[1]
    return acos(inner_product) 

# angle between |u> and |0> 
theta3 = angle_between_two_quantum_states(u3,[1,0])
print(theta3)

# COPY-PASTE the functions from the notebook "B80_Reflections"

def amplitudes_of_a_quantum_state(quantum_circuit):
    # import all necessary objects and methods for quantum circuits
    from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
    
     # the following code is used to get the quantum state of a quantum circuit
    job = execute(quantum_circuit,Aer.get_backend('statevector_simulator'))
    current_quantum_state=job.result().get_statevector(quantum_circuit) 
    
    # now we read the real parts of the amplitudes
    the_first_amplitude = current_quantum_state[0].real # amplitude of |0>
    the_second_amplitude = current_quantum_state[1].real # amplitude of |1>
    
    return[the_first_amplitude,the_second_amplitude]
# end of function

def visualize_quantum_states(quantum_states):
    # import the useful tool for drawing figures in pythpn
    from matplotlib.pyplot import plot, show, figure, Circle, axis, gca, annotate, arrow, text
    # import the constant pi
    from math import pi
    
    figure(figsize=(6,6), dpi=80) # size of the figure
    gca().add_patch( Circle((0,0),1,color='black',fill=False) ) # draw the circle
    # auxiliary points
    plot(-1.3,0)
    plot(1.3,0)
    plot(0,1.3)
    plot(0,-1.3)
    # axes
    arrow(0,0,1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,-1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,0,-1.1,head_width=0.04, head_length=0.08)
    arrow(0,0,0,1.1,head_width=0.04, head_length=0.08)
    
    # draw all quantum states

    for quantum_state in quantum_states:
        # show the quantum state as an arrow on the diagram
        state_name = quantum_state[0] # label of the quantum state 
        x_value = quantum_state[1] # amplitude of |0>
        y_value = quantum_state[2] # amplitude of |1>
        # draw the arrow
        arrow(0,0,x_value,y_value,head_width=0.04, head_length=0.04,color='blue')

         # the following code is used to write the name of quantum states
        if x_value<0: text_x_value=x_value-0.1
        else: text_x_value=x_value+0.05
        if y_value<0: text_y_value=y_value-0.1
        else: text_y_value=y_value+0.05       
        text(text_x_value,text_y_value,state_name)

    show() # show the diagram
# end of function

# import all necessary objects and methods for quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
from qiskit.tools.visualization import matplotlib_circuit_drawer as drawer


all_visited_quantum_states3 =[]


qreg3 = QuantumRegister(1) # quantum register with 1 qubit
creg3 = ClassicalRegister(1) # classical register with 1 bit
mycircuit3 = QuantumCircuit(qreg3,creg3) # quantum circuit with quantum and classical registers

# set the qubit to |u>
# rotate by theta3
# do not forget to multiply it by 2
mycircuit3.ry(2*theta3,qreg3[0])

# read and store the current quantum state
[x,y] = amplitudes_of_a_quantum_state(mycircuit3)
all_visited_quantum_states3.append(['u',x,y])

# this is -2 * theta3 in the first iteration
rotation_angle_for_the_first_reflection3 = -2 * theta3
# this is always (2*theta3) more than (-1*rotation_angle_for_the_first_reflection3)
rotation_angle_for_the_second_reflection3 = (2*theta3) + (-1*rotation_angle_for_the_first_reflection3)

for i in range(3): # three iterations # later check 4, 5, 6, 7, 8, 9, and 10

    # the first reflection: rotate by rotation_angle_for_the_first_reflection3
    mycircuit3.ry(2*rotation_angle_for_the_first_reflection3,qreg3[0])
    # read and store the current quantum state
    [x,y] = amplitudes_of_a_quantum_state(mycircuit3)
    all_visited_quantum_states3.append(['r'+str(i+1),x,y]) # the labels are r1, r2, ... (reflected states)

    # the second reflection: rotate by rotation_angle_for_the_second_reflection3
    mycircuit3.ry(2*rotation_angle_for_the_second_reflection3,qreg3[0])
    # read and store the current quantum state
    [x,y] = amplitudes_of_a_quantum_state(mycircuit3)
    all_visited_quantum_states3.append(['n'+str(i+1),x,y]) # the labels are n1, n2, ... (new states)
    
    # this will be increased by (-4*theta2) after each iteration
    rotation_angle_for_the_first_reflection3 = rotation_angle_for_the_first_reflection3 -4* theta3
    # this is always (2*theta2) more than (-1*rotation_angle_for_the_first_reflection3)
    rotation_angle_for_the_second_reflection3 = (2*theta3) + (-1*rotation_angle_for_the_first_reflection3)
# end of iterations

visualize_quantum_states(all_visited_quantum_states3)
    
# measure both qubits
mycircuit3.measure(qreg3,creg3)
    
# execute the circuit 100 times, and print the outcomes
job = execute(mycircuit3,Aer.get_backend('qasm_simulator'),shots=100)
counts3 = job.result().get_counts(mycircuit3)
for outcome in counts3:
    reverse_outcome = ''
    for i in outcome:
        reverse_outcome = i + reverse_outcome
    print(reverse_outcome,"is observed",counts3[outcome],"times")

print(all_visited_quantum_states3)

# find the angle theta
# k=8, 2^k = 256

u4 = [(255/256)**0.5,(1/256)**0.5]
print(u4)

from math import acos # acos is the inverse of function cosine
from math import pi 

def angle_between_two_quantum_states(quantum_state1,quantum_state2):
    inner_product = quantum_state1[0] * quantum_state2[0] + quantum_state1[1] * quantum_state2[1]
    return acos(inner_product) 

# angle between |u> and |0> 
theta4 = angle_between_two_quantum_states(u4,[1,0])
print(theta4)

# COPY-PASTE the functions from the notebook "B80_Reflections"

def amplitudes_of_a_quantum_state(quantum_circuit):
    # import all necessary objects and methods for quantum circuits
    from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
    
     # the following code is used to get the quantum state of a quantum circuit
    job = execute(quantum_circuit,Aer.get_backend('statevector_simulator'))
    current_quantum_state=job.result().get_statevector(quantum_circuit) 
    
    # now we read the real parts of the amplitudes
    the_first_amplitude = current_quantum_state[0].real # amplitude of |0>
    the_second_amplitude = current_quantum_state[1].real # amplitude of |1>
    
    return[the_first_amplitude,the_second_amplitude]
# end of function

def visualize_quantum_states(quantum_states):
    # import the useful tool for drawing figures in pythpn
    from matplotlib.pyplot import plot, show, figure, Circle, axis, gca, annotate, arrow, text
    # import the constant pi
    from math import pi
    
    figure(figsize=(6,6), dpi=80) # size of the figure
    gca().add_patch( Circle((0,0),1,color='black',fill=False) ) # draw the circle
    # auxiliary points
    plot(-1.3,0)
    plot(1.3,0)
    plot(0,1.3)
    plot(0,-1.3)
    # axes
    arrow(0,0,1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,-1.1,0,head_width=0.04, head_length=0.08)
    arrow(0,0,0,-1.1,head_width=0.04, head_length=0.08)
    arrow(0,0,0,1.1,head_width=0.04, head_length=0.08)
    
    # draw all quantum states

    for quantum_state in quantum_states:
        # show the quantum state as an arrow on the diagram
        state_name = quantum_state[0] # label of the quantum state 
        x_value = quantum_state[1] # amplitude of |0>
        y_value = quantum_state[2] # amplitude of |1>
        # draw the arrow
        arrow(0,0,x_value,y_value,head_width=0.04, head_length=0.04,color='blue')

         # the following code is used to write the name of quantum states
        if x_value<0: text_x_value=x_value-0.1
        else: text_x_value=x_value+0.05
        if y_value<0: text_y_value=y_value-0.1
        else: text_y_value=y_value+0.05       
        text(text_x_value,text_y_value,state_name)

    show() # show the diagram
# end of function

# import all necessary objects and methods for quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
from qiskit.tools.visualization import matplotlib_circuit_drawer as drawer


how_many_times_state_one_observed = 0

decrease = False

number_of_iteration = 1

while decrease == False:

    qreg4 = QuantumRegister(1) # quantum register with 1 qubit
    creg4 = ClassicalRegister(1) # classical register with 1 bit
    mycircuit4 = QuantumCircuit(qreg4,creg4) # quantum circuit with quantum and classical registers

    # set the qubit to |u>
    # rotate by theta4
    # do not forget to multiply it by 2
    mycircuit4.ry(2*theta4,qreg4[0])


    # this is -2 * theta4 in the first iteration
    rotation_angle_for_the_first_reflection4 = -2 * theta4
    # this is always (2*theta4) more than (-1*rotation_angle_for_the_first_reflection4)
    rotation_angle_for_the_second_reflection4 = (2*theta4) + (-1*rotation_angle_for_the_first_reflection4)

    for i in range(number_of_iteration):
        
        # the first reflection: rotate by rotation_angle_for_the_first_reflection4
        mycircuit4.ry(2*rotation_angle_for_the_first_reflection4,qreg4[0])

        # the second reflection: rotate by rotation_angle_for_the_second_reflcetion4
        mycircuit4.ry(2*rotation_angle_for_the_second_reflection4,qreg4[0])
    
        # this will be increased by (-4*theta4) after each iteration
        rotation_angle_for_the_first_reflection4 = rotation_angle_for_the_first_reflection4 -4* theta4
        # this is always (2*theta4) more than (-1*rotation_angle_for_the_first_reflection4)
        rotation_angle_for_the_second_reflection4 = (2*theta4) + (-1*rotation_angle_for_the_first_reflection4)
    # end of iterations

    
    # measure both qubits
    mycircuit4.measure(qreg4,creg4)
    
    # execute the circuit 1000 times, and print the outcomes
    job = execute(mycircuit4,Aer.get_backend('qasm_simulator'),shots=1000)
    counts4 = job.result().get_counts(mycircuit4)
    print(number_of_iteration,counts4) # print the outcomes
    for outcome in counts4:
        if outcome == '1': 
            # how_many_times_state_one_observed is more than 500 and has a less value than the previous one
            # then it is time to STOP
            if how_many_times_state_one_observed> 500 and how_many_times_state_one_observed > counts4[outcome]: 
                print("B is",number_of_iteration-1)
                decrease = True
            else: # we should continue
                how_many_times_state_one_observed = counts4[outcome] # update how_many_times_state_one_observed
                number_of_iteration = number_of_iteration + 1 # increase number_of_iteration