Gyanateet Dutta
Fix Space loading: direct Streamlit, lazy imports, ReNova page, fix deps
9f8cf99
Raw
History Blame Contribute Delete
6.74 kB
"""
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()