mirror of
https://github.com/kristoferssolo/grovers-visualizer.git
synced 2025-10-21 20:10:35 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89e12233f0 | |||
| a405df416c | |||
| 30cc848ecd | |||
| 78952d9364 |
72
README.md
72
README.md
@ -2,11 +2,9 @@
|
||||
|
||||
A tiny Python package that steps through Grover’s Search algorithm and shows you, after each iteration:
|
||||
|
||||
- A bar‐chart of amplitudes (or probabilities)
|
||||
- A sine‐curve of success‐probability vs. iteration
|
||||
- A geometric "rotation" on the unit circle
|
||||
|
||||
Choose between a Matplotlib-based CLI or an optional DearPyGui GUI (WIP).
|
||||
- A bar‐chart of amplitudes (or probabilities)
|
||||
- A sine‐curve of success‐probability vs. iteration
|
||||
- A geometric "rotation" on the unit circle
|
||||
|
||||
---
|
||||
|
||||
@ -16,69 +14,37 @@ Choose between a Matplotlib-based CLI or an optional DearPyGui GUI (WIP).
|
||||
|
||||
## Installation
|
||||
|
||||
### Via [pipx](https://pipx.pypa.io/stable/installation/)
|
||||
|
||||
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):
|
||||
### Using [uv](https://docs.astral.sh/uv/)/[uvx](https://docs.astral.sh/uv/guides/tools/)
|
||||
|
||||
```bash
|
||||
uvx grovers-visualizer 1111
|
||||
```
|
||||
|
||||
With the optional UI extra:
|
||||
### Using [pip](https://pypi.org/project/pip/)/[pipx](https://pipx.pypa.io/stable/installation/)
|
||||
|
||||
```bash
|
||||
uvx "grovers-visualizer[ui]" --ui
|
||||
pip grovers-visualizer
|
||||
# or
|
||||
pipx grovers-visualizer # (recommended)
|
||||
|
||||
# and then run
|
||||
grovers-visualizer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI Mode
|
||||
### Flags
|
||||
|
||||
Flags:
|
||||
|
||||
• `-t, --target`
|
||||
Target bit‐string (e.g. `010`). Length determines number of qubits.
|
||||
• `-s, --speed`
|
||||
Delay between iterations (seconds). Default `0.5`.
|
||||
• `-i, --iterations`
|
||||
- `TARGET`
|
||||
Target bit‐string (e.g. `010`). Length also determines number of qubits.
|
||||
- `-i, --iterations ITERATIONS`
|
||||
Max iterations; `0` means use the optimal $\lfloor\frac\pi4\sqrt{2^n}\rfloor$.
|
||||
• `--ui`
|
||||
Launch the optional DearPyGui GUI (requires the `[ui]` extra) (WIP).
|
||||
|
||||
### GUI Mode (WIP)
|
||||
|
||||
If you installed with `"[ui]"`, launch the DearPyGui window:
|
||||
|
||||
```
|
||||
grovers-visualizer --ui
|
||||
```
|
||||
|
||||
In the UI you can:
|
||||
|
||||
- Set number of qubits
|
||||
- Enter the target bit‐string
|
||||
- Choose max iterations or leave at 0 for optimal
|
||||
- Control the animation speed
|
||||
|
||||
Hit **Start** to watch the bar chart, sine plot, and rotation‐circle update in real time.
|
||||
- `-s, --speed SPEED`
|
||||
Delay between iterations (seconds). Default `0.5`.
|
||||
- `-p, --phase PHASE`
|
||||
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$).
|
||||
|
||||
---
|
||||
|
||||
|
||||
BIN
media/demo.gif
BIN
media/demo.gif
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 177 KiB |
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "grovers-visualizer"
|
||||
version = "0.4.3"
|
||||
version = "0.5.0"
|
||||
description = "A tiny Python package that steps through Grover’s Search algorithm."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import math
|
||||
from argparse import ArgumentParser
|
||||
from dataclasses import dataclass
|
||||
|
||||
@ -11,6 +12,7 @@ class Args:
|
||||
iterations: int
|
||||
speed: float
|
||||
ui: bool
|
||||
phase: float
|
||||
|
||||
|
||||
def parse_args() -> Args:
|
||||
@ -26,6 +28,7 @@ def parse_args() -> Args:
|
||||
iterations=ns.iterations,
|
||||
speed=ns.speed,
|
||||
ui=ns.ui,
|
||||
phase=ns.phase,
|
||||
)
|
||||
|
||||
|
||||
@ -61,4 +64,15 @@ def parse_cli(base_parser: ArgumentParser) -> None:
|
||||
default=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")
|
||||
|
||||
@ -1,25 +1,41 @@
|
||||
import math
|
||||
|
||||
from qiskit import QuantumCircuit
|
||||
from qiskit.circuit.library import PhaseGate
|
||||
|
||||
from .state import QubitState
|
||||
|
||||
|
||||
def oracle(qc: QuantumCircuit, target_state: QubitState) -> None:
|
||||
def oracle(qc: QuantumCircuit, target_state: QubitState, /, *, phase: float = math.pi) -> None:
|
||||
"""Oracle that flips the sign of the target state."""
|
||||
n = len(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
|
||||
|
||||
|
||||
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."""
|
||||
qc.h(range(n))
|
||||
qc.x(range(n))
|
||||
apply_phase_inversion(qc, n)
|
||||
apply_phase_inversion(qc, n, phase=phase)
|
||||
qc.x(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:
|
||||
"""Apply X gates to qubits where the target state bit is '0'."""
|
||||
for i, bit in enumerate(reversed(target_state)):
|
||||
@ -27,11 +43,10 @@ def encode_target_state(qc: QuantumCircuit, target_state: QubitState) -> None:
|
||||
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."""
|
||||
if n == 1:
|
||||
qc.z(0)
|
||||
qc.p(phase, 0)
|
||||
return
|
||||
qc.h(n - 1)
|
||||
qc.mcx(list(range(n - 1)), n - 1)
|
||||
qc.h(n - 1)
|
||||
mc_phase = PhaseGate(phase).control(n - 1)
|
||||
qc.append(mc_phase, list(range(n)))
|
||||
|
||||
@ -7,7 +7,7 @@ from .args import Args
|
||||
def run_cli(args: Args) -> None:
|
||||
vis = GroverVisualizer(args.target, pause=args.speed)
|
||||
|
||||
for it, sv in grover_evolver(vis.target, args.iterations):
|
||||
for it, sv in grover_evolver(vis.target, args.iterations, phase=args.phase):
|
||||
if not vis.is_running:
|
||||
break
|
||||
vis.update(it, sv)
|
||||
|
||||
@ -1,31 +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, oracle
|
||||
from grovers_visualizer.circuit import diffusion_circuit, oracle_circuit
|
||||
from grovers_visualizer.state import QubitState
|
||||
|
||||
|
||||
def grover_evolver(target: QubitState, max_iterations: int = 0) -> Iterator[tuple[int, Statevector]]:
|
||||
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. If
|
||||
max_iterations > 0, stop after that many iterations. If
|
||||
max_iterations == 0, run indefinitely (until the consumer breaks).
|
||||
- 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
|
||||
yield 0, Statevector.from_instruction(qc)
|
||||
sv = Statevector.from_instruction(qc)
|
||||
yield 0, sv
|
||||
|
||||
# pick an iterator for subsequent steps
|
||||
iter = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
|
||||
oracle_op = Operator(oracle_circuit(target, phase=phase))
|
||||
diffusion_op = Operator(diffusion_circuit(n_qubits, phase=phase))
|
||||
|
||||
for i in iter:
|
||||
oracle(qc, target)
|
||||
diffusion(qc, n_qubits)
|
||||
yield i, Statevector.from_instruction(qc)
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user