Compare commits

..

No commits in common. "main" and "v0.4.3" have entirely different histories.
main ... v0.4.3

8 changed files with 77 additions and 80 deletions

View File

@ -2,9 +2,11 @@
A tiny Python package that steps through Grovers Search algorithm and shows you, after each iteration: A tiny Python package that steps through Grovers Search algorithm and shows you, after each iteration:
- A barchart of amplitudes (or probabilities) - A barchart of amplitudes (or probabilities)
- 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).
--- ---
@ -14,37 +16,69 @@ A tiny Python package that steps through Grovers 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 bitstring (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 bitstring (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 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: 177 KiB

After

Width:  |  Height:  |  Size: 292 KiB

View File

@ -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 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,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")

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

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