File size: 6,060 Bytes
9f8cf99 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import time
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from qiskit import QuantumCircuit, transpile
from qiskit_aer import Aer
import qhybrid_kernels
import os
def numpy_statevector_simulator(qc: QuantumCircuit):
"""A very simple pure NumPy statevector simulator for comparison."""
n_qubits = qc.num_qubits
state = np.zeros(2**n_qubits, dtype=complex)
state[0] = 1.0
# Transpile to basic gates
qc_decomposed = transpile(qc, basis_gates=['u', 'cx'], optimization_level=0)
for instruction in qc_decomposed.data:
gate = instruction.operation
qubits = [qc_decomposed.find_bit(q).index for q in instruction.qubits]
if gate.name == 'u':
theta, phi, lam = gate.params
u_mat = np.array([
[np.cos(theta/2), -np.exp(1j*lam)*np.sin(theta/2)],
[np.exp(1j*phi)*np.sin(theta/2), np.exp(1j*(phi+lam))*np.cos(theta/2)]
])
state = apply_gate(state, u_mat, qubits[0], n_qubits)
elif gate.name == 'cx':
cx_mat = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0]
])
state = apply_2q_gate(state, cx_mat, qubits[0], qubits[1], n_qubits)
return state
def apply_gate(state, gate_mat, qubit, n_qubits):
state = state.reshape([2]*(n_qubits))
target_axis = n_qubits - 1 - qubit
state = np.tensordot(gate_mat, state, axes=([1], [target_axis]))
state = np.moveaxis(state, 0, target_axis)
return state.flatten()
def apply_2q_gate(state, gate_mat, q_ctrl, q_trgt, n_qubits):
state = state.reshape([2]*(n_qubits))
ctrl_axis = n_qubits - 1 - q_ctrl
trgt_axis = n_qubits - 1 - q_trgt
gate_mat = gate_mat.reshape((2, 2, 2, 2))
state = np.tensordot(gate_mat, state, axes=((2, 3), (ctrl_axis, trgt_axis)))
state = np.moveaxis(state, [0, 1], [ctrl_axis, trgt_axis])
return state.flatten()
def run_benchmark():
qubit_range = range(4, 27) # Stress test up to 26 qubits
results = []
print(f"{'Qubits':<10} | {'Rust (ms)':<12} | {'NumPy (ms)':<12} | {'Aer-CPU (ms)':<12} | {'Aer-GPU (ms)':<12}")
print("-" * 75)
numpy_too_slow = False
# Backends
backend_cpu = Aer.get_backend('statevector_simulator')
backend_gpu = Aer.get_backend('statevector_simulator')
backend_gpu.set_options(device='GPU')
# We'll try to enable cuStateVec if possible
backend_custatevec = Aer.get_backend('statevector_simulator')
try:
backend_custatevec.set_options(device='GPU', cuStateVec_enable=True)
custatevec_works = True
except Exception:
custatevec_works = False
for n_qubits in qubit_range:
qc = QuantumCircuit(n_qubits)
for i in range(n_qubits): qc.h(i)
for i in range(n_qubits - 1): qc.cx(i, i+1)
# 1. Rust Benchmark
circuit_json = qhybrid_kernels.qiskit_to_qhybrid_json(qc)
start = time.perf_counter()
qhybrid_kernels.execute_quantum_circuit(circuit_json)
rust_time = (time.perf_counter() - start) * 1000
# 2. NumPy Benchmark
numpy_time = None
if not numpy_too_slow:
try:
start = time.perf_counter()
numpy_statevector_simulator(qc)
numpy_time = (time.perf_counter() - start) * 1000
if numpy_time > 15000:
numpy_too_slow = True
except Exception:
numpy_too_slow = True
# 3. Aer CPU Benchmark
start = time.perf_counter()
backend_cpu.run(transpile(qc, backend_cpu)).result()
aer_cpu_time = (time.perf_counter() - start) * 1000
# 4. Aer GPU Benchmark
start = time.perf_counter()
backend_gpu.run(transpile(qc, backend_gpu)).result()
aer_gpu_time = (time.perf_counter() - start) * 1000
# 5. Aer cuStateVec Benchmark (Optional)
aer_cusv_time = None
if custatevec_works:
start = time.perf_counter()
backend_custatevec.run(transpile(qc, backend_custatevec)).result()
aer_cusv_time = (time.perf_counter() - start) * 1000
numpy_str = f"{numpy_time:.2f}" if numpy_time else "SKIP"
print(f"{n_qubits:<10} | {rust_time:<12.2f} | {numpy_str:<12} | {aer_cpu_time:<12.2f} | {aer_gpu_time:<12.2f}")
results.append({
'qubits': n_qubits,
'rust': rust_time,
'numpy': numpy_time,
'aer_cpu': aer_cpu_time,
'aer_gpu': aer_gpu_time,
'aer_cusv': aer_cusv_time
})
df = pd.DataFrame(results)
# Plotting
plt.figure(figsize=(12, 8))
# Filter valid results for plotting
valid_numpy = df.dropna(subset=['numpy'])
plt.plot(valid_numpy['qubits'], valid_numpy['numpy'], label='NumPy (Naive)', marker='o', linestyle='--')
plt.plot(df['qubits'], df['aer_cpu'], label='Qiskit Aer (CPU)', marker='s', linestyle='-')
plt.plot(df['qubits'], df['aer_gpu'], label='Qiskit Aer (GPU)', marker='d', linestyle='-')
if custatevec_works:
valid_cusv = df.dropna(subset=['aer_cusv'])
plt.plot(valid_cusv['qubits'], valid_cusv['aer_cusv'], label='Qiskit Aer (cuStateVec)', marker='x', linestyle='-.')
plt.plot(df['qubits'], df['rust'], label='qhybrid (Rust - CPU)', marker='^', linestyle='-', linewidth=2, color='green')
plt.yscale('log')
plt.xlabel('Number of Qubits')
plt.ylabel('Execution Time (ms) - Log Scale')
plt.title('HPC Quantum Simulator Comparison (CPU vs GPU vs Rust)')
plt.legend()
plt.grid(True, which="both", ls="-", alpha=0.5)
os.makedirs('docs/assets', exist_ok=True)
plt.savefig('docs/assets/speedup_hpc.png', dpi=300)
print(f"\nHPC benchmark chart saved to docs/assets/speedup_hpc.png")
if __name__ == "__main__":
run_benchmark()
|