# import the quantum circuit, quantum register, and classical register
# from the IBM's Qiskit library
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

# import the numpy library
import numpy as np


# define the function to generate the quantum circuit
# for the 1st step of the Iterative Phase Estimation (IPE) of the S gate
def step_1_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
  
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 2 bits

    # create a quantum circuit with the quantum register and with
    # the classical register defined before, to implement
    # the 1st step of the Iterative Phase Estimation (IPE) of the S gate
    qc = QuantumCircuit(qr, cr)

    
    ####### your code goes here #######
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    # apply the Pauli-X gate to the 1st qubit (auxiliary qubit)
    qc.x(1)
    
    
    # apply the Controlled-Phase gate for the theta angle equal to pi,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( np.pi, 0, 1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 1st bit of
    # the classical register
    qc.measure(0, 0)
    
    
    # return the quantum circuit
    # for the Iterative Phase Estimation (IPE) of the S gate
    return qc


# create the quantum register for
# the quantum circuit to implement the 1st step of
# the Iterative Phase Estimation (IPE) of the S gate
qr = QuantumRegister(2, "q")

# create the classical register for
# the quantum circuit to implement the 1st step of
# the Iterative Phase Estimation (IPE) of the S gate
cr = ClassicalRegister(2, "c")


# create the quantum circuit to implement the 1st step of
# the Iterative Phase Estimation (IPE) of
# the S gate, with the quantum register
# and the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing the 1st step of
# the Iterative Phase Estimation (IPE) of the S gate
qc = step_1_circuit(qr, cr)


# draw the quantum circuit implementing the 1st step of
# the Iterative Phase Estimation (IPE) of the S gate
qc.draw("mpl")

# submit your circuit

# import the grader for the exercise 1 of the lab 4
from qc_grader.challenges.qgss_2023 import grade_lab4_ex1

# grade the exercise 1 of the lab 4
grade_lab4_ex1( qc )

# define the function to generate the quantum circuit
# for the 2nd step of the Iterative Phase Estimation (IPE) of the S gate
def step_2_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
  
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 2 bits

    # retrieve the quantum circuit implementing the 1st step of
    # the Iterative Phase Estimation (IPE) of the S gate
    qc = step_1_circuit(qr, cr)

    
    ####### your code goes here #######
    
    # reset the state of the 1st qubit (system's qubit)
    qc.reset(0)
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # if the 1st bit of the classical register of
    # the dynamic quantum circuit has the classical state 1
    with qc.if_test( ( cr[0], True ) ):
    
        # apply the phase correction of -pi / 2 to
        # the 1st qubit of the quantum register with a P gate    
        qc.p( -np.pi / 2, 0 )
    
    
    # apply the Controlled-Phase gate for the theta angle equal to pi / 2,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( np.pi / 2, 0, 1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 2nd bit of
    # the classical register
    qc.measure(0, 1)
    
    
    # return the quantum circuit implementing the 2nd step of
    # the Iterative Phase Estimation (IPE) of the S gate
    return qc


# create the quantum register for
# the quantum circuit to implement the 2nd step of
# the Iterative Phase Estimation (IPE) of the S gate
qr = QuantumRegister(2, "q")

# create the classical register for
# the quantum circuit to implement the 2nd step of
# the Iterative Phase Estimation (IPE) of the S gate
cr = ClassicalRegister(2, "c")


# create the quantum circuit to implement the 2nd step of
# the Iterative Phase Estimation (IPE) of
# the S gate, with the quantum register
# and the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing the 2nd step of
# the Iterative Phase Estimation (IPE) of the S gate
qc = step_2_circuit(qr, cr)


# draw the quantum circuit implementing the 2nd step of
# the Iterative Phase Estimation (IPE) of the S gate
qc.draw("mpl")

# submit your circuit

# import the grader for the exercise 2 of the lab 4
from qc_grader.challenges.qgss_2023 import grade_lab4_ex2

# grade the exercise 2 of the lab 4
grade_lab4_ex2( qc )

# import the Aer Simulator from the IBM's Qiskit Aer module
from qiskit_aer import AerSimulator


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the S gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# show the result counts of the quantum simulation
counts

# import the quantum circuit, quantum register, and classical register
# from the IBM's Qiskit library
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

# import the numpy library
import numpy as np


# define the function to generate the quantum circuit
# for the Iterative Phase Estimation (IPE) of the T gate
def t_gate_ipe_circuit( qr: QuantumRegister, cr: ClassicalRegister ) -> QuantumCircuit:
  
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 3 bits

    # create a quantum circuit with the quantum register and with
    # the classical register defined before, to implement
    # the Iterative Phase Estimation (IPE) of the T gate
    qc = QuantumCircuit(qr, cr)

    
    ####### your code goes here #######
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    # apply the Pauli-X gate to the 1st qubit (auxiliary qubit)
    qc.x(1)
    
    
    # apply the Controlled-Phase gate for the theta angle equal to pi,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( np.pi, 0, 1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 1st bit of
    # the classical register
    qc.measure(0, 0)
    
    
    # reset the state of the 1st qubit (system's qubit)
    qc.reset(0)
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # if the 1st bit of the classical register of
    # the dynamic quantum circuit has the classical state 1
    with qc.if_test( ( cr[0], True ) ):
    
        # apply the phase correction of -pi / 2 to
        # the 1st qubit of the quantum register with a P gate    
        qc.p( -np.pi / 2, 0 )
    
    
    # apply the Controlled-Phase gate for the theta angle equal to pi / 2,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( np.pi / 2, 0, 1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 2nd bit of
    # the classical register
    qc.measure(0, 1)
    
    
    # reset the state of the 1st qubit (system's qubit)
    qc.reset(0)
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)

    
    # if the 1st bit of the classical register of
    # the dynamic quantum circuit has the classical state 1
    with qc.if_test( ( cr[0], True ) ):
    
        # apply the phase correction of -pi / 2 to
        # the 1st qubit of the quantum register with a P gate   
        qc.p(-np.pi / 4, 0)
    
    
    # apply the Controlled-Phase gate for the theta angle equal to pi / ,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( np.pi / 4, 0, 1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 3rd bit of
    # the classical register
    qc.measure(0, 2)
    
    
    # return the quantum circuit
    # for the Iterative Phase Estimation (IPE) of the T gate
    return qc


# create the quantum register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the T gate
qr = QuantumRegister(2, "q")

# create the classical register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the T gate
cr = ClassicalRegister(3, "c")


# create the quantum circuit to implement
# the Iterative Phase Estimation (IPE) of
# the T gate, with the quantum register
# and the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the T gate
qc = t_gate_ipe_circuit(qr, cr)


# draw the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the T gate
qc.draw("mpl")

# import the Aer Simulator from the IBM's Qiskit Aer module
from qiskit_aer import AerSimulator


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the T gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# show the result counts of the quantum simulation
counts

# submit your circuit

# import the grader for the exercise 3 of the lab 4
from qc_grader.challenges.qgss_2023 import grade_lab4_ex3

# grade the exercise 3 of the lab 4
grade_lab4_ex3( qc )

# import the quantum circuit, quantum register, and classical register
# from the IBM's Qiskit library
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

# import the numpy library
import numpy as np


# define the function to generate the quantum circuit
# for the Iterative Phase Estimation (IPE) of an U gate
def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
  
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 2 bits

    # create a quantum circuit with the quantum register and with
    # the classical register defined before, to implement
    # the Iterative Phase Estimation (IPE) of the U gate
    qc = QuantumCircuit(qr, cr)

    # retrieve the two qubits of the quantum register
    q0, q1 = qr

    # retrieve the two bits of the classical register
    c0, c1 = cr
    
    
    ####### your code goes here #######
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)
    
    # apply the Pauli-X gate to the 1st qubit (auxiliary qubit)
    qc.x(q1)
    
    
    # define the theta angle of the U gate as 2 * pi / 3
    u_angle = 2 * np.pi / 3
    
    # define the number of the current iteration for
    # the Iterative Phase Estimation (IPE) of the U gate
    k = 1
    
    
    # define the theta angle of the Controlled-Phase gate,
    # according to the current iteration of
    # the Iterative Phase Estimation (IPE) of the U gate
    cphase_angle = u_angle * 2**k
    
    
    # apply the Controlled-Phase gate for the kth theta angle,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( cphase_angle, q0, q1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 1st bit of
    # the classical register
    qc.measure(q0, c0)
    
    
    # reset the state of the 1st qubit (system's qubit)
    qc.reset(q0)
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)

        
    # define the number of the current iteration for
    # the Iterative Phase Estimation (IPE) of the U gate
    k = 0
    
    # define the theta angle of the Controlled-Phase gate,
    # according to the current iteration of
    # the Iterative Phase Estimation (IPE) of the U gate
    cphase_angle = u_angle * 2**k
    
    
    # if the 1st bit of the classical register of
    # the dynamic quantum circuit has the classical state 1
    with qc.if_test( ( cr[0], True ) ):
    
        # apply the phase correction of the kth theta angle to
        # the 1st qubit of the quantum register with a P gate   
        qc.p( -cphase_angle, q0 )
    
    
    # apply the Controlled-Phase gate for the kth theta angle,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( cphase_angle, q0, q1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 2nd bit of
    # the classical register
    qc.measure(q0, c1)

    
    # return the quantum circuit
    # for the Iterative Phase Estimation (IPE) of an U gate
    return qc



# create the quantum register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate
qr = QuantumRegister(2, "q")

# create the classical register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate
cr = ClassicalRegister(2, "c")


# create the quantum circuit to implement
# the Iterative Phase Estimation (IPE) of
# the U gate, with the quantum register
# and the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate
qc = u_circuit(qr, cr)


# draw the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate
qc.draw("mpl")

# import the Aer Simulator from the IBM's Qiskit Aer module
from qiskit_aer import AerSimulator


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the U gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# print the result counts of the quantum simulation
print(counts)


# compute the success probability of the correct outcome
success_probability = counts["01"] / counts.shots()


# print the success probability of the correct outcome
print(f"Success probability: {success_probability}")

# import the quantum circuit, quantum register, and classical register
# from the IBM's Qiskit library
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

# import the numpy library
import numpy as np


# define the function to generate the quantum circuit
# for the Iterative Phase Estimation (IPE) of an U gate
def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
  
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 1 bit

    # create a quantum circuit with the quantum register and with
    # the classical register defined before, to implement
    # the Iterative Phase Estimation (IPE) of the U gate
    qc = QuantumCircuit(qr, cr)

    # retrieve the two qubits of the quantum register
    q0, q1 = qr

    # retrieve bit of the classical register
    c0 = cr
    
    
    ####### your code goes here #######
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)
    
    # apply the Pauli-X gate to the 1st qubit (auxiliary qubit)
    qc.x(q1)
    
    
    # define the theta angle of the U gate as 2 * pi / 3
    u_angle = 2 * np.pi / 3
    
    # define the number of the current iteration for
    # the Iterative Phase Estimation (IPE) of the U gate
    k = 1
    
    
    # define the theta angle of the Controlled-Phase gate,
    # according to the current iteration of
    # the Iterative Phase Estimation (IPE) of the U gate
    cphase_angle = u_angle * 2**k
    
    
    # apply the Controlled-Phase gate for the kth theta angle,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( cphase_angle, q0, q1 )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(q0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 1st bit of
    # the classical register
    qc.measure(q0, c0)

    
    # return the quantum circuit
    # for the Iterative Phase Estimation (IPE) of an U gate
    return qc


# create the quantum register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate
qr = QuantumRegister(2, "q")

# create the classical register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate
cr = ClassicalRegister(1, "c")


# create the quantum circuit to implement
# the Iterative Phase Estimation (IPE) of
# the U gate, with the quantum register
# and the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate
qc = u_circuit(qr, cr)


# draw the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate
qc.draw("mpl")

# create a job for the quantum circuit implementing
# the IPE for the U gate, running the Aer Simulator
job = sim.run(qc, shots=15)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# print the result counts of the quantum simulation
print(counts)

# define the variable for
# the bit resulting from the 1st step of
# the Iterative Phase Estimation (IPE)
step1_bit: int


####### your code goes here #######

# define the bit resulting from the 1st step of
# the Iterative Phase Estimation (IPE)
step1_bit = 1

# print the bit resulting from the 1st step of
# the Iterative Phase Estimation (IPE)
print(step1_bit)

# submit your result

# import the grader for the exercise 4 of the lab 4
from qc_grader.challenges.qgss_2023 import grade_lab4_ex4

# grade the exercise 4 of the lab 4
grade_lab4_ex4( step1_bit )

# import the quantum circuit, quantum register, and classical register
# from the IBM's Qiskit library
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

# import the numpy library
import numpy as np


# define the function to generate the quantum circuit
# for the Iterative Phase Estimation (IPE) of an U gate,
# based on the classical bit obtained from the 1st step
def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:
    
    # notes:
    # - qr is a quantum register with 2 qubits
    # - cr is a classical register with 1 bit

    # create a quantum circuit with the quantum register and with
    # the classical register defined before, to implement
    # the Iterative Phase Estimation (IPE) of the U gate
    qc = QuantumCircuit(qr, cr)

    # retrieve the two qubits of the quantum register
    q0, q1 = qr

    # retrieve the two bit of the classical register
    c0, c1 = cr
    
    
    ####### your code goes here #######

    # if the classical bit obtained from
    # the 1st step is the classical state 1
    with qc.if_test( ( step1_bit, True ) ):
    
        # apply the Pauli-X gate to the 1st qubit (system's qubit)
        qc.x(q0)
    
    
    # apply the Pauli-X gate to the 2nd qubit (auxiliary qubit)
    qc.x(q1)
    
    
    # define the theta angle of the U gate as pi / 3
    u_angle = np.pi / 3
    
    
    # define the number of the current iteration for
    # the Iterative Phase Estimation (IPE) of the U gate
    k = 1
    
    
    # define the theta angle of the Controlled-Phase gate,
    # according to the current iteration of
    # the Iterative Phase Estimation (IPE) of the U gate
    cphase_angle = u_angle * 2**k


    # apply the Controlled-Phase gate for the kth theta angle,
    # with control on the 1st qubit and target on the 2nd qubit
    qc.cp( cphase_angle, q0, q1 )
    

    # measure the state of the 2nd qubit (auxiliary qubit),
    # storing the classical outcome on the 1st bit of
    # the classical register   
    qc.measure(q1, c0)
    
    
    # return the quantum circuit
    # for the Iterative Phase Estimation (IPE) of an U gate,
    # based on the classical bit obtained from the 1st step
    return qc


# create the quantum register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate,
# based on the classical bit obtained from the 1st step
qr = QuantumRegister(2, "q")

# create the classical register for the quantum circuit to
# implement the Iterative Phase Estimation (IPE) of the U gate,
# based on the classical bit obtained from the 1st step
cr = ClassicalRegister(2, "c")


# create the quantum circuit to implement
# the Iterative Phase Estimation (IPE) of
# the U gate, based on the classical bit
# obtained from the 1st step,
# with the quantum register and
# the classical register defined before
qc = QuantumCircuit(qr, cr)


# create the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate,
# based on the classical bit obtained from the 1st step
qc = u_circuit(qr, cr)


# draw the quantum circuit implementing
# the Iterative Phase Estimation (IPE) of the U gate,
# based on the classical bit obtained from the 1st step
qc.draw("mpl")

# submit your result

# import the grader for the exercise 5 of the lab 4
from qc_grader.challenges.qgss_2023 import grade_lab4_ex5

# grade the exercise 5 of the lab 4
grade_lab4_ex5( qc )

# import the Aer Simulator from the IBM's Qiskit Aer module
from qiskit_aer import AerSimulator


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the U gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# print the result counts of the quantum simulation
print(counts)


# compute the success probability of the correct outcome
success_probability = counts["01"] / counts.shots()


# print the success probability of the correct outcome
print(f"Success probability: {success_probability}")

# import the Gate from the IBM's Qiskit Circuit module
from qiskit.circuit import Gate


# define the function to generate the dynamic quantum circuit
# for a general Iterative Phase Estimation (IPE)
def iterative_phase_estimation(
    qr: QuantumRegister,
    cr: ClassicalRegister,
    controlled_unitaries: list[Gate],
    state_prep: Gate,
) -> QuantumCircuit:
    qc = QuantumCircuit(qr, cr)

    
    ####### your code goes here #######
    
    # retrieve the number of the Controlled-U gates
    m = len( controlled_unitaries )
    
    
    # create a copy of the list of the Controlled-U gates
    controlled_unitaries_reversed = controlled_unitaries.copy()

    # retrieve the reverse list of the Controlled-U gates
    controlled_unitaries_reversed.reverse()
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # initialize the preparation of the state
    # with the eigenstate |1>
    qc.append( state_prep, [1] )
    
    
    # apply the Controlled-U gate for t = (m - 1)
    qc.append( controlled_unitaries_reversed[0], [0, 1] )
    
    
    # apply the Hadamard gate to the 1st qubit (system's qubit)
    qc.h(0)
    
    
    # measure the state of the 1st qubit (system's qubit),
    # storing the classical outcome on the 1st bit
    qc.measure(0, 0)
   

    # for the remaining (m - 1) iterations
    for k in range(1, m):
    
        # reset the state of the 1st qubit (system's qubit)
        qc.reset(0)

        
        # apply the Hadamard gate to the 1st qubit (system's qubit)
        qc.h(0)

        
        # if the 1st bit of the classical register of
        # the dynamic quantum circuit has the classical state 1
        with qc.if_test( ( cr[0], 1 ) ):
            
            # apply the phase correction to the 1st qubit of
            # the quantum register with a P gate
            qc.p( -controlled_unitaries[ ( m - k - 1 ) ].params[0], 0 )

        
        # apply the Controlled-U gate for t = (m - k)
        qc.append( controlled_unitaries_reversed[k], [0, 1] )

        
        # apply the Hadamard gate to the 1st qubit (system's qubit)
        qc.h(0)

        
        # measure the state of the 1st qubit (system's qubit),
        # storing the classical outcome on the kth bit
        qc.measure(0, k)

    
    # return the dynamic quantum circuit
    # for a general Iterative Phase Estimation (IPE)
    return qc

# import the Controlled-Phase and Pauli-X gates
# from the IBM's Qiskit Circuit Library module
from qiskit.circuit.library import CPhaseGate, XGate


# create the quantum register with two qubits
# for the quantum circuit to implement IPE on the Z gate
qr = QuantumRegister(2, "q")

# create the quantum register with three bits
# for the quantum circuit to implement IPE on the Z gate
cr = ClassicalRegister(1, "c")


# define the theta angle for the Z gate
z_angle = np.pi


# create the list of Controlled-U/Controlled-Phases gates,
# considering the theta angle for the Z gate
controlled_unitaries = [ CPhaseGate( z_angle * 2**k ) for k in range(1) ]


# generate the quantum circuit implementing
# the IPE for the Z gate
qc = iterative_phase_estimation( qr, cr, controlled_unitaries, XGate() )


# draw the quantum circuit implementing
# the IPE for the Z gate
qc.draw("mpl")


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the Z gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# show the result counts of the quantum simulation
counts

# import the Controlled-Phase and Pauli-X gates
# from the IBM's Qiskit Circuit Library module
from qiskit.circuit.library import CPhaseGate, XGate


# create the quantum register with two qubits
# for the quantum circuit to implement IPE on the S gate
qr = QuantumRegister(2, "q")

# create the quantum register with three bits
# for the quantum circuit to implement IPE on the S gate
cr = ClassicalRegister(2, "c")


# define the theta angle for the S gate
s_angle = np.pi / 2


# create the list of Controlled-U/Controlled-Phase gates,
# considering the theta angle for the S gate
controlled_unitaries = [ CPhaseGate( s_angle * 2**k ) for k in range(2) ]


# generate the quantum circuit implementing
# the IPE for the S gate
qc = iterative_phase_estimation( qr, cr, controlled_unitaries, XGate() )


# draw the quantum circuit implementing
# the IPE for the S gate
qc.draw("mpl")


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the S gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# show the result counts of the quantum simulation
counts

# import the Controlled-Phase and Pauli-X gates
# from the IBM's Qiskit Circuit Library module
from qiskit.circuit.library import CPhaseGate, XGate


# create the quantum register with two qubits
# for the quantum circuit to implement IPE on the T gate
qr = QuantumRegister(2, "q")

# create the quantum register with three bits
# for the quantum circuit to implement IPE on the T gate
cr = ClassicalRegister(3, "c")


# define the theta angle for the T gate
t_angle = np.pi / 4


# create the list of Controlled-U/Controlled-Phase gates,
# considering the theta angle for the T gate
controlled_unitaries = [ CPhaseGate( t_angle * 2**k ) for k in range(3) ]


# generate the quantum circuit implementing
# the IPE for the T gate
qc = iterative_phase_estimation( qr, cr, controlled_unitaries, XGate() )


# draw the quantum circuit implementing
# the IPE for the T gate
qc.draw("mpl")


# create an Aer Simulator object
sim = AerSimulator()


# create a job for the quantum circuit implementing
# the IPE for the T gate, running the Aer Simulator
job = sim.run(qc, shots=1000)


# retrieve the result counts of the quantum simulation
result = job.result()

# retrieve the result counts of the quantum simulation
counts = result.get_counts()


# show the result counts of the quantum simulation
counts

# import the IBMps Provider from Qiskit IBM Provider module
from qiskit_ibm_provider import IBMProvider

# create an IBM's Provider object
provider = IBMProvider()

# define the hub for the IBM's Provider
hub = "summer-school-6"

# define the group for the IBM's Provider
group = "group-3"

# define the project for the IBM's Provider
project = "7048813929"


# define the backend's name for the IBM's Provider
backend_name = "ibmq_manila"


# retrieve the backend from the IBM's Provider
backend = provider.get_backend( backend_name, instance=f"{hub}/{group}/{project}" )

# import the transpile feature
# from the IBM's Qiskit library
from qiskit import transpile


# create the quantum register with two qubits
# for the quantum circuit to implement IPE on the S gate
qr = QuantumRegister(2, "q")

# create a classical register with two bits
# for the quantum circuit to implement IPE on the S gate
cr = ClassicalRegister(2, "c")


# create the quantum circuit with
# the quantum and classical registers
# defined before to implement IPE on the S gate
qc = QuantumCircuit(qr, cr)


# perform the 2nd step of the IPE on the S gate to
# the quantum circuit created before
qc = step_2_circuit(qr, cr)

# create the transpiled quantum circuit
# to implement IPE on the S gate
qc_transpiled = transpile(qc, backend)


# draw the quantum circuit implementing
# an IPE on the S gate
qc_transpiled.draw("mpl")

# OPTIONAL: run the job of the IPE for the S gate

# run the transpiled dynamic quantum circuit of
# the IPE for the S gate
#job = backend.run(qc_transpiled, shots=1000, dynamic=True)

# retrieve the id of the job of the quantum simulation
# for the IPE for the S gate
#job_id = job.job_id()

# print the id of the job of the quantum simulation
# for the IPE for the S gate
#print(job_id)

# OPTIONAL: retrieve and show the job's status of the IPE for the S gate

# retrieve the job of the quantum simulation
# for the IPE for the S gate
#retrieve_job = provider.retrieve_job(job_id)

# show the job's status of the quantum simulation
# for the IPE for the S gate
#retrieve_job.status()

# OPTIONAL: plot the histogram of the results of the IPE for the S gate

# import the plot histogram from
# the IBM's Qiskit Tools' Visualization module
#from qiskit.tools.visualization import plot_histogram

# retrieve the counts of the results of the job
# for the quantum simulation for the IPE for the S gate
#counts = retrieve_job.result().get_counts()

# plot the historgram of the count results of
# the quantum simulation for the IPE for the S gate
#plot_histogram(counts)

# import the IBM's Qiskit Jupyter Tools
import qiskit.tools.jupyter

# show the table of the IBM's Qiskit version
%qiskit_version_table