## Blank Code Cell
## Use only if you need to install the grader and/or Qiskit
## If you are running this notebook in the IBM Quantum Lab - you can ignore this cell

!pip install qiskit
!pip install 'qc-grader[qiskit] @ git+https://github.com/qiskit-community/Quantum-Challenge-Grader.git'

## Run this cell to make sure your grader is setup correctly

%set_env QC_GRADE_ONLY=true
%set_env QC_GRADING_ENDPOINT=https://qac-grading.quantum-computing.ibm.com

from qiskit import QuantumCircuit
from qiskit.circuit import QuantumRegister, ClassicalRegister

qr = QuantumRegister(1)
cr = ClassicalRegister(2)
qc = QuantumCircuit(qr, cr)

# unpack the qubit and classical bits from the registers
(q0,) = qr
b0, b1 = cr

# apply Hadamard
qc.h(q0)
# measure
qc.measure(q0, b0)

# begin if test block. the contents of the block are executed if b0 == 1
with qc.if_test((b0, 1)):
    # if the condition is satisfied (b0 == 1), then flip the bit back to 0
    qc.x(q0)
# finally, measure q0 again
qc.measure(q0, b1)

qc.draw(output="mpl", idle_wires=False)

from qiskit_aer import AerSimulator

# initialize the simulator
backend_sim = AerSimulator()

# run the circuit
reset_sim_job = backend_sim.run(qc)
# get the results
reset_sim_result = reset_sim_job.result()
# retrieve the bitstring counts
reset_sim_counts = reset_sim_result.get_counts()

print(f"Counts: {reset_sim_counts}")

from qiskit.visualization import *

# plot histogram
plot_histogram(reset_sim_counts)

qr = QuantumRegister(2)
cr = ClassicalRegister(2)
qc = QuantumCircuit(qr, cr)

q0, q1 = qr
b0, b1 = cr

qc.h(q0)
qc.measure(q0, b0)


## Write your code below this line ##




## Do not change the code below this line ##


qc.measure(q1, b1)
qc.draw(output="mpl", idle_wires=False)

backend_sim = AerSimulator()

job_1 = backend_sim.run(qc)
result_1 = job_1.result()
counts_1 = result_1.get_counts()

print(f"Counts: {counts_1}")

# Grader Cell: Run this to submit your answer

from qc_grader.challenges.fall_fest23 import grade_ex3b

grade_ex3b(qc)

controls = QuantumRegister(2, name="control")
target = QuantumRegister(1, name="target")

mid_measure = ClassicalRegister(2, name="mid")
final_measure = ClassicalRegister(1, name="final")

base = QuantumCircuit(controls, target, mid_measure, final_measure)

def trial(
    circuit: QuantumCircuit,
    target: QuantumRegister,
    controls: QuantumRegister,
    measures: ClassicalRegister,
):
    """Probabilistically perform Rx(theta) on the target, where cos(theta) = 3/5."""


    ## Write your code below this line, making sure it's indented to where this comment begins from ##




    ## Do not change the code below this line ##


qc = base.copy_empty_like()
trial(qc, target, controls, mid_measure)
qc.draw("mpl", cregbundle=False)

# Grader Cell: Run this to submit your answer

from qc_grader.challenges.fall_fest23 import grade_ex3c

grade_ex3c(qc)

def reset_controls(
    circuit: QuantumCircuit, controls: QuantumRegister, measures: ClassicalRegister
):
    """Reset the control qubits if they are in |1>."""


    ## Write your code below this line, making sure it's indented to where this comment begins from ##




    ## Do not change the code below this line ##


qc = base.copy_empty_like()
trial(qc, target, controls, mid_measure)
reset_controls(qc, controls, mid_measure)
qc.measure(controls, mid_measure)
qc.draw("mpl", cregbundle=False)

# Grader Cell: Run this to submit your answer

from qc_grader.challenges.fall_fest23 import grade_ex3d

grade_ex3d(qc)

# Set the maximum number of trials
max_trials = 2

# Create a clean circuit with the same structure (bits, registers, etc) as the initial base we set up.
circuit = base.copy_empty_like()

# The first trial does not need to reset its inputs, since the controls are guaranteed to start in the |0> state.
trial(circuit, target, controls, mid_measure)

# Manually add the rest of the trials.  In the future, we will be able to use a dynamic `while` loop to do this, but for now,
# we statically add each loop iteration with a manual condition check on each one.
# This involves more classical synchronizations than the while loop, but will suffice for now.

for _ in range(max_trials - 1):
    reset_controls(circuit, controls, mid_measure)
    with circuit.if_test((mid_measure, 0b00)) as else_:
        # This is the success path, but Qiskit can't directly
        # represent a negative condition yet, so we have an
        # empty `true` block in order to use the `else` branch.
        pass
    with else_:


        ## Write your code below this line, making sure it's indented to where this comment begins from ##




        ## Do not change the code below this line ##


# We need to measure the control qubits again to ensure we get their final results; this is a hardware limitation.
circuit.measure(controls, mid_measure)

# Finally, let's measure our target, to check that we're getting the rotation we desired.
circuit.measure(target, final_measure)

circuit.draw("mpl", cregbundle=False)

# Grader Cell: Run this to submit your answer

from qc_grader.challenges.fall_fest23 import grade_ex3e

grade_ex3e(circuit)

sim = AerSimulator()
job = sim.run(circuit, shots=1000)
result = job.result()
counts = result.get_counts()

plot_histogram(counts)