Skip to content

Commit e7dc3b9

Browse files
committed
Finish setup new backend
1 parent c42426e commit e7dc3b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6343
-440
lines changed

examples/ICCAD22_tutorial/sec1_basic.ipynb

Lines changed: 437 additions & 437 deletions
Large diffs are not rendered by default.
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#!/usr/bin/env python3
2+
"""Example running VQE algorithm on IBM Quantum hardware."""
3+
4+
import torch
5+
import numpy as np
6+
import sys
7+
import os
8+
9+
# Add project root to path
10+
sys.path.insert(0, os.path.abspath('..'))
11+
12+
from torchquantum.backend import ParameterizedQuantumCircuit, QuantumExpectation
13+
from torchquantum.backend.qiskit_backend import QiskitBackend, HardwareManager
14+
from torchquantum.operator.standard_gates import RY, RZ, CNOT
15+
16+
17+
def create_vqe_ansatz(n_qubits=2, n_layers=2):
18+
"""Create a hardware-efficient VQE ansatz."""
19+
n_params = n_qubits * n_layers * 2
20+
circuit = ParameterizedQuantumCircuit(n_wires=n_qubits, n_trainable_params=n_params)
21+
22+
# Initialize parameters near ground state
23+
circuit.set_trainable_params(torch.randn(n_params) * 0.1)
24+
25+
param_idx = 0
26+
for layer in range(n_layers):
27+
# Rotation layer
28+
for q in range(n_qubits):
29+
circuit.append_gate(RY, wires=q, trainable_idx=param_idx)
30+
param_idx += 1
31+
circuit.append_gate(RZ, wires=q, trainable_idx=param_idx)
32+
param_idx += 1
33+
34+
# Entangling layer
35+
for q in range(n_qubits - 1):
36+
circuit.append_gate(CNOT, wires=[q, q + 1])
37+
38+
return circuit
39+
40+
41+
def select_backend():
42+
"""Select an appropriate backend for VQE."""
43+
print("🔍 Finding suitable quantum backend...")
44+
45+
try:
46+
# Try to connect to IBM Quantum
47+
from qiskit_ibm_runtime import QiskitRuntimeService
48+
service = QiskitRuntimeService()
49+
backends = service.backends()
50+
51+
print(f"✅ Connected to IBM Quantum Runtime")
52+
print(f"📋 Found {len(backends)} available backends")
53+
54+
# Prefer simulators for reliable results, but show real hardware options
55+
simulators = []
56+
real_devices = []
57+
58+
for backend in backends:
59+
if backend.num_qubits >= 2: # Need at least 2 qubits for our VQE
60+
if backend.simulator:
61+
simulators.append(backend)
62+
else:
63+
try:
64+
status = backend.status()
65+
if status.operational:
66+
real_devices.append((backend, status.pending_jobs))
67+
except:
68+
pass
69+
70+
print("\n🎯 Available options:")
71+
72+
# Show simulators
73+
if simulators:
74+
print("\n🖥️ Simulators (recommended for VQE):")
75+
for i, sim in enumerate(simulators[:3]):
76+
print(f" {i+1}. {sim.name}: {sim.num_qubits} qubits")
77+
78+
# Show real devices
79+
if real_devices:
80+
real_devices.sort(key=lambda x: x[1]) # Sort by queue length
81+
print("\n🔬 Real Quantum Devices:")
82+
for i, (device, queue) in enumerate(real_devices[:3]):
83+
print(f" {i+1+len(simulators)}. {device.name}: {device.num_qubits} qubits (Queue: {queue})")
84+
85+
# Let user choose
86+
total_options = len(simulators) + len(real_devices)
87+
if total_options == 0:
88+
print("❌ No suitable backends found")
89+
return None
90+
91+
print(f"\n🔢 Select backend (1-{total_options}), or 0 for local simulator:")
92+
choice = input("Choice: ").strip()
93+
94+
try:
95+
choice = int(choice)
96+
if choice == 0:
97+
return "local"
98+
elif 1 <= choice <= len(simulators):
99+
return simulators[choice - 1]
100+
elif len(simulators) < choice <= total_options:
101+
device, _ = real_devices[choice - len(simulators) - 1]
102+
return device
103+
else:
104+
print("❌ Invalid choice, using local simulator")
105+
return "local"
106+
except ValueError:
107+
print("❌ Invalid choice, using local simulator")
108+
return "local"
109+
110+
except Exception as e:
111+
print(f"⚠️ Could not connect to IBM Quantum: {e}")
112+
print("Using local simulator instead")
113+
return "local"
114+
115+
116+
def run_vqe(backend_choice, max_iterations=50):
117+
"""Run VQE algorithm on the selected backend."""
118+
print(f"\n🚀 Running VQE Algorithm")
119+
print("=" * 40)
120+
121+
# Create VQE circuit for H2 molecule (simplified)
122+
circuit = create_vqe_ansatz(n_qubits=2, n_layers=2)
123+
124+
# H2 Hamiltonian (simplified, 2-qubit version)
125+
hamiltonian = {
126+
'ZZ': -1.0523732, # Main interaction
127+
'ZI': -0.39793742, # Single qubit terms
128+
'IZ': -0.39793742,
129+
'XX': -0.01128010, # Exchange terms
130+
'YY': 0.01128010
131+
}
132+
133+
print(f"🧬 Optimizing H2 molecule ground state")
134+
print(f"🔬 Hamiltonian: {len(hamiltonian)} terms")
135+
136+
# Create backend
137+
if backend_choice == "local":
138+
backend = QiskitBackend(device='qasm_simulator', shots=8192)
139+
print(f"🖥️ Using local QASM simulator")
140+
else:
141+
backend = QiskitBackend(device=backend_choice, shots=4096) # Lower shots for hardware
142+
print(f"🔬 Using {backend_choice.name}: {backend_choice.num_qubits} qubits")
143+
144+
# Create VQE model
145+
vqe_model = QuantumExpectation(circuit, backend, hamiltonian)
146+
147+
# Optimizer
148+
optimizer = torch.optim.Adam([circuit.trainable_params], lr=0.1)
149+
150+
print(f"\n⚙️ Starting optimization ({max_iterations} iterations)...")
151+
152+
best_energy = float('inf')
153+
energies = []
154+
155+
try:
156+
for iteration in range(max_iterations):
157+
optimizer.zero_grad()
158+
159+
# Compute energy
160+
energy_tensor = vqe_model()
161+
total_energy = energy_tensor.sum()
162+
163+
# Backward pass
164+
total_energy.backward()
165+
optimizer.step()
166+
167+
current_energy = total_energy.item()
168+
energies.append(current_energy)
169+
170+
if current_energy < best_energy:
171+
best_energy = current_energy
172+
173+
# Print progress
174+
if iteration % 10 == 0 or iteration == max_iterations - 1:
175+
print(f" Iter {iteration:3d}: Energy = {current_energy:.6f} Ha")
176+
177+
print(f"\n✅ Optimization complete!")
178+
print(f"🎯 Best energy: {best_energy:.6f} Ha")
179+
print(f"📊 Theoretical H2 ground state: ≈ -1.857 Ha")
180+
181+
error = abs(best_energy - (-1.857))
182+
if error < 0.5:
183+
print(f"✅ Good agreement! Error: {error:.3f} Ha")
184+
else:
185+
print(f"⚠️ Large error: {error:.3f} Ha (hardware noise expected)")
186+
187+
return best_energy, energies
188+
189+
except Exception as e:
190+
print(f"❌ VQE optimization failed: {e}")
191+
return None, []
192+
193+
194+
def plot_convergence(energies):
195+
"""Plot VQE convergence (if matplotlib available)."""
196+
try:
197+
import matplotlib.pyplot as plt
198+
199+
plt.figure(figsize=(10, 6))
200+
plt.plot(energies, 'b-', linewidth=2, label='VQE Energy')
201+
plt.axhline(y=-1.857, color='r', linestyle='--', label='Theoretical Ground State')
202+
plt.xlabel('Iteration')
203+
plt.ylabel('Energy (Ha)')
204+
plt.title('VQE Convergence on Quantum Hardware')
205+
plt.legend()
206+
plt.grid(True, alpha=0.3)
207+
plt.tight_layout()
208+
209+
plt.savefig('vqe_convergence.png', dpi=150, bbox_inches='tight')
210+
print("📊 Convergence plot saved as 'vqe_convergence.png'")
211+
212+
except ImportError:
213+
print("⚠️ Matplotlib not available, skipping plot")
214+
215+
216+
def main():
217+
"""Main VQE hardware demo."""
218+
print("🧪 VQE on IBM Quantum Hardware")
219+
print("=" * 50)
220+
221+
print("This example demonstrates running the Variational Quantum Eigensolver")
222+
print("algorithm to find the ground state of the H2 molecule using real")
223+
print("quantum hardware or high-fidelity simulators.")
224+
225+
# Select backend
226+
backend_choice = select_backend()
227+
if backend_choice is None:
228+
print("❌ No backend available")
229+
return False
230+
231+
# Ask for number of iterations
232+
print(f"\n⏱️ How many optimization iterations?")
233+
print(" Simulators: 50-100 iterations recommended")
234+
print(" Real hardware: 20-30 iterations (due to queue time)")
235+
236+
try:
237+
max_iter = int(input("Iterations (press Enter for 30): ").strip() or "30")
238+
max_iter = max(1, min(max_iter, 200)) # Reasonable bounds
239+
except ValueError:
240+
max_iter = 30
241+
242+
# Run VQE
243+
best_energy, energies = run_vqe(backend_choice, max_iter)
244+
245+
if best_energy is not None:
246+
# Plot results if we have data
247+
if len(energies) > 1:
248+
plot_convergence(energies)
249+
250+
print("\n🎉 VQE Demo Complete!")
251+
print("\n📋 Summary:")
252+
print(f" Backend: {backend_choice if isinstance(backend_choice, str) else backend_choice.name}")
253+
print(f" Iterations: {len(energies)}")
254+
print(f" Final energy: {best_energy:.6f} Ha")
255+
print(f" Target energy: -1.857 Ha")
256+
print(f" Error: {abs(best_energy - (-1.857)):.3f} Ha")
257+
258+
return True
259+
else:
260+
print("❌ VQE demo failed")
261+
return False
262+
263+
264+
if __name__ == "__main__":
265+
success = main()
266+
sys.exit(0 if success else 1)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""Example of using the PyTorch backend with the new architecture."""
2+
3+
import torch
4+
from torchquantum.backend import (
5+
ParameterizedQuantumCircuit,
6+
PyTorchBackend,
7+
QuantumExpectation,
8+
QuantumSampling
9+
)
10+
from torchquantum.operator.standard_gates import Hadamard, RX, CNOT, RZ
11+
12+
13+
def create_bell_circuit():
14+
"""Create a simple Bell state preparation circuit."""
15+
circuit = ParameterizedQuantumCircuit(n_wires=2, n_trainable_params=0)
16+
circuit.append_gate(Hadamard, wires=0)
17+
circuit.append_gate(CNOT, wires=[0, 1])
18+
return circuit
19+
20+
21+
def create_vqe_circuit(n_qubits=4, n_layers=2):
22+
"""Create a simple VQE ansatz circuit."""
23+
n_params = n_qubits * n_layers * 2 # RX and RZ for each qubit in each layer
24+
circuit = ParameterizedQuantumCircuit(n_wires=n_qubits, n_trainable_params=n_params)
25+
26+
# Initialize with random parameters
27+
circuit.set_trainable_params(torch.randn(n_params) * 0.1)
28+
29+
param_idx = 0
30+
for layer in range(n_layers):
31+
# Rotation layer
32+
for q in range(n_qubits):
33+
circuit.append_gate(RX, wires=q, trainable_idx=param_idx)
34+
param_idx += 1
35+
circuit.append_gate(RZ, wires=q, trainable_idx=param_idx)
36+
param_idx += 1
37+
38+
# Entangling layer
39+
for q in range(0, n_qubits - 1, 2):
40+
circuit.append_gate(CNOT, wires=[q, q + 1])
41+
for q in range(1, n_qubits - 1, 2):
42+
circuit.append_gate(CNOT, wires=[q, q + 1])
43+
44+
return circuit
45+
46+
47+
def main():
48+
# Example 1: Bell state with expectation values
49+
print("=== Example 1: Bell State ===")
50+
bell_circuit = create_bell_circuit()
51+
52+
# Create backend
53+
backend = PyTorchBackend(device='cpu')
54+
55+
# Define observables
56+
observables = ['ZZ', 'XX', 'YY'] # Bell state correlations
57+
58+
# Create expectation module
59+
expectation = QuantumExpectation(bell_circuit, backend, observables)
60+
61+
# Compute expectations (no input params for Bell state)
62+
exp_vals = expectation()
63+
print(f"Expectation values: {exp_vals}")
64+
print(f"<ZZ> = {exp_vals[0, 0].item():.4f}, <XX> = {exp_vals[0, 1].item():.4f}, <YY> = {exp_vals[0, 2].item():.4f}")
65+
66+
# Example 2: VQE circuit with optimization
67+
print("\n=== Example 2: VQE Circuit ===")
68+
vqe_circuit = create_vqe_circuit(n_qubits=4, n_layers=2)
69+
70+
# Define Hamiltonian as linear combination
71+
hamiltonian = [
72+
{'ZIII': 0.5, 'IZII': 0.5, 'IIZI': 0.5, 'IIIZ': 0.5}, # Sum of Z operators
73+
{'XXII': 0.25, 'IIXX': 0.25} # Nearest neighbor interactions
74+
]
75+
76+
# Create model
77+
model = QuantumExpectation(vqe_circuit, backend, hamiltonian)
78+
79+
# Optimize
80+
optimizer = torch.optim.Adam([vqe_circuit.trainable_params], lr=0.1)
81+
82+
print("Optimizing...")
83+
for step in range(50):
84+
optimizer.zero_grad()
85+
energies = model() # Shape: [1, 2] for 2 Hamiltonians
86+
total_energy = energies.sum()
87+
total_energy.backward()
88+
optimizer.step()
89+
90+
if step % 10 == 0:
91+
print(f"Step {step}: Energy = {total_energy.item():.4f}")
92+
93+
# Example 3: Sampling
94+
print("\n=== Example 3: Sampling ===")
95+
sampler = QuantumSampling(vqe_circuit, backend, n_samples=1000, wires=None)
96+
samples = sampler() # Returns list of bitstrings
97+
98+
# Count occurrences
99+
from collections import Counter
100+
counts = Counter(samples[0]) # First (and only) batch
101+
print("Top 5 measurement outcomes:")
102+
for bitstring, count in counts.most_common(5):
103+
print(f" |{bitstring}⟩: {count/1000:.3f}")
104+
105+
# Example 4: GPU support (if available)
106+
if torch.cuda.is_available():
107+
print("\n=== Example 4: GPU Acceleration ===")
108+
109+
# Create a simple Bell circuit for GPU test
110+
simple_circuit = ParameterizedQuantumCircuit(n_wires=2, n_trainable_params=0)
111+
simple_circuit.append_gate(Hadamard, wires=0)
112+
simple_circuit.append_gate(CNOT, wires=[0, 1])
113+
114+
backend_gpu = PyTorchBackend(device='cuda')
115+
simple_observables = ['ZZ']
116+
117+
expectation_gpu = QuantumExpectation(simple_circuit, backend_gpu, simple_observables)
118+
119+
print("Testing GPU computation...")
120+
energies_gpu = expectation_gpu()
121+
print(f"GPU Bell state <ZZ> expectation: {energies_gpu.item():.4f}")
122+
print(f"GPU computation successful!")
123+
else:
124+
print("\n=== Example 4: GPU Acceleration ===")
125+
print("CUDA not available, skipping GPU example")
126+
127+
128+
if __name__ == "__main__":
129+
main()

0 commit comments

Comments
 (0)