mirror of
https://github.com/kristoferssolo/grovers-visualizer.git
synced 2025-10-21 20:10:35 +00:00
Compare commits
No commits in common. "main" and "v0.4.3" have entirely different histories.
72
README.md
72
README.md
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
A tiny Python package that steps through Grover’s Search algorithm and shows you, after each iteration:
|
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 bar‐chart of amplitudes (or probabilities)
|
||||||
- A sine‐curve of success‐probability vs. iteration
|
- A sine‐curve of success‐probability 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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -14,37 +16,69 @@ A tiny Python package that steps through Grover’s Search algorithm and shows y
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Using [uv](https://docs.astral.sh/uv/)/[uvx](https://docs.astral.sh/uv/guides/tools/)
|
### 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):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx grovers-visualizer 1111
|
uvx grovers-visualizer 1111
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using [pip](https://pypi.org/project/pip/)/[pipx](https://pipx.pypa.io/stable/installation/)
|
With the optional UI extra:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip grovers-visualizer
|
uvx "grovers-visualizer[ui]" --ui
|
||||||
# or
|
|
||||||
pipx grovers-visualizer # (recommended)
|
|
||||||
|
|
||||||
# and then run
|
|
||||||
grovers-visualizer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Flags
|
### CLI Mode
|
||||||
|
|
||||||
- `TARGET`
|
Flags:
|
||||||
Target bit‐string (e.g. `010`). Length also determines number of qubits.
|
|
||||||
- `-i, --iterations ITERATIONS`
|
• `-t, --target`
|
||||||
Max iterations; `0` means use the optimal $\lfloor\frac\pi4\sqrt{2^n}\rfloor$.
|
Target bit‐string (e.g. `010`). Length determines number of qubits.
|
||||||
- `-s, --speed SPEED`
|
• `-s, --speed`
|
||||||
Delay between iterations (seconds). Default `0.5`.
|
Delay between iterations (seconds). Default `0.5`.
|
||||||
- `-p, --phase PHASE`
|
• `-i, --iterations`
|
||||||
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$).
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
BIN
media/demo.gif
BIN
media/demo.gif
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 292 KiB |
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "grovers-visualizer"
|
name = "grovers-visualizer"
|
||||||
version = "0.5.0"
|
version = "0.4.3"
|
||||||
description = "A tiny Python package that steps through Grover’s Search algorithm."
|
description = "A tiny Python package that steps through Grover’s Search algorithm."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import math
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ class Args:
|
|||||||
iterations: int
|
iterations: int
|
||||||
speed: float
|
speed: float
|
||||||
ui: bool
|
ui: bool
|
||||||
phase: float
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> Args:
|
def parse_args() -> Args:
|
||||||
@ -28,7 +26,6 @@ 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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -64,15 +61,4 @@ 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")
|
||||||
|
|||||||
@ -1,41 +1,25 @@
|
|||||||
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 oracle(qc: QuantumCircuit, target_state: QubitState, /, *, phase: float = math.pi) -> 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."""
|
||||||
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, phase=phase)
|
apply_phase_inversion(qc, n)
|
||||||
encode_target_state(qc, target_state) # Undo
|
encode_target_state(qc, target_state) # Undo
|
||||||
|
|
||||||
|
|
||||||
def oracle_circuit(target: QubitState, /, *, phase: float = math.pi) -> QuantumCircuit:
|
def diffusion(qc: QuantumCircuit, n: int) -> None:
|
||||||
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, phase=phase)
|
apply_phase_inversion(qc, n)
|
||||||
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)):
|
||||||
@ -43,10 +27,11 @@ def encode_target_state(qc: QuantumCircuit, target_state: QubitState) -> None:
|
|||||||
qc.x(i)
|
qc.x(i)
|
||||||
|
|
||||||
|
|
||||||
def apply_phase_inversion(qc: QuantumCircuit, n: int, /, *, phase: float = math.pi) -> None:
|
def apply_phase_inversion(qc: QuantumCircuit, n: int) -> 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.p(phase, 0)
|
qc.z(0)
|
||||||
return
|
return
|
||||||
mc_phase = PhaseGate(phase).control(n - 1)
|
qc.h(n - 1)
|
||||||
qc.append(mc_phase, list(range(n)))
|
qc.mcx(list(range(n - 1)), n - 1)
|
||||||
|
qc.h(n - 1)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from .args import Args
|
|||||||
def run_cli(args: Args) -> None:
|
def run_cli(args: Args) -> None:
|
||||||
vis = GroverVisualizer(args.target, pause=args.speed)
|
vis = GroverVisualizer(args.target, pause=args.speed)
|
||||||
|
|
||||||
for it, sv in grover_evolver(vis.target, args.iterations, phase=args.phase):
|
for it, sv in grover_evolver(vis.target, args.iterations):
|
||||||
if not vis.is_running:
|
if not vis.is_running:
|
||||||
break
|
break
|
||||||
vis.update(it, sv)
|
vis.update(it, sv)
|
||||||
|
|||||||
@ -1,39 +1,31 @@
|
|||||||
import math
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
from qiskit import QuantumCircuit
|
from qiskit import QuantumCircuit
|
||||||
from qiskit.qasm2.parse import Operator
|
|
||||||
from qiskit.quantum_info import Statevector
|
from qiskit.quantum_info import Statevector
|
||||||
|
|
||||||
from grovers_visualizer.circuit import diffusion_circuit, oracle_circuit
|
from grovers_visualizer.circuit import diffusion, oracle
|
||||||
from grovers_visualizer.state import QubitState
|
from grovers_visualizer.state import QubitState
|
||||||
|
|
||||||
|
|
||||||
def grover_evolver(
|
def grover_evolver(target: QubitState, max_iterations: int = 0) -> Iterator[tuple[int, Statevector]]:
|
||||||
target: QubitState,
|
|
||||||
max_iterations: int = 0,
|
|
||||||
*,
|
|
||||||
phase: float = math.pi,
|
|
||||||
) -> Iterator[tuple[int, Statevector]]:
|
|
||||||
"""Yields (iteration, statevector) pairs.
|
"""Yields (iteration, statevector) pairs.
|
||||||
|
|
||||||
- iteration=0 is the uniform-Hadamard initialization
|
iteration=0 is the uniform-Hadamard initialization. If
|
||||||
- max_iterations > 0, stop after that many iterations
|
max_iterations > 0, stop after that many iterations. If
|
||||||
- max_iterations == 0, run indefinitely (until the consumer breaks)
|
max_iterations == 0, run indefinitely (until the consumer breaks).
|
||||||
"""
|
"""
|
||||||
n_qubits = len(target)
|
n_qubits = len(target)
|
||||||
qc = QuantumCircuit(n_qubits)
|
qc = QuantumCircuit(n_qubits)
|
||||||
qc.h(range(n_qubits))
|
qc.h(range(n_qubits))
|
||||||
|
|
||||||
# initial statevector
|
# initial statevector
|
||||||
sv = Statevector.from_instruction(qc)
|
yield 0, Statevector.from_instruction(qc)
|
||||||
yield 0, sv
|
|
||||||
|
|
||||||
oracle_op = Operator(oracle_circuit(target, phase=phase))
|
# pick an iterator for subsequent steps
|
||||||
diffusion_op = Operator(diffusion_circuit(n_qubits, phase=phase))
|
iter = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
|
||||||
|
|
||||||
iters = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
|
for i in iter:
|
||||||
for i in iters:
|
oracle(qc, target)
|
||||||
sv = sv.evolve(oracle_op).evolve(diffusion_op)
|
diffusion(qc, n_qubits)
|
||||||
yield i, sv
|
yield i, Statevector.from_instruction(qc)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user