File size: 6,742 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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | """
GQE Multi-Backend Comparison for H2 Molecule
Compares Rust (qhybrid) vs Python implementations using different simulators
"""
import time
import numpy as np
import json
import matplotlib.pyplot as plt
import random
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector
# H2 minimal Hamiltonian (2-qubit)
H2_TERMS = [
(-1.052373245772859, "II"),
(0.39793742484318045, "IZ"),
(-0.39793742484318045, "ZI"),
(-0.01128010425623538, "ZZ"),
(0.18093119978423156, "XX"),
]
EXACT_GROUND = -1.8572750302023795
def pauli_expectation_qiskit(circuit, pauli_string, backend):
"""Compute Pauli expectation using Qiskit backend."""
from qiskit.quantum_info import SparsePauliOp
sv = Statevector.from_instruction(circuit)
# Create Pauli operator
op = SparsePauliOp.from_list([(pauli_string, 1.0)])
expectation = sv.expectation_value(op).real
return expectation
def evaluate_hamiltonian(circuit, backend_type='statevector'):
"""Evaluate H2 Hamiltonian for a circuit."""
total = 0.0
if backend_type == 'statevector':
sv = Statevector.from_instruction(circuit)
for coeff, pauli in H2_TERMS:
from qiskit.quantum_info import SparsePauliOp
op = SparsePauliOp.from_list([(pauli, 1.0)])
exp_val = sv.expectation_value(op).real
total += coeff * exp_val
return total
def generate_random_circuit(n_qubits, max_depth):
"""Generate a random quantum circuit."""
qc = QuantumCircuit(n_qubits)
n_gates = random.randint(1, max_depth)
gate_types = ['h', 'x', 'y', 'z', 's', 't', 'rx', 'ry', 'rz', 'cx']
for _ in range(n_gates):
gate = random.choice(gate_types)
if gate == 'cx':
ctrl = random.randint(0, n_qubits-1)
targ = random.randint(0, n_qubits-1)
while targ == ctrl:
targ = random.randint(0, n_qubits-1)
qc.cx(ctrl, targ)
elif gate in ['rx', 'ry', 'rz']:
qubit = random.randint(0, n_qubits-1)
angle = random.uniform(-np.pi, np.pi)
getattr(qc, gate)(angle, qubit)
else:
qubit = random.randint(0, n_qubits-1)
getattr(qc, gate)(qubit)
return qc
def mutate_circuit(qc, mutation_type='add'):
"""Mutate a circuit."""
new_qc = qc.copy()
if mutation_type == 'add' and len(new_qc.data) < 10:
gate_types = ['h', 'x', 'y', 'z', 's']
gate = random.choice(gate_types)
qubit = random.randint(0, new_qc.num_qubits-1)
getattr(new_qc, gate)(qubit)
elif mutation_type == 'remove' and len(new_qc.data) > 0:
idx = random.randint(0, len(new_qc.data)-1)
new_qc.data.pop(idx)
return new_qc
def python_gqe(backend_name='statevector', use_gpu=False):
"""Python GQE implementation."""
n_qubits = 2
population_size = 50
n_generations = 200
mutation_rate = 0.5
history = []
best_energy = float('inf')
best_circuit = None
# Initialize population
population = [generate_random_circuit(n_qubits, 10) for _ in range(population_size)]
for gen in range(n_generations):
# Evaluate fitness
fitness = []
for circ in population:
try:
energy = evaluate_hamiltonian(circ, backend_type='statevector')
fitness.append(energy)
except:
fitness.append(float('inf'))
# Update best
min_idx = np.argmin(fitness)
if fitness[min_idx] < best_energy:
best_energy = fitness[min_idx]
best_circuit = population[min_idx].copy()
history.append(best_energy)
# Selection (tournament)
new_population = []
for _ in range(population_size):
tournament = random.sample(list(zip(population, fitness)), 3)
winner = min(tournament, key=lambda x: x[1])[0]
new_population.append(winner.copy())
# Mutation
for i in range(len(new_population)):
if random.random() < mutation_rate:
mut_type = random.choice(['add', 'remove'])
new_population[i] = mutate_circuit(new_population[i], mut_type)
# Add elitism
new_population[0] = best_circuit.copy()
population = new_population
return {
'backend': f'Python-{backend_name}',
'energy': best_energy,
'history': history,
'generations': n_generations
}
def rust_gqe():
"""Load Rust GQE results."""
with open('gqe_history.json', 'r') as f:
history = json.load(f)
return {
'backend': 'qhybrid (Rust)',
'energy': min(history),
'history': history,
'generations': len(history)
}
def run_gqe_comparison():
print("Running GQE on multiple backends...")
print("="*60)
results = []
# 1. Rust
print("1. Loading Rust GQE results...")
results.append(rust_gqe())
# 2. Python/NumPy
print("2. Running Python/NumPy GQE...")
start = time.perf_counter()
py_result = python_gqe('statevector')
py_result['time'] = time.perf_counter() - start
results.append(py_result)
# Print summary
print("\n" + "="*60)
print(f"{'Backend':<25} | {'Energy':<15} | {'Error':<12} | {'Time (s)':<10}")
print("-"*60)
for r in results:
err = abs(r['energy'] - EXACT_GROUND)
time_str = f"{r.get('time', 0):.3f}" if 'time' in r else "N/A"
print(f"{r['backend']:<25} | {r['energy']:<15.8f} | {err:<12.2e} | {time_str:<10}")
# Plotting
plt.figure(figsize=(12, 6))
for r in results:
if 'history' in r:
plt.plot(r['history'], label=r['backend'], marker='.', markersize=3, alpha=0.7)
plt.axhline(y=EXACT_GROUND, color='red', linestyle='--', linewidth=2, label='Exact Ground State')
plt.xlabel('Generation')
plt.ylabel('Energy (Hartree)')
plt.title('GQE Convergence Comparison Across Backends (H2 Molecule)')
plt.legend()
plt.grid(True, which="both", ls="-", alpha=0.3)
import os
os.makedirs('docs/assets', exist_ok=True)
plt.savefig('docs/assets/gqe_backends_comparison.png', dpi=300)
print("\nGQE backend comparison saved to docs/assets/gqe_backends_comparison.png")
# Save results
with open('gqe_backends_results.json', 'w') as f:
json.dump(results, f, indent=2, default=str)
print("Results saved to gqe_backends_results.json")
if __name__ == "__main__":
run_gqe_comparison()
|