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 |
66
README.md
66
README.md
@ -6,8 +6,6 @@ A tiny Python package that steps through Grover’s Search algorithm and shows y
|
|||||||
- 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).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
@ -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 bit‐string (e.g. `010`). Length also determines number of qubits.
|
||||||
• `-t, --target`
|
- `-i, --iterations ITERATIONS`
|
||||||
Target bit‐string (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 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: 292 KiB After Width: | Height: | Size: 177 KiB |
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "grovers-visualizer"
|
name = "grovers-visualizer"
|
||||||
version = "0.4.3"
|
version = "0.5.0"
|
||||||
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,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")
|
||||||
|
|||||||
@ -1,25 +1,41 @@
|
|||||||
|
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) -> None:
|
def oracle(qc: QuantumCircuit, target_state: QubitState, /, *, phase: float = math.pi) -> 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)):
|
||||||
@ -27,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)
|
|
||||||
|
|||||||
@ -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):
|
for it, sv in grover_evolver(vis.target, args.iterations, phase=args.phase):
|
||||||
if not vis.is_running:
|
if not vis.is_running:
|
||||||
break
|
break
|
||||||
vis.update(it, sv)
|
vis.update(it, sv)
|
||||||
|
|||||||
@ -1,31 +1,39 @@
|
|||||||
|
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, oracle
|
from grovers_visualizer.circuit import diffusion_circuit, oracle_circuit
|
||||||
from grovers_visualizer.state import QubitState
|
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.
|
"""Yields (iteration, statevector) pairs.
|
||||||
|
|
||||||
iteration=0 is the uniform-Hadamard initialization. If
|
- iteration=0 is the uniform-Hadamard initialization
|
||||||
max_iterations > 0, stop after that many iterations. If
|
- max_iterations > 0, stop after that many iterations
|
||||||
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
|
||||||
yield 0, Statevector.from_instruction(qc)
|
sv = Statevector.from_instruction(qc)
|
||||||
|
yield 0, sv
|
||||||
|
|
||||||
# pick an iterator for subsequent steps
|
oracle_op = Operator(oracle_circuit(target, phase=phase))
|
||||||
iter = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
|
diffusion_op = Operator(diffusion_circuit(n_qubits, phase=phase))
|
||||||
|
|
||||||
for i in iter:
|
iters = range(1, max_iterations + 1) if max_iterations > 0 else count(1)
|
||||||
oracle(qc, target)
|
for i in iters:
|
||||||
diffusion(qc, n_qubits)
|
sv = sv.evolve(oracle_op).evolve(diffusion_op)
|
||||||
yield i, Statevector.from_instruction(qc)
|
yield i, sv
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user