chore(ui): setup dependencies

This commit is contained in:
Kristofers Solo 2025-04-24 15:02:09 +03:00
parent 995e8ff45d
commit 50e843a884
Signed by: kristoferssolo
GPG Key ID: 74FF8144483D82C8
6 changed files with 135 additions and 93 deletions

View File

@ -5,20 +5,21 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"argparse>=1.4.0", "argparse==1.4.0",
"numpy>=2.2.4", "numpy==2.2.4",
"qiskit-aer>=0.17.0", "pyqt6==6.9.0",
"qiskit[visualization]>=2.0.0", "qiskit-aer==0.17.0",
"qiskit[visualization]==2.0.0",
] ]
[project.scripts] [project.scripts]
grovers-visualizer = "grovers_visualizer.main:main" grovers-visualizer = "grovers_visualizer.main:main"
[project.optional-dependencies] [project.optional-dependencies]
mpl = ["pyqt6>=6.9.0"] ui = ["dearpygui==2.0.0"]
[dependency-groups] [dependency-groups]
dev = ["mypy>=1.15.0", "ruff>=0.11.4"] dev = ["mypy~=1.15", "ruff~=0.11"]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]

View File

@ -0,0 +1,79 @@
from math import asin, sqrt
from typing import Callable
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 .circuit import diffusion, oracle
from .parse import Args
from .plot import SinePlotData, plot_amplitudes, plot_circle, plot_sine
from .utils import all_states, optimal_grover_iterations
def run_cli(args: Args) -> None:
target_state = args.target
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()
fig = plt.figure(figsize=(14, 6))
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
fig.canvas.mpl_disconnect(cid)
plt.ioff()
if __name__ == "__main__":
main()

View File

@ -6,84 +6,14 @@ simulation using Qiskit's Aer simulator, and visualizes the results
using matplotlib. using matplotlib.
""" """
from math import asin, sqrt from .cli import run_cli
from typing import Callable from .parse import parse_args
from .ui import run_dpg_ui
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 grovers_visualizer.circuit import diffusion, oracle
from grovers_visualizer.parse import parse_args
from grovers_visualizer.plot import SinePlotData, plot_amplitudes, plot_circle, plot_sine
from grovers_visualizer.utils import all_states, optimal_grover_iterations
def main() -> None: def main() -> None:
args = parse_args() args = parse_args()
if args.ui:
target_state = args.target run_dpg_ui(args)
n_qubits = len(target_state) else:
basis_states = [str(bit) for bit in all_states(n_qubits)] run_cli(args)
optimal_iterations = optimal_grover_iterations(n_qubits)
theta = 2 * asin(1 / sqrt(2**n_qubits))
state_angle = 0.5 * theta
plt.ion()
fig = plt.figure(figsize=(14, 6))
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
fig.canvas.mpl_disconnect(cid)
plt.ioff()
if __name__ == "__main__":
main()

View File

@ -9,6 +9,7 @@ class Args:
target: QubitState target: QubitState
iterations: int iterations: int
speed: float speed: float
ui: bool
def parse_args() -> Args: def parse_args() -> Args:
@ -32,9 +33,11 @@ def parse_args() -> Args:
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("--ui", action="store_true", help="Run with DearPyGui UI")
ns = parser.parse_args() ns = parser.parse_args()
return Args( return Args(
target=QubitState.from_str(ns.target), target=QubitState.from_str(ns.target),
iterations=ns.iterations, iterations=ns.iterations,
speed=ns.speed, speed=ns.speed,
ui=ns.ui,
) )

View File

@ -0,0 +1,16 @@
from importlib.util import find_spec
from .parse import Args
def is_dearpygui_available() -> bool:
try:
return find_spec("dearpygui.dearpygui") is not None
except ModuleNotFoundError:
return False
def run_dpg_ui(_args: Args) -> None:
if not is_dearpygui_available():
print("DearPyGui is not installed. Install with: pip install .[ui]")
return

33
uv.lock
View File

@ -51,6 +51,17 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
] ]
[[package]]
name = "dearpygui"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/58/4e1744d2af72a3aa432fd82aeb97dcbc43351b17f837d879b4c4926bd16c/dearpygui-2.0.0-cp313-cp313-macosx_10_6_x86_64.whl", hash = "sha256:3bf0aa19baedb4f130b8de636d4644740ffd9b7008481794c574bf59240808dc", size = 2223494 },
{ url = "https://files.pythonhosted.org/packages/70/e0/09885f99df476d03e573183a755dc515cd88565cdbf036c385f0dda41dbb/dearpygui-2.0.0-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:ae6fc9aa3390b29387c2bc2bf3ac7a92c10eae2b479818171864fc29a7de344e", size = 1873562 },
{ url = "https://files.pythonhosted.org/packages/d6/2b/0714feed9e072377d575cb8801ac13a909f7880ca51c9dd6f2d768bcd1ba/dearpygui-2.0.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:f02b0ab56700a775d7e6446e3c424d5bed3386efe721a04518d02461851daadf", size = 2636153 },
{ url = "https://files.pythonhosted.org/packages/55/9e/a957ffc21d12a1fb66d611f82871944565112b44afa0856bb15c6ece77d5/dearpygui-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e3d52057f49773b10808962806711c3b3119e829d36407afb84ad50522edc9b0", size = 1814308 },
]
[[package]] [[package]]
name = "dill" name = "dill"
version = "0.3.9" version = "0.3.9"
@ -84,13 +95,14 @@ source = { editable = "." }
dependencies = [ dependencies = [
{ name = "argparse" }, { name = "argparse" },
{ name = "numpy" }, { name = "numpy" },
{ name = "pyqt6" },
{ name = "qiskit", extra = ["visualization"] }, { name = "qiskit", extra = ["visualization"] },
{ name = "qiskit-aer" }, { name = "qiskit-aer" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
mpl = [ ui = [
{ name = "pyqt6" }, { name = "dearpygui" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@ -101,18 +113,19 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "argparse", specifier = ">=1.4.0" }, { name = "argparse", specifier = "==1.4.0" },
{ name = "numpy", specifier = ">=2.2.4" }, { name = "dearpygui", marker = "extra == 'ui'", specifier = "==2.0.0" },
{ name = "pyqt6", marker = "extra == 'mpl'", specifier = ">=6.9.0" }, { name = "numpy", specifier = "==2.2.4" },
{ name = "qiskit", extras = ["visualization"], specifier = ">=2.0.0" }, { name = "pyqt6", specifier = "==6.9.0" },
{ name = "qiskit-aer", specifier = ">=0.17.0" }, { name = "qiskit", extras = ["visualization"], specifier = "==2.0.0" },
{ name = "qiskit-aer", specifier = "==0.17.0" },
] ]
provides-extras = ["mpl"] provides-extras = ["ui"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "mypy", specifier = ">=1.15.0" }, { name = "mypy", specifier = "~=1.15" },
{ name = "ruff", specifier = ">=0.11.4" }, { name = "ruff", specifier = "~=0.11" },
] ]
[[package]] [[package]]