feat(circle): add triangulation circle

This commit is contained in:
Kristofers Solo 2025-04-22 18:14:34 +03:00
parent 6346349266
commit 8aedc59760

View File

@ -8,20 +8,24 @@ using matplotlib.
from collections.abc import Iterator from collections.abc import Iterator
from itertools import product from itertools import product
from math import floor, pi, sqrt from math import asin, cos, floor, pi, sin, sqrt
from typing import Callable from typing import TYPE_CHECKING, Callable
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
from matplotlib.axes import Axes from matplotlib.axes import Axes
from matplotlib.container import BarContainer from matplotlib.container import BarContainer
from matplotlib.patches import Circle
from qiskit import QuantumCircuit from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector from qiskit.quantum_info import Statevector
from grovers_visualizer.gates import apply_phase_inversion, encode_target_state from grovers_visualizer.gates import apply_phase_inversion, encode_target_state
from grovers_visualizer.state import QubitState from grovers_visualizer.state import QubitState
if TYPE_CHECKING:
from matplotlib.figure import Figure
def oracle(qc: QuantumCircuit, target_state: QubitState) -> None: def oracle(qc: QuantumCircuit, target_state: QubitState) -> None:
"""Oracle that flips the sign of the target state.""" """Oracle that flips the sign of the target state."""
@ -84,11 +88,15 @@ def optimal_grover_iterations(n_qubits: int) -> int:
return floor(pi / 4 * sqrt(2**n_qubits)) return floor(pi / 4 * sqrt(2**n_qubits))
def is_optimal_iteration(iteration: int, optimal_iteration: int) -> bool:
return iteration % optimal_iteration == 0 and iteration != 0
def get_bar_color(state: str, target_state: QubitState | None, iteration: int, optimal_iteration: int | None) -> str: def get_bar_color(state: str, target_state: QubitState | None, iteration: int, optimal_iteration: int | None) -> str:
"""Return the color for a bar based on state and iteration.""" """Return the color for a bar based on state and iteration."""
if state != target_state: if state != target_state:
return "skyblue" return "skyblue"
if optimal_iteration and iteration % optimal_iteration == 0 and iteration != 0: if optimal_iteration and is_optimal_iteration(iteration, optimal_iteration):
return "green" return "green"
return "orange" return "orange"
@ -98,7 +106,7 @@ def plot_amplitudes_live(
bars: BarContainer, bars: BarContainer,
statevector: Statevector, statevector: Statevector,
basis_states: list[str], basis_states: list[str],
step_label: str, iteration_label: str,
iteration: int, iteration: int,
target_state: QubitState | None = None, target_state: QubitState | None = None,
optimal_iteration: int | None = None, optimal_iteration: int | None = None,
@ -110,7 +118,7 @@ def plot_amplitudes_live(
bar.set_height(amp) bar.set_height(amp)
bar.set_color(get_bar_color(state, target_state, iteration, optimal_iteration)) bar.set_color(get_bar_color(state, target_state, iteration, optimal_iteration))
ax.set_title(f"Iteration {iteration}: {step_label}") ax.set_title(f"Iteration {iteration}: {iteration_label}")
ax.set_ylim(-1, 1) ax.set_ylim(-1, 1)
for l in ax.lines: # Remove previous mean line(s) for l in ax.lines: # Remove previous mean line(s)
@ -120,7 +128,50 @@ def plot_amplitudes_live(
if not ax.get_legend(): if not ax.get_legend():
ax.legend(loc="upper right") ax.legend(loc="upper right")
plt.pause(1)
def draw_grover_circle(
ax: Axes,
iteration: int,
optimal_iterations: int,
theta: float,
state_angle: float,
) -> None:
ax.clear()
ax.set_aspect("equal")
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.set_xlabel("Unmarked amplitude")
ax.set_ylabel("Target amplitude")
ax.set_title("Grover State Vector Rotation")
# Draw unit circle
circle = Circle((0, 0), 1, color="gray", fill=False)
ax.add_artist(circle)
# Draw axes
ax.axhline(0, color="black", linewidth=0.5)
ax.axvline(0, color="black", linewidth=0.5)
# Draw labels
ax.text(1.05, 0, "", va="center", ha="left", fontsize=10)
ax.text(0, 1.05, "1", va="bottom", ha="center", fontsize=10)
ax.text(-1.05, 0, "", va="center", ha="right", fontsize=10)
ax.text(0, -1.05, "-1", va="top", ha="center", fontsize=10)
angle = state_angle + iteration * theta
x, y = cos(angle), sin(angle)
is_optimal = optimal_iterations and is_optimal_iteration(iteration, optimal_iterations)
# Arrow color: green at optimal, blue otherwise
color = "green" if is_optimal else "blue"
ax.arrow(0, 0, x, y, head_width=0.07, head_length=0.1, fc=color, ec=color, length_includes_head=True)
# Probability of target state is y^2
prob = y**2
ax.set_title(
f"Grover State Vector Rotation\nIteration {iteration} | Probability of target: {prob:.2f}{' (optimal)' if is_optimal else ''}"
)
def main() -> None: def main() -> None:
@ -128,16 +179,19 @@ def main() -> None:
n_qubits = len(target_state) n_qubits = len(target_state)
basis_states = [str(bit) for bit in all_states(n_qubits)] basis_states = [str(bit) for bit in all_states(n_qubits)]
optimal_iterations = optimal_grover_iterations(n_qubits) optimal_iterations = optimal_grover_iterations(n_qubits)
theta = 2 * asin(1 / sqrt(2**n_qubits))
state_angle = 0.5 * theta
plt.ion() plt.ion()
fig, ax = plt.subplots(figsize=(8, 3)) fig: Figure
bars = ax.bar(basis_states, [0] * len(basis_states), color="skyblue") ax_bar: Axes
ax.set_xlabel("Basis State") ax_circle: Axes
ax.set_ylabel("Real Amplitude") fig, (ax_bar, ax_circle) = plt.subplots(1, 2, figsize=(12, 4))
ax.set_ylim(-1, 1) bars = ax_bar.bar(basis_states, [0] * len(basis_states), color="skyblue")
ax.set_title("Grover Amplitudes") ax_bar.set_ylim(-1, 1)
ax_bar.set_title("Amplitudes (example)")
def step_and_plot( def iterate_and_plot(
operation: Callable[[QuantumCircuit], None] | None, operation: Callable[[QuantumCircuit], None] | None,
step_label: str, step_label: str,
iteration: int, iteration: int,
@ -145,17 +199,21 @@ def main() -> None:
if operation is not None: if operation is not None:
operation(qc) operation(qc)
sv = Statevector.from_instruction(qc) sv = Statevector.from_instruction(qc)
plot_amplitudes_live(ax, bars, sv, basis_states, step_label, iteration, target_state, optimal_iterations) plot_amplitudes_live(ax_bar, bars, sv, basis_states, step_label, iteration, target_state, optimal_iterations)
draw_grover_circle(ax_circle, iteration, optimal_iterations, theta, state_angle)
plt.pause(1)
# Start with Hadamard # Start with Hadamard
qc = QuantumCircuit(n_qubits) qc = QuantumCircuit(n_qubits)
qc.h(range(n_qubits)) qc.h(range(n_qubits))
step_and_plot(None, "Hadamard (Initialization)", 0) iterate_and_plot(None, "Hadamard (Initialization)", 0)
iteration = 1 iteration = 1
while plt.fignum_exists(fig.number): while plt.fignum_exists(fig.number):
step_and_plot(lambda qc: oracle(qc, target_state), "Oracle (Query Phase)", iteration) iterate_and_plot(lambda qc: oracle(qc, target_state), "Oracle (Query Phase)", iteration)
step_and_plot(lambda qc: diffusion(qc, n_qubits), "Diffusion (Inversion Phase)", iteration) iterate_and_plot(lambda qc: diffusion(qc, n_qubits), "Diffusion (Inversion Phase)", iteration)
iteration += 1 iteration += 1
plt.ioff() plt.ioff()