Skip to content

Commit e33717f

Browse files
refactor: make main.py more maintanable and readable. make "python main.py -h" faster
1. Make main.py more maintanable splitting long main() into smaller modules 2. add docstrings for readability 2. Make "python main.py -h" command faster because of not importing from src.pipeline if -h command
1 parent 4191ab9 commit e33717f

1 file changed

Lines changed: 91 additions & 48 deletions

File tree

main.py

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,9 @@
22
import os
33
import re
44

5-
import matplotlib.pyplot as plt
6-
7-
from src.pipeline import (
8-
build_grover_circuit,
9-
optimal_iterations,
10-
run_ideal,
11-
run_noisy,
12-
extract_marked_probability,
13-
expected_success_probability,
14-
compute_probabilities,
15-
save_circuit_diagram,
16-
NOISE_MODEL,
17-
)
18-
195

206
def _parse_search(value: str) -> list[str]:
7+
"""Parse comma-separated binary search states into a list."""
218
parts = [s.strip() for s in value.split(",")]
229
for p in parts:
2310
if not re.fullmatch(r"[01]+", p):
@@ -28,6 +15,7 @@ def _parse_search(value: str) -> list[str]:
2815

2916

3017
def _validate_args(args: argparse.Namespace) -> None:
18+
"""Validate qubits count and lengths of search state(s)."""
3119
if args.qubits < 1:
3220
raise ValueError("--qubits must be >= 1")
3321
for s in args.search:
@@ -43,6 +31,10 @@ def _plot_histogram(
4331
title: str,
4432
filepath: str | None,
4533
) -> None:
34+
"""Plot or save a probability histogram with marked states highlighted."""
35+
# Lazy import so `-h` stays instant
36+
import matplotlib.pyplot as plt
37+
4638
states = sorted(probs.keys())
4739
values = [probs[s] for s in states]
4840

@@ -72,94 +64,145 @@ def _plot_histogram(
7264
pass
7365

7466

75-
def main() -> None:
67+
def _print_header(args: argparse.Namespace, iters: int, label: str) -> None:
68+
"""Print run configuration banner."""
69+
print()
70+
print("=" * 60)
71+
print(f" Grover's Search Algorithm")
72+
print("=" * 60)
73+
print(f" Qubits: {args.qubits}")
74+
print(f" Search state(s): |{label}>")
75+
if len(args.search) > 1:
76+
print(f" Marked count: {len(args.search)}")
77+
print(f" Optimal iters: {iters}")
78+
print(f" Shots: {args.shots}")
79+
print(f" Noise model: {'Enabled' if args.noise else 'Disabled'}")
80+
print("=" * 60)
81+
print()
82+
83+
84+
def _print_results(
85+
probs: dict,
86+
marked_prob: float,
87+
expected: float,
88+
args: argparse.Namespace,
89+
label: str,
90+
) -> None:
91+
"""Print top-5 measurement results and marked-state probability."""
92+
sim_label = "Noisy" if args.noise else "Ideal"
93+
top = sorted(probs.items(), key=lambda x: -x[1])[:5]
94+
print(f" Measurement Results ({sim_label} Simulator):")
95+
print(f" {'State':>8} {'Probability':>12}")
96+
print(f" {'-'*8} {'-'*12}")
97+
for state, prob in top:
98+
marker = " <- marked" if state in args.search else ""
99+
print(f" {state:>8} {prob:>10.2%}{marker}")
100+
print()
101+
print(f" Marked state(s) |{label}> probability: {marked_prob:.2%}")
102+
print(f" Expected (ideal): {expected:.1%}")
103+
104+
105+
def _build_parser() -> argparse.ArgumentParser:
106+
"""Build and return the argument parser with all CLI flags."""
76107
parser = argparse.ArgumentParser(
77-
description="Grover's Search Algorithm - scalable, noise-aware implementation",
108+
description="Grover's Search Algorithm -- dynamic oracle, M-state search, noise simulation",
109+
epilog=(
110+
"Examples:\n"
111+
" python main.py --qubits 3 --search 101\n"
112+
" python main.py --qubits 4 --search 1101 --noise --shots 4096\n"
113+
" python main.py --qubits 3 --search \"000,111\" --save-plot outputs/hist.png\n"
114+
" python main.py --qubits 5 --search 10101 --save-circuit outputs/circ.png"
115+
),
116+
formatter_class=argparse.RawDescriptionHelpFormatter,
78117
)
79118
parser.add_argument(
80119
"--qubits",
81120
type=int,
82121
default=4,
83-
help="Number of qubits (default: 4)",
122+
help="Number of qubits (n). Search space = 2^n states (default: 4)",
84123
)
85124
parser.add_argument(
86125
"--search",
87126
type=str,
88127
required=True,
89-
help="Marked state(s) as binary string(s), comma-separated for multi-state, e.g. 1101 or 001,110",
128+
help="Binary state(s) to search for. Comma-separated for multi-state (e.g. 1101 or 000,111)",
90129
)
91130
parser.add_argument(
92131
"--noise",
93132
action="store_true",
94-
help="Run on a noisy simulated backend",
133+
help="Enable noise model (depolarizing gate + readout errors)",
95134
)
96135
parser.add_argument(
97136
"--shots",
98137
type=int,
99138
default=8192,
100-
help="Number of measurement shots (default: 8192)",
139+
help="Measurement repetitions. Higher = less sampling noise (default: 8192)",
101140
)
102141
parser.add_argument(
103142
"--save-plot",
104143
type=str,
105144
default=None,
106-
help="File path to save the probability histogram (e.g. outputs/histogram.png)",
145+
help="Save probability histogram as PNG (e.g. outputs/histogram.png)",
107146
)
108147
parser.add_argument(
109148
"--save-circuit",
110149
type=str,
111150
default=None,
112-
help="File path to save the circuit diagram (e.g. outputs/circuit.txt)",
151+
help="Save circuit diagram. .png = mpl style, .txt = ASCII (e.g. outputs/circuit.png)",
113152
)
153+
return parser
114154

155+
156+
def main() -> None:
157+
# ── Build argument parser ──
158+
parser = _build_parser()
159+
160+
# ── Parse and validate arguments ──
115161
args = parser.parse_args()
116162
args.search = _parse_search(args.search)
117163
_validate_args(args)
118164

165+
166+
# Lazy imports: Qiskit, Aer load here so `-h` stays instant
167+
from src.pipeline import (
168+
build_grover_circuit,
169+
optimal_iterations,
170+
run_ideal,
171+
run_noisy,
172+
extract_marked_probability,
173+
expected_success_probability,
174+
compute_probabilities,
175+
save_circuit_diagram,
176+
NOISE_MODEL,
177+
)
178+
179+
# ── Display run configuration ──
119180
num_marked = len(args.search)
120181
label = ",".join(args.search) if num_marked > 1 else args.search[0]
121182
iters = optimal_iterations(args.qubits, num_marked)
122183

123-
print()
124-
print("=" * 60)
125-
print(f" Grover's Search Algorithm")
126-
print("=" * 60)
127-
print(f" Qubits: {args.qubits}")
128-
print(f" Search state(s): |{label}>")
129-
if num_marked > 1:
130-
print(f" Marked count: {num_marked}")
131-
print(f" Optimal iters: {iters}")
132-
print(f" Shots: {args.shots}")
133-
print(f" Noise model: {'Enabled' if args.noise else 'Disabled'}")
134-
print("=" * 60)
135-
print()
184+
_print_header(args, iters, label)
136185

186+
# ── Build Grover circuit ──
137187
circuit = build_grover_circuit(args.qubits, args.search, iters)
138-
139188
if args.save_circuit:
140189
save_circuit_diagram(circuit, args.save_circuit)
141190
print(f" Circuit diagram saved to {args.save_circuit}")
142191

192+
# ── Run simulation ──
143193
run_fn = run_noisy if args.noise else run_ideal
144194
counts = run_fn(circuit, shots=args.shots)
145195
probs = compute_probabilities(counts, args.qubits)
146196
marked_prob = extract_marked_probability(counts, args.search)
147197

148-
sim_label = "Noisy" if args.noise else "Ideal"
149-
title = f"Grover's Algorithm ({sim_label}) - {args.qubits} qubits, |{label}>"
150198

151-
top = sorted(probs.items(), key=lambda x: -x[1])[:5]
152-
print(f" Measurement Results ({sim_label} Simulator):")
153-
print(f" {'State':>8} {'Probability':>12}")
154-
print(f" {'-'*8} {'-'*12}")
155-
for state, prob in top:
156-
marker = " <- marked" if state in args.search else ""
157-
print(f" {state:>8} {prob:>10.2%}{marker}")
158-
print()
159-
print(f" Marked state(s) |{label}> probability: {marked_prob:.2%}")
199+
# ── Display results ──
160200
expected = expected_success_probability(args.qubits, num_marked, iters)
161-
print(f" Expected (ideal): {expected:.1%}")
201+
_print_results(probs, marked_prob, expected, args, label)
162202

203+
# ── Plot histogram ──
204+
sim_label = "Noisy" if args.noise else "Ideal"
205+
title = f"Grover's Algorithm ({sim_label}) - {args.qubits} qubits, |{label}>"
163206
_plot_histogram(probs, args.search, title, args.save_plot)
164207

165208

0 commit comments

Comments
 (0)