Compare commits

...

5 Commits
v0.4.2 ... main

Author SHA1 Message Date
89e12233f0
docs: update readme 2025-05-15 09:39:54 +03:00
a405df416c
docs: update readme 2025-05-08 18:55:18 +03:00
30cc848ecd
feat(cli): add phase arg 2025-05-08 18:49:39 +03:00
78952d9364
feat(circuits): improve runtime speed 2025-05-08 17:12:58 +03:00
f4b99262ec
refactor(cli): refactor CLI into modular simulation and visualization components
- Extract Grover iteration logic into simulation.py (grover_evolver generator)
- Move all Matplotlib setup/update/teardown into visualization.py (GroverVisualizer)
2025-05-08 14:11:18 +03:00
17 changed files with 334 additions and 283 deletions

View File

@ -6,8 +6,6 @@ A tiny Python package that steps through Grovers Search algorithm and shows y
- A sinecurve of successprobability vs. iteration - A sinecurve of successprobability vs. iteration
- A geometric "rotation" on the unit circle - A geometric "rotation" on the unit circle
Choose between a Matplotlib-based CLI or an optional DearPyGui GUI (WIP).
--- ---
![Demo of Grovers Visualizer](media/demo.gif) ![Demo of Grovers Visualizer](media/demo.gif)
@ -16,69 +14,37 @@ Choose between a Matplotlib-based CLI or an optional DearPyGui GUI (WIP).
## Installation ## Installation
### Via [pipx](https://pipx.pypa.io/stable/installation/) ### Using [uv](https://docs.astral.sh/uv/)/[uvx](https://docs.astral.sh/uv/guides/tools/)
Basic (CLI only):
```bash
pipx install grovers-visualizer
grovers-visualizer 1111
```
With the optional DearPyGui UI (WIP):
```bash
pipx "grovers-visualizer[ui]"
grovers-visualizer --ui
```
### Via [uvx](https://docs.astral.sh/uv/guides/tools/)
Basic (CLI only):
```bash ```bash
uvx grovers-visualizer 1111 uvx grovers-visualizer 1111
``` ```
With the optional UI extra: ### Using [pip](https://pypi.org/project/pip/)/[pipx](https://pipx.pypa.io/stable/installation/)
```bash ```bash
uvx "grovers-visualizer[ui]" --ui pip grovers-visualizer
# or
pipx grovers-visualizer # (recommended)
# and then run
grovers-visualizer
``` ```
--- ---
## Usage ## Usage
### CLI Mode ### Flags
Flags: - `TARGET`
Target bitstring (e.g. `010`). Length also determines number of qubits.
`-t, --target` - `-i, --iterations ITERATIONS`
Target bitstring (e.g. `010`). Length determines number of qubits.
`-s, --speed`
Delay between iterations (seconds). Default `0.5`.
`-i, --iterations`
Max iterations; `0` means use the optimal $\lfloor\frac\pi4\sqrt{2^n}\rfloor$. Max iterations; `0` means use the optimal $\lfloor\frac\pi4\sqrt{2^n}\rfloor$.
`--ui` - `-s, --speed SPEED`
Launch the optional DearPyGui GUI (requires the `[ui]` extra) (WIP). Delay between iterations (seconds). Default `0.5`.
- `-p, --phase PHASE`
### GUI Mode (WIP) The phase $\psi$ (in radians) used for both the oracle and diffusion steps. Defaults to $\pi$ (i.e. a sign-flip, $e^{i\pi}=-1$).
If you installed with `"[ui]"`, launch the DearPyGui window:
```
grovers-visualizer --ui
```
In the UI you can:
- Set number of qubits
- Enter the target bitstring
- Choose max iterations or leave at 0 for optimal
- Control the animation speed
Hit **Start** to watch the bar chart, sine plot, and rotationcircle update in real time.
--- ---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1,6 +1,6 @@
[project] [project]
name = "grovers-visualizer" name = "grovers-visualizer"
version = "0.4.2" version = "0.5.0"
description = "A tiny Python package that steps through Grovers Search algorithm." description = "A tiny Python package that steps through Grovers Search algorithm."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@ -1,3 +1,4 @@
import math
from argparse import ArgumentParser from argparse import ArgumentParser
from dataclasses import dataclass from dataclasses import dataclass
@ -11,6 +12,7 @@ class Args:
iterations: int iterations: int
speed: float speed: float
ui: bool ui: bool
phase: float
def parse_args() -> Args: def parse_args() -> Args:
@ -26,6 +28,7 @@ def parse_args() -> Args:
iterations=ns.iterations, iterations=ns.iterations,
speed=ns.speed, speed=ns.speed,
ui=ns.ui, ui=ns.ui,
phase=ns.phase,
) )
@ -61,4 +64,15 @@ def parse_cli(base_parser: ArgumentParser) -> None:
default=0.5, default=0.5,
help="Pause duration (seconds) between steps (deafult: 0.5)", help="Pause duration (seconds) between steps (deafult: 0.5)",
) )
parser.add_argument(
"-p",
"--phase",
type=float,
default=math.pi,
help=(
"The phase φ (in radians) used for the oracle and diffusion steps. "
"Defaults to π, which implements the usual sign flip e^(iπ) = -1."
),
)
parser.add_argument("--ui", action="store_true", help="Run with DearPyGui UI") parser.add_argument("--ui", action="store_true", help="Run with DearPyGui UI")

View File

@ -1,45 +1,41 @@
from math import floor, pi, sqrt import math
from qiskit import QuantumCircuit from qiskit import QuantumCircuit
from qiskit.circuit.library import PhaseGate
from .state import QubitState from .state import QubitState
def grover_search(target_state: QubitState, iterations: int | None = None) -> QuantumCircuit: def oracle(qc: QuantumCircuit, target_state: QubitState, /, *, phase: float = math.pi) -> None:
"""Construct a Grover search circuit for the given target state."""
n = len(target_state)
qc = QuantumCircuit(n, n)
qc.h(range(n))
if iterations is None or iterations < 0:
iterations = floor(pi / 4 * sqrt(2**n))
for _ in range(iterations):
oracle(qc, target_state)
diffusion(qc, n)
qc.measure(range(n), range(n))
return qc
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."""
n = len(target_state) n = len(target_state)
encode_target_state(qc, target_state) encode_target_state(qc, target_state)
apply_phase_inversion(qc, n) apply_phase_inversion(qc, n, phase=phase)
encode_target_state(qc, target_state) # Undo encode_target_state(qc, target_state) # Undo
def diffusion(qc: QuantumCircuit, n: int) -> None: def oracle_circuit(target: QubitState, /, *, phase: float = math.pi) -> QuantumCircuit:
n = len(target)
qc = QuantumCircuit(n)
oracle(qc, target, phase=phase)
return qc
def diffusion(qc: QuantumCircuit, n: int, /, *, phase: float = math.pi) -> None:
"""Apply the Grovers diffusion operator.""" """Apply the Grovers diffusion operator."""
qc.h(range(n)) qc.h(range(n))
qc.x(range(n)) qc.x(range(n))
apply_phase_inversion(qc, n) apply_phase_inversion(qc, n, phase=phase)
qc.x(range(n)) qc.x(range(n))
qc.h(range(n)) qc.h(range(n))
def diffusion_circuit(n: int, /, *, phase: float = math.pi) -> QuantumCircuit:
qc = QuantumCircuit(n)
diffusion(qc, n, phase=phase)
return qc
def encode_target_state(qc: QuantumCircuit, target_state: QubitState) -> None: def encode_target_state(qc: QuantumCircuit, target_state: QubitState) -> None:
"""Apply X gates to qubits where the target state bit is '0'.""" """Apply X gates to qubits where the target state bit is '0'."""
for i, bit in enumerate(reversed(target_state)): for i, bit in enumerate(reversed(target_state)):
@ -47,11 +43,10 @@ def encode_target_state(qc: QuantumCircuit, target_state: QubitState) -> None:
qc.x(i) qc.x(i)
def apply_phase_inversion(qc: QuantumCircuit, n: int) -> None: def apply_phase_inversion(qc: QuantumCircuit, n: int, /, *, phase: float = math.pi) -> None:
"""Apply a multi-controlled phase inversion (Z) to the marked state.""" """Apply a multi-controlled phase inversion (Z) to the marked state."""
if n == 1: if n == 1:
qc.z(0) qc.p(phase, 0)
return return
qc.h(n - 1) mc_phase = PhaseGate(phase).control(n - 1)
qc.mcx(list(range(n - 1)), n - 1) qc.append(mc_phase, list(range(n)))
qc.h(n - 1)

View File

@ -1,75 +1,15 @@
from math import asin, sqrt from grovers_visualizer.simulation import grover_evolver
from typing import Callable from grovers_visualizer.visualization import GroverVisualizer
import matplotlib.pyplot as plt
from matplotlib.backend_bases import KeyEvent
from matplotlib.gridspec import GridSpec
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from .args import Args from .args import Args
from .circuit import diffusion, oracle
from .plot import SinePlotData, plot_amplitudes, plot_circle, plot_sine
from .utils import all_states, optimal_grover_iterations
def run_cli(args: Args) -> None: def run_cli(args: Args) -> None:
target_state = args.target vis = GroverVisualizer(args.target, pause=args.speed)
n_qubits = len(target_state)
basis_states = [str(bit) for bit in all_states(n_qubits)]
optimal_iterations = optimal_grover_iterations(n_qubits)
theta = 2 * asin(1 / sqrt(2**n_qubits))
state_angle = 0.5 * theta
plt.ion() for it, sv in grover_evolver(vis.target, args.iterations, phase=args.phase):
fig = plt.figure(figsize=(14, 6)) if not vis.is_running:
gs = GridSpec(2, 2, width_ratios=(3, 1), figure=fig)
ax_bar = fig.add_subplot(gs[0, 0])
ax_sine = fig.add_subplot(gs[1, 0])
ax_circle = fig.add_subplot(gs[:, 1])
bars = ax_bar.bar(basis_states, [0] * len(basis_states), color="skyblue")
ax_bar.set_ylim(-1, 1)
ax_bar.set_title("Amplitudes (example)")
sine_data = SinePlotData()
def plot_bar(
operation: Callable[[QuantumCircuit], None] | None,
step_label: str,
iteration: int,
) -> None:
if operation is not None:
operation(qc)
sv = Statevector.from_instruction(qc)
plot_amplitudes(ax_bar, bars, sv, basis_states, step_label, iteration, target_state, optimal_iterations)
# Start with Hadamard
qc = QuantumCircuit(n_qubits)
qc.h(range(n_qubits))
plot_bar(None, "Hadamard (Initialization)", 0)
iteration = 1
running = True
def on_key(event: KeyEvent) -> None:
nonlocal running
if event.key == "q":
running = False
cid = fig.canvas.mpl_connect("key_press_event", on_key)
while plt.fignum_exists(fig.number) and running:
plot_bar(lambda qc: oracle(qc, target_state), "Oracle (Query Phase)", iteration)
plot_bar(lambda qc: diffusion(qc, n_qubits), "Diffusion (Inversion Phase)", iteration)
plot_circle(ax_circle, iteration, optimal_iterations, theta, state_angle)
sine_data.calc_and_append_probability(iteration, theta)
plot_sine(ax_sine, sine_data)
plt.pause(args.speed)
iteration += 1
if args.iterations > 0 and iteration > args.iterations:
break break
vis.update(it, sv)
fig.canvas.mpl_disconnect(cid) vis.finalize()
plt.ioff()

View File

@ -1,129 +0,0 @@
from dataclasses import dataclass, field
from math import cos, sin
import numpy as np
import numpy.typing as npt
from matplotlib.axes import Axes
from matplotlib.container import BarContainer
from matplotlib.patches import Circle
from qiskit.quantum_info import Statevector
from .state import QubitState
from .utils import get_bar_color, is_optimal_iteration
def plot_amplitudes(
ax: Axes,
bars: BarContainer,
statevector: Statevector,
basis_states: list[str],
iteration_label: str,
iteration: int,
target_state: QubitState | None = None,
optimal_iteration: int | None = None,
) -> None:
amplitudes: npt.NDArray[np.float64] = statevector.data.real # Real part of amplitudes
mean = np.mean(amplitudes)
for bar, state, amp in zip(bars, basis_states, amplitudes, strict=False):
bar.set_height(amp)
bar.set_color(get_bar_color(state, target_state, iteration, optimal_iteration))
ax.set_title(f"Iteration {iteration}: {iteration_label}")
ax.set_ylim(-1, 1)
for l in ax.lines: # Remove previous mean line(s)
l.remove()
# Draw axes and mean
ax.axhline(0, color="black", linewidth=0.5)
ax.axhline(float(mean), color="red", linestyle="--", label="Mean")
if not ax.get_legend():
ax.legend(loc="upper right")
def plot_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 = 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
# Draw the value at the tip of the arrow
ax.text(
x,
y,
f"{prob:.2f}",
color=color,
fontsize=10,
ha="left" if x >= 0 else "right",
va="bottom" if y >= 0 else "top",
fontweight="bold",
bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.7, "boxstyle": "round,pad=0.2"},
)
ax.set_title(
f"Grover State Vector Rotation\nIteration {iteration} | Probability of target: {prob}{' (optimal)' if is_optimal else ''}"
)
@dataclass
class SinePlotData:
x: list[float] = field(default_factory=list)
y: list[float] = field(default_factory=list)
def append(self, x: float, y: float) -> None:
self.x.append(x)
self.y.append(y)
def calc_and_append_probability(self, iteration: int, theta: float) -> None:
prob = sin((2 * iteration + 1) * theta / 2) ** 2
self.append(iteration, prob)
def plot_sine(
ax: Axes,
sine_data: SinePlotData,
) -> None:
ax.clear()
ax.plot(sine_data.x, sine_data.y, marker="o", color="purple", label="Target Probability")
ax.set_xlabel("Iteration")
ax.set_ylabel("Probability")
ax.set_title("Grover Target Probability vs. Iteration")
ax.set_ylim(0, 1)
ax.set_xlim(0, max(10, max(sine_data.x) + 1))
ax.legend()

View File

@ -0,0 +1,5 @@
from .amplitudes import plot_amplitudes
from .circle import plot_circle
from .sine import SinePlotData, plot_sine
__all__ = ("SinePlotData", "plot_amplitudes", "plot_circle", "plot_sine")

View File

@ -0,0 +1,39 @@
import numpy as np
import numpy.typing as npt
from matplotlib.axes import Axes
from matplotlib.container import BarContainer
from qiskit.quantum_info import Statevector
from grovers_visualizer.state import QubitState
from grovers_visualizer.utils import get_bar_color
def plot_amplitudes(
ax: Axes,
bars: BarContainer,
statevector: Statevector,
basis_states: list[str],
iteration_label: str,
iteration: int,
target_state: QubitState | None = None,
optimal_iteration: int | None = None,
) -> None:
amplitudes: npt.NDArray[np.float64] = statevector.data.real # Real part of amplitudes
mean = np.mean(amplitudes)
for bar, state, amp in zip(bars, basis_states, amplitudes, strict=False):
bar.set_height(amp)
bar.set_color(get_bar_color(state, target_state, iteration, optimal_iteration))
ax.set_title(f"Iteration {iteration}: {iteration_label}")
ax.set_ylim(-1, 1)
for l in ax.lines: # Remove previous mean line(s)
l.remove()
# Draw axes and mean
ax.axhline(0, color="black", linewidth=0.5)
ax.axhline(float(mean), color="red", linestyle="--", label="Mean")
if not ax.get_legend():
ax.legend(loc="upper right")

View File

@ -0,0 +1,64 @@
from math import cos, sin
from matplotlib.axes import Axes
from matplotlib.patches import Circle
from grovers_visualizer.utils import is_optimal_iteration
def plot_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 = 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
# Draw the value at the tip of the arrow
ax.text(
x,
y,
f"{prob:.2f}",
color=color,
fontsize=10,
ha="left" if x >= 0 else "right",
va="bottom" if y >= 0 else "top",
fontweight="bold",
bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.7, "boxstyle": "round,pad=0.2"},
)
ax.set_title(
f"Grover State Vector Rotation\nIteration {iteration} | Probability of target: {prob}{' (optimal)' if is_optimal else ''}"
)

View File

@ -0,0 +1,32 @@
from dataclasses import dataclass, field
from math import sin
from matplotlib.axes import Axes
@dataclass
class SinePlotData:
x: list[float] = field(default_factory=list)
y: list[float] = field(default_factory=list)
def append(self, x: float, y: float) -> None:
self.x.append(x)
self.y.append(y)
def calc_and_append_probability(self, iteration: int, theta: float) -> None:
prob = sin((2 * iteration + 1) * theta / 2) ** 2
self.append(iteration, prob)
def plot_sine(
ax: Axes,
sine_data: SinePlotData,
) -> None:
ax.clear()
ax.plot(sine_data.x, sine_data.y, marker="o", color="purple", label="Target Probability")
ax.set_xlabel("Iteration")
ax.set_ylabel("Probability")
ax.set_title("Grover Target Probability vs. Iteration")
ax.set_ylim(0, 1)
ax.set_xlim(0, max(10, max(sine_data.x) + 1))
ax.legend()

View File

@ -0,0 +1,39 @@
import math
from collections.abc import Iterator
from itertools import count
from qiskit import QuantumCircuit
from qiskit.qasm2.parse import Operator
from qiskit.quantum_info import Statevector
from grovers_visualizer.circuit import diffusion_circuit, oracle_circuit
from grovers_visualizer.state import QubitState
def grover_evolver(
target: QubitState,
max_iterations: int = 0,
*,
phase: float = math.pi,
) -> Iterator[tuple[int, Statevector]]:
"""Yields (iteration, statevector) pairs.
- iteration=0 is the uniform-Hadamard initialization
- max_iterations > 0, stop after that many iterations
- max_iterations == 0, run indefinitely (until the consumer breaks)
"""
n_qubits = len(target)
qc = QuantumCircuit(n_qubits)
qc.h(range(n_qubits))
# initial statevector
sv = Statevector.from_instruction(qc)
yield 0, sv
oracle_op = Operator(oracle_circuit(target, phase=phase))
diffusion_op = Operator(diffusion_circuit(n_qubits, phase=phase))
iters = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
for i in iters:
sv = sv.evolve(oracle_op).evolve(diffusion_op)
yield i, sv

View File

@ -10,11 +10,11 @@ def is_dearpygui_available() -> bool:
return False return False
def run_dpg_ui(args: Args) -> None: def run_dpg_ui(_args: Args) -> None:
if not is_dearpygui_available(): if not is_dearpygui_available():
print("DearPyGui is not installed. Install with: pip install 'grovers-visualizer[ui]'") print("DearPyGui is not installed. Install with: pip install 'grovers-visualizer[ui]'")
return return
from .dpg import run_dearpygui_ui from .dpg import run_dearpygui_ui
run_dearpygui_ui(args) run_dearpygui_ui()

View File

@ -1,7 +1,9 @@
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
from grovers_visualizer.args import Args
def run_dearpygui_ui() -> None:
def run_dearpygui_ui(_args: Args) -> None:
dpg.create_context() dpg.create_context()
dpg.create_viewport(title="Grover's Search Visualizer", width=900, height=600) dpg.create_viewport(title="Grover's Search Visualizer", width=900, height=600)
dpg.setup_dearpygui() dpg.setup_dearpygui()

View File

@ -14,7 +14,7 @@ def all_states(n_qubits: int) -> Iterator[QubitState]:
def optimal_grover_iterations(n_qubits: int) -> int: def optimal_grover_iterations(n_qubits: int) -> int:
"""Return the optimal number of Grover iterations for n qubits.""" """Return the optimal number of Grover iterations for n qubits."""
return floor(pi / 4 * sqrt(2**n_qubits)) return floor(pi / 4 * sqrt(2.0**n_qubits))
def is_optimal_iteration(iteration: int, optimal_iteration: int) -> bool: def is_optimal_iteration(iteration: int, optimal_iteration: int) -> bool:

View File

@ -0,0 +1,84 @@
from math import asin, sqrt
from typing import TYPE_CHECKING
import matplotlib.pyplot as plt
from matplotlib.backend_bases import Event, KeyEvent
from matplotlib.gridspec import GridSpec
from qiskit.quantum_info import Statevector
from grovers_visualizer.plot import SinePlotData, plot_amplitudes, plot_circle, plot_sine
from grovers_visualizer.state import QubitState
from grovers_visualizer.utils import all_states, optimal_grover_iterations
if TYPE_CHECKING:
from matplotlib.axes import Axes
from matplotlib.container import BarContainer
from matplotlib.figure import Figure
class GroverVisualizer:
def __init__(self, target: QubitState, pause: float = 0.5) -> None:
self.target: QubitState = target
self.n: int = len(self.target)
self.basis_states: list[str] = [str(b) for b in all_states(self.n)]
self.optimal: int = optimal_grover_iterations(self.n)
self.theta: float = 2 * asin(1 / sqrt(2.0**self.n))
self.state_angle: float = 0.5 * self.theta
self.sine_data: SinePlotData = SinePlotData()
self.is_running: bool = True
self.pause: float = pause
self._build_figure()
def _build_figure(self) -> None:
plt.ion()
self.fig: Figure = plt.figure(figsize=(14, 6))
gs = GridSpec(2, 2, width_ratios=(3, 1), figure=self.fig)
self.ax_bar: Axes = self.fig.add_subplot(gs[0, 0])
self.ax_sine: Axes = self.fig.add_subplot(gs[1, 0])
self.ax_circle: Axes = self.fig.add_subplot(gs[:, 1])
# bars
self.bars: BarContainer = self.ax_bar.bar(self.basis_states, [0] * len(self.basis_states), color="skyblue")
self.ax_bar.set_ylim(-1, 1)
self.ax_bar.set_title("Amplitudes (example)")
# key handler to quit
self.cid: int = self.fig.canvas.mpl_connect("key_press_event", self._on_key)
def _on_key(self, event: Event) -> None:
if isinstance(event, KeyEvent) and event.key == "q":
self.is_running = False
def update(self, iteration: int, sv: Statevector) -> None:
"""Given (iteration, Statevector), update all three plots."""
# amplitudes
plot_amplitudes(
self.ax_bar,
self.bars,
sv,
self.basis_states,
"Grover Iteration",
iteration,
self.target,
self.optimal,
)
# circle
plot_circle(
self.ax_circle,
iteration,
self.optimal,
self.theta,
self.state_angle,
)
# sine curve
self.sine_data.calc_and_append_probability(iteration, self.theta)
plot_sine(self.ax_sine, self.sine_data)
plt.pause(self.pause)
def finalize(self) -> None:
"""Clean up after loop ends."""
self.fig.canvas.mpl_disconnect(self.cid)
plt.ioff()

View File

@ -155,7 +155,7 @@ wheels = [
[[package]] [[package]]
name = "grovers-visualizer" name = "grovers-visualizer"
version = "0.4.2" version = "0.5.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "numpy" }, { name = "numpy" },