from qiskit import QuantumCircuit, assemble, Aer # Qiskit features imported
from qiskit.visualization import plot_bloch_multivector, plot_histogram # For visualization
from qiskit.quantum_info import Statevector # Intermediary statevector saving
import numpy as np # Numpy math library
sim = Aer.get_backend('aer_simulator') # Simulation that this notebook relies on
### For real quantum computer backend, replace Aer.get_backend with backend object of choice

# Widgets for value sliders
import ipywidgets as widgets
from IPython.display import display

# Value sliders
theta = widgets.FloatSlider(
    value=0,
    min=0,
    max=180,
    step=0.5,
    description='θ',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)

phi = widgets.FloatSlider(
    value=0,
    min=0,
    max=360,
    step=0.5,
    description='φ',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)

display(theta) # Zenith angle in degrees
display(phi)   # Azimuthal angle in degrees
## Careful! These act as zenith and azimuthal only when applied to |0> statevector in rx(theta), rz(phi) order

qc = QuantumCircuit(1) # 1 qubit quantum circuit, every qubits starts at |0>
qc.rx(theta.value/180.0*np.pi,0) # Rotation about Bloch vector x-axis
qc.rz(phi.value/180.0*np.pi,0)   # Rotation about Bloch vector y-axis
qc.save_statevector() # Get final statevector after measurement
qc.measure_all() # Measurement: collapses the statevector to either |0> or |1>
qobj = assemble(qc) # Quantum circuit saved to an object
interStateV = sim.run(qobj).result().get_statevector()
finalStates = sim.run(qobj).result().get_counts()
qc.draw()

plot_bloch_multivector(interStateV) # Plot intermediary statevector after two gates on Bloch sphere

plot_histogram(finalStates) # Probability of |0> or |1> from measuring above statevector