from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import plot_bloch_multivector, array_to_latex
from math import pi

x_gate = QuantumCircuit(1) # Create a quantum circuit with 1 qubit
x_gate.x(0)
x_gate.draw(output='mpl')

backend = Aer.get_backend('statevector_simulator')
result = execute(x_gate, backend).result().get_statevector()
plot_bloch_multivector(result)

# Let's do an H-gate on a |0> qubit
h_gate = QuantumCircuit(1)
h_gate.h(0)
h_gate.draw(output='mpl')

# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(h_gate, backend).result().get_statevector()
plot_bloch_multivector(result)

# To make the problem more interesting, let's do an P-gate on a |+> state
p_gate = QuantumCircuit(1)
p_gate.h(0)
p_gate.p(pi/2, 0)
p_gate.draw(output='mpl')

# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(p_gate, backend).result().get_statevector()
plot_bloch_multivector(result)

array_to_latex(result)

cx_gate = QuantumCircuit(2)
cx_gate.cx(0,1)
cx_gate.draw(output='mpl')

# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(cx_gate, backend).result().get_statevector()
plot_bloch_multivector(result)

circuit = QuantumCircuit(2)
##### ==================================
# Write your solution in here.
circuit.x(1)
circuit.cx(0, 1)
##### ==================================
backend = Aer.get_backend('statevector_simulator')
result = execute(circuit, backend).result().get_statevector()
plot_bloch_multivector(result)

circuit = QuantumCircuit(2)
##### ==================================
# Write your solution in here.
circuit.x(0)
circuit.x(1)
circuit.cx(0, 1)
##### ==================================
backend = Aer.get_backend('statevector_simulator')
result = execute(circuit, backend).result().get_statevector()
plot_bloch_multivector(result)

ccx_gate = QuantumCircuit(3)
ccx_gate.ccx(0,1,2)
ccx_gate.draw(output='mpl')

def encode_integer(circuit, qubit, integer):
    ##### ==================================
    # Write your solution in here.
    if integer == 1:
        circuit.x(qubit)
    ##### ==================================
    return circuit

def classical_add(a, b):
    assert a in [0, 1]
    assert b in [0, 1]
    circuit = QuantumCircuit(2)
    ##### ==================================
    # Write your solution in here.
    encode_integer(circuit, 0, a)
    encode_integer(circuit, 1, b)
    circuit.cx(0, 1)
    ##### ==================================
    return circuit

# You may want to the following code to test your results
this_circ = classical_add(a=0, b=1)
backend = Aer.get_backend('statevector_simulator')
result = execute(this_circ, backend).result().get_statevector()
plot_bloch_multivector(result)

def classical_add_with_carry(a, b, carry):
    assert a in [0, 1]
    assert b in [0, 1]
    assert carry in [0, 1]
    circuit = QuantumCircuit(3)
    ##### ==================================
    # Write your solution in here.
    encode_integer(circuit, 0, carry)
    encode_integer(circuit, 1, a)
    encode_integer(circuit, 2, b)
    circuit.cx(0, 2)
    circuit.cx(1, 2)
    ##### ==================================
    return circuit

# You may want to the following code to test your results
this_circ = classical_add_with_carry(a=1, b=0, carry=1)
backend = Aer.get_backend('statevector_simulator')
result = execute(this_circ, backend).result().get_statevector()
plot_bloch_multivector(result)

def encode_integer_to_phase(circuit, qubit):
    ##### ==================================
    # Write your solution in here. Remember, before encoding to phases, 
    # your integers are already encoded as quantum state |0> and |1>
    circuit.h(qubit)
    ##### ==================================
    return circuit

def phase_addition(circuit, incoming_qubit, recipient_qubit):
    ##### ==================================
    # Write your solution in here.
    circuit.cp(pi, incoming_qubit, recipient_qubit)
    ##### ==================================
    return circuit

# You may want to the following code to test your results
a = 0; b = 1;
this_circ = QuantumCircuit(2)
this_circ = encode_integer(this_circ, 0, a)
this_circ = encode_integer(this_circ, 1, b)
this_circ = encode_integer_to_phase(this_circ, 1)
this_circ = phase_addition(this_circ, 0, 1)
backend = Aer.get_backend('statevector_simulator')
result = execute(this_circ, backend).result().get_statevector()
plot_bloch_multivector(result)

def decode_phase_to_integer(circuit, qubit):
    ##### ==================================
    # Write your solution in here. 
    circuit.h(qubit)
    ##### ==================================
    return circuit

def quantum_add(a, b):
    assert a in [0, 1]
    assert b in [0, 1]
    circuit = QuantumCircuit(2)
    ##### ==================================
    # Write your solution in here.
    circuit = QuantumCircuit(2)
    circuit = encode_integer(circuit, 0, a)
    circuit = encode_integer(circuit, 1, b)
    circuit = encode_integer_to_phase(circuit, 1)
    circuit = phase_addition(circuit, 0, 1)
    circuit = decode_phase_to_integer(circuit, 1)
    ##### ==================================
    return circuit

# You may want to the following code to test your results
this_circ = quantum_add(a=1, b=0)
backend = Aer.get_backend('statevector_simulator')
result = execute(this_circ, backend).result().get_statevector()
plot_bloch_multivector(result)

import qiskit.tools.jupyter
%qiskit_version_table

