# Bloch Spheres
In classical computing a bit can be only 0 or 1. In quantum computing a quantum bit (qubit) is a vector with 2 complex numbers, which can be visualized in 3D. Like in classical computing, gates are used to change the qubit state, but in quantum computing gates act as rotations.
This visualization lets you explore quantum gates and learn how to control qubits. This is an essential skill for programming quantum computers.
Source Code
import numpy as np
from fasthtml.common import *
import plotly.graph_objects as go
from fh_plotly import plotly2fasthtml, plotly_headers
########################
### FastHTML Section ###
########################
app, rt = fast_app(hdrs=plotly_headers)
@app.get('/')
def homepage():
desc = """
The Bloch Sphere is a 3D visualization of a single quantum state.
You can interact with the buttons (Gates) to see how the state changes. See the description below for more information on what each gate represents.
"""
hx_vals = 'js:{"gates": document.getElementById("quantum_circuit").textContent}'
return (Title("Interactive Bloch Sphere"),
Main(P(desc),
*[Button(gate, hx_get=f"/apply_gate/{gate}", hx_target="#chart", hx_swap="innerHTML", hx_vals=hx_vals, title=f"Apply {gate} gate") for gate in single_qubit_gates.keys()],
Button("Reset", hx_get="/reset", hx_target="#chart", hx_swap="innerHTML", title="Reset the circuit"),
Div(update_state_apply_gate.__wrapped__(), id="chart"),
H4("Available gates"),
Ul(Li(Strong("H :"),"Hadamard gate. Puts the state in superposition. "),
Li(Strong("X :"),"Pauli-X (NOT) gate. Rotate 180 degrees around the X-Axis."),
Li(Strong("Y :"),"Pauli-Y (\"bit-flip\") gate. Rotate 180 degrees around the Y-Axis."),
Li(Strong("Z :"),"Pauli-Z (\"phase-flip\") gate. Rotate 180 degrees around the Z-Axis."),
Li(Strong("S :"),"Phase gate. Rotates around the Z-axis by 90 degrees."),
Li(Strong("T :"),"π/8 gate. Rotates around the Z-axis by 45 degrees."))))
@app.get('/reset')
def reset(): return update_state_apply_gate.__wrapped__()
@app.get('/apply_gate/{gate}')
def update_state_apply_gate(gate: str=None, gates: str=None):
if gates is None: gates = []
else:
# Transform from circuit representation to gate names
gates = [g for g in gates if g in single_qubit_gates.keys()]
gates.append(gate)
# Create initial state
state = np.array([1, 0]) # |0> basis state
for gate in gates: state = single_qubit_gates[gate] @ state
# Create visualization
return Div(plot_bloch(state),
H4(f"Quantum Circuit: {visualize_circuit(gates)}", id="quantum_circuit"),
id="chart")
def visualize_circuit(gates: list[str]):
circuit = "|0⟩-"
circuit += "".join([f"[{gate}]─" for gate in gates]) + "|"
return circuit
############################
### Math/Quantum Section ###
############################
def calculate_coordinates(theta, phi):
x = np.sin(theta) * np.cos(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(theta)
return x, y, z
def create_scenes():
axis2ticktext = {'X': ['|-⟩', '|+⟩'], 'Y': ['|-i⟩', '|i⟩'], 'Z': ['|1⟩', '|0⟩']}
scenes = {}
for axis in ['X','Y','Z']:
scenes[f'{axis.lower()}axis'] = dict(title=dict(text=axis, font=dict(size=25)),
range=[-1.2, 1.2], tickvals=[-1, 1],
ticktext=axis2ticktext[axis],
tickfont=dict(size=15) )
return scenes
def plot_bloch(state: np.array):
fig = go.Figure()
# State vector coordinates
alpha, beta = state[0], state[1]
theta = 2 * np.arccos(np.abs(alpha))
phi = np.angle(beta) - np.angle(alpha)
x, y, z = calculate_coordinates(theta, phi)
# Surface coordinates
surface_phi, surface_theta = np.mgrid[0:2*np.pi:100j, 0:np.pi:50j]
xs, ys, zs = calculate_coordinates(surface_theta, surface_phi)
fig.add_trace(go.Surface(x=xs, y=ys, z=zs, opacity=0.5, colorscale='Blues', showscale=False))
fig.add_trace(go.Scatter3d(x=[0, x],y=[0, y],z=[0, z], mode='lines+markers+text', marker=dict(size=10, color='green'),
line=dict(color='green', width=8), textposition="top center", showlegend=True,name=f"{alpha:.2f}|0⟩ + {beta:.2f}|1⟩"))
# Mark basis states
fig.add_trace(go.Scatter3d(x=[0, 0, 1, -1, 0, 0],y=[0, 0, 0, 0, 1, -1], z=[1, -1, 0, 0, 0, 0],
mode='markers', marker=dict(size=5, color='black'), hovertext=['|0⟩', '|1⟩', '|+⟩', '|-⟩', '|i⟩', '|-i⟩'],
showlegend=False, name="Basis states"))
# Add lines across axes
boundary_phi = np.linspace(0, 2 * np.pi, 100)
coords = [(np.cos(boundary_phi), np.sin(boundary_phi), np.zeros_like(boundary_phi)),
(np.zeros_like(boundary_phi), np.cos(boundary_phi), np.sin(boundary_phi)),
(np.cos(boundary_phi), np.zeros_like(boundary_phi), np.sin(boundary_phi)) ]
for x, y, z in coords:
fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode='lines', line=dict(color='black', width=2), showlegend=False, name="Axes"))
fig.update_layout(scene=dict(**create_scenes(), aspectmode='cube',),
legend=dict( font=dict(size=20), x=0.05,y=0.95, xanchor='left', yanchor='top', bgcolor='rgba(0,0,0,0)',),
margin=dict(l=0, r=0, t=0, b=0))
return plotly2fasthtml(fig)
single_qubit_gates = {
# Hadamard
"H": np.array([[1, 1],
[1, -1]]) / np.sqrt(2),
# Pauli matrices
"X": np.array([[0, 1],
[1, 0]]),
"Y": np.array([[0, -1j],
[1j, 0]]),
"Z": np.array([[1, 0],
[0, -1]]),
# Phase gates
"S": np.array([[1, 0],
[0, 1j]]),
"T": np.array([[1, 0],
[0, np.exp(1j * np.pi / 4)]])
}