Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
benchmark_perf.py
Go to the documentation of this file.
1 """
2 Performance Benchmark: SQUANDER vs Qiskit Noisy Circuit Simulation
3 
4 Benchmarks execution time of identical circuits in both frameworks.
5 
6 Run with: python benchmarks/benchmark_perf.py
7 """
8 
9 import time
10 
11 import numpy as np
12 from circuits import BENCHMARK_CIRCUITS, CIRCUITS_BY_QUBITS
13 
14 from squander.density_matrix import DensityMatrix
15 
16 PRIMARY_BACKEND = "density_matrix"
17 REFERENCE_BACKEND = "qiskit_aer_density_matrix"
18 
19 # Benchmark configuration
20 NUM_RUNS = 1 # Number of timed runs per circuit
21 WARMUP_RUNS = 0 # Number of warmup runs before timing
22 
23 
24 def benchmark_func(func, num_runs=10, warmup=3):
25  """Benchmark a function with warmup runs."""
26  # Warmup
27  for _ in range(warmup):
28  func()
29 
30  # Timed runs
31  times = []
32  for _ in range(num_runs):
33  start = time.perf_counter()
34  func()
35  end = time.perf_counter()
36  times.append((end - start) * 1000) # ms
37 
38  return {
39  "mean": np.mean(times),
40  "std": np.std(times),
41  "min": np.min(times),
42  "max": np.max(times),
43  }
44 
45 
46 def run_benchmark(circuits, num_runs=10, warmup=3):
47  """Run benchmark on a list of circuits."""
48  results = []
49 
50  for name, builder_fn in circuits:
51  # Build circuits once
52  builder = builder_fn()
53 
54  # Benchmark SQUANDER
55  def run_sq(b=builder):
56  rho = DensityMatrix(b.n)
57  b.sq.apply_to(np.array([]), rho)
58 
59  sq_time = benchmark_func(run_sq, num_runs=num_runs, warmup=warmup)
60 
61  # Benchmark Qiskit using the same method as validation (AerSimulator)
62  # Note: QiskitDensityMatrix.evolve() cannot handle QuantumError noise channels
63  def run_qk(b=builder):
64  b.run_qiskit()
65 
66  qk_time = benchmark_func(run_qk, num_runs=num_runs, warmup=warmup)
67 
68  speedup = qk_time["mean"] / sq_time["mean"]
69  results.append(
70  {
71  "case_name": name,
72  "backend": PRIMARY_BACKEND,
73  "reference_backend": REFERENCE_BACKEND,
74  "qubits": builder.n,
75  "ops": len(builder.ops),
76  "squander_ms": sq_time["mean"],
77  "squander_std": sq_time["std"],
78  "qiskit_ms": qk_time["mean"],
79  "qiskit_std": qk_time["std"],
80  "speedup": speedup,
81  }
82  )
83 
84  return results
85 
86 
87 def print_results(results, title="Benchmark Results"):
88  """Print benchmark results in a formatted table."""
89  print(
90  f"\n{'Circuit':<20} {'Qubits':<8} {'Ops':<6} "
91  f"{'SQUANDER[density_matrix](ms)':<29} "
92  f"{'Qiskit[qiskit_aer_density_matrix](ms)':<36} {'Speedup':<10}"
93  )
94  print("-" * 125)
95 
96  for r in results:
97  print(
98  f" {r['case_name']:<18} {r['qubits']:<8} {r['ops']:<6} "
99  f"{r['squander_ms']:>10.3f} {r['qiskit_ms']:>10.3f} {r['speedup']:>6.1f}x"
100  )
101 
102 
103 def print_summary(results):
104  """Print benchmark summary statistics."""
105  speedups = [r["speedup"] for r in results]
106 
107  print("\n" + "=" * 70)
108  print(" BENCHMARK SUMMARY")
109  print("=" * 70)
110 
111  print(f"\n Average speedup: {np.mean(speedups):.1f}x faster")
112 
113  best = max(results, key=lambda x: x["speedup"])
114  worst = min(results, key=lambda x: x["speedup"])
115 
116  print(
117  " Compared backends: {} vs {}".format(
118  PRIMARY_BACKEND, REFERENCE_BACKEND
119  )
120  )
121  print(f" Best speedup: {best['speedup']:.1f}x ({best['case_name']})")
122  print(f" Worst speedup: {worst['speedup']:.1f}x ({worst['case_name']})")
123 
124 
125 def main():
126  print("=" * 70)
127  print(
128  " PERFORMANCE BENCHMARK: SQUANDER ({}) vs Qiskit ({})".format(
129  PRIMARY_BACKEND, REFERENCE_BACKEND
130  )
131  )
132  print("=" * 70)
133  print(f"\nMeasuring execution time ({NUM_RUNS} run(s), {WARMUP_RUNS} warmup)...")
134 
135  # Run benchmark on representative circuits
136  results = run_benchmark(BENCHMARK_CIRCUITS, num_runs=NUM_RUNS, warmup=WARMUP_RUNS)
137 
138  # Print results
139  print_results(results)
140  print_summary(results)
141 
142  print("\n" + "=" * 70)
143 
144  # Optional: Run full benchmark by qubit count
145  print("\n\nDetailed benchmark by qubit count:")
146 
147  for n_qubits in sorted(CIRCUITS_BY_QUBITS.keys()):
148  circuits = CIRCUITS_BY_QUBITS[n_qubits]
149  print(f"\n--- {n_qubits}-QUBIT CIRCUITS ---")
150  results = run_benchmark(circuits, num_runs=NUM_RUNS, warmup=WARMUP_RUNS)
151 
152  for r in results:
153  print(
154  f" {r['case_name']:<20} "
155  f"SQUANDER[{r['backend']}]: {r['squander_ms']:>8.3f}ms "
156  f"Qiskit[{r['reference_backend']}]: {r['qiskit_ms']:>10.3f}ms "
157  f"Speedup: {r['speedup']:>8.1f}x"
158  )
159 
160  print("\n" + "=" * 70)
161  print(" BENCHMARK COMPLETE")
162  print("=" * 70)
163 
164 
165 if __name__ == "__main__":
166  main()
def print_results(results, title="Benchmark Results")
def print_summary(results)
def run_benchmark(circuits, num_runs=10, warmup=3)
def benchmark_func(func, num_runs=10, warmup=3)