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,))
@rt
def index():
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."))))
@rt
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)]])
}