Running on Real Quantum Hardware #
Connecting to IBM Quantum #
from qiskit_ibm_runtime import QiskitRuntimeService
# Load your account
service = QiskitRuntimeService(channel="ibm_quantum")
# List available backends
backends = service.backends()
for backend in backends:
print(f"Backend: {backend.name}")
print(f" Qubits: {backend.num_qubits}")
print(f" Status: {backend.status().status_msg}\n")
# Select a backend
backend = service.backend("ibm_brisbane") # Example backend name
Submitting Jobs to Real Hardware #
from qiskit import QuantumCircuit, transpile
# Create your circuit
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
# Transpile for the specific backend
transpiled_qc = transpile(qc, backend=backend, optimization_level=3)
# Submit job
job = backend.run(transpiled_qc, shots=1024)
# Monitor job status
print(f"Job ID: {job.job_id()}")
print(f"Job Status: {job.status()}")
# Get results (this will wait for job completion)
result = job.result()
counts = result.get_counts()
print("Results:", counts)
Using Qiskit Runtime #
from qiskit_ibm_runtime import Session, Sampler, Estimator
# Create a session
with Session(service=service, backend="ibm_brisbane") as session:
# Use Sampler for sampling circuits
sampler = Sampler(session=session)
job = sampler.run(qc, shots=1000)
result = job.result()
print(result)
Circuit Optimization and Transpilation #
Understanding Transpilation #
Transpilation converts your quantum circuit to match the constraints of specific hardware.
from qiskit import transpile
from qiskit.transpiler import CouplingMap, Layout
# Create a circuit
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
# Basic transpilation
transpiled = transpile(qc, backend=backend)
# Custom transpilation with optimization levels
# Level 0: Just mapping, no optimization
# Level 1: Light optimization
# Level 2: Medium optimization
# Level 3: Heavy optimization
transpiled = transpile(
qc,
backend=backend,
optimization_level=3,
seed_transpiler=42 # For reproducibility
)
print(f"Original depth: {qc.depth()}")
print(f"Transpiled depth: {transpiled.depth()}")
Custom Pass Manager #
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
Unroller,
Optimize1qGates,
CXCancellation,
CommutativeCancellation
)
# Create custom pass manager
pm = PassManager()
pm.append(Unroller(['u', 'cx']))
pm.append(Optimize1qGates())
pm.append(CXCancellation())
pm.append(CommutativeCancellation())
# Apply passes
optimized_qc = pm.run(qc)
Noise Simulation and Mitigation #
Creating Noise Models #
from qiskit_aer.noise import (
NoiseModel,
depolarizing_error,
thermal_relaxation_error
)
# Create a noise model
noise_model = NoiseModel()
# Add depolarizing error
error_1q = depolarizing_error(0.001, 1) # 1-qubit error
error_2q = depolarizing_error(0.01, 2) # 2-qubit error
# Add errors to gates
noise_model.add_all_qubit_quantum_error(error_1q, ['h', 'x', 'y', 'z'])
noise_model.add_all_qubit_quantum_error(error_2q, ['cx'])
# Add thermal relaxation
t1 = 50e3 # T1 time (ns)
t2 = 70e3 # T2 time (ns)
gate_time = 100 # Gate time (ns)
thermal_error = thermal_relaxation_error(t1, t2, gate_time)
noise_model.add_all_qubit_quantum_error(thermal_error, ['h', 'x'])
print(noise_model)
Simulating with Noise #
from qiskit_aer import AerSimulator
# Create noisy simulator
noisy_simulator = AerSimulator(noise_model=noise_model)
# Run circuit with noise
job = noisy_simulator.run(transpiled_qc, shots=1024)
result = job.result()
noisy_counts = result.get_counts()
print("Noisy results:", noisy_counts)
Noise Mitigation #
from qiskit.ignis.mitigation.measurement import (
complete_meas_cal,
CompleteMeasFitter
)
# Generate calibration circuits
qr = QuantumRegister(2)
meas_calibs, state_labels = complete_meas_cal(qr=qr)
# Run calibration circuits
cal_job = simulator.run(meas_calibs, shots=1000)
cal_results = cal_job.result()
# Create measurement fitter
meas_fitter = CompleteMeasFitter(cal_results, state_labels)
# Apply mitigation
mitigated_results = meas_fitter.filter.apply(result)
mitigated_counts = mitigated_results.get_counts()
Advanced Circuit Techniques #
Dynamic Circuits (Mid-Circuit Measurements) #
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
# Create registers
qr = QuantumRegister(2, 'q')
cr = ClassicalRegister(2, 'c')
qc = QuantumCircuit(qr, cr)
# Perform mid-circuit measurement
qc.h(0)
qc.measure(0, 0)
# Conditional operation based on measurement
qc.x(1).c_if(cr[0], 1) # Apply X if measurement was 1
qc.measure(1, 1)
Pulse-Level Control #
from qiskit import pulse
from qiskit.pulse import library as pulse_lib
# Get backend's pulse properties
backend = service.backend("ibm_brisbane")
# Create a pulse schedule
with pulse.build(backend) as my_schedule:
# Define a Gaussian pulse
gaussian_pulse = pulse_lib.Gaussian(
duration=160,
amp=0.5,
sigma=40
)
# Play pulse on drive channel of qubit 0
drive_chan = pulse.drive_channel(0)
pulse.play(gaussian_pulse, drive_chan)
# Measure
pulse.measure_all()
# Execute pulse schedule
job = backend.run(my_schedule)
Circuit Synthesis and Decomposition #
Unitary Synthesis #
import numpy as np
from qiskit.quantum_info import Operator
from qiskit.synthesis import OneQubitEulerDecomposer
# Define a unitary matrix
unitary_matrix = np.array([
[np.cos(np.pi/4), -np.sin(np.pi/4)],
[np.sin(np.pi/4), np.cos(np.pi/4)]
])
# Create operator
operator = Operator(unitary_matrix)
# Synthesize circuit
qc = QuantumCircuit(1)
qc.unitary(operator, [0])
# Decompose into basic gates
decomposed = qc.decompose()
print(decomposed)
Two-Qubit Gate Decomposition #
from qiskit.synthesis import TwoQubitBasisDecomposer
from qiskit.circuit.library import CXGate
# Create decomposer
decomposer = TwoQubitBasisDecomposer(CXGate())
# Decompose a unitary into CX and single-qubit gates
two_qubit_unitary = Operator.from_label('CZ')
circuit = decomposer(two_qubit_unitary)
Performance Optimization #
Circuit Caching #
from functools import lru_cache
@lru_cache(maxsize=None)
def create_qft_circuit(n):
"""Cached QFT circuit creation"""
qc = QuantumCircuit(n)
# ... QFT implementation
return qc
# First call computes and caches
qft_3 = create_qft_circuit(3)
# Second call retrieves from cache
qft_3_cached = create_qft_circuit(3)
Batch Job Submission #
# Submit multiple circuits at once
circuits = [qc1, qc2, qc3, qc4]
transpiled_circuits = transpile(circuits, backend=backend)
# Run as batch
job = backend.run(transpiled_circuits, shots=1024)
results = job.result()
# Access individual results
for i, circuit in enumerate(circuits):
counts = results.get_counts(i)
print(f"Circuit {i} results: {counts}")
Quantum State and Process Tomography #
State Tomography #
from qiskit.ignis.verification.tomography import (
state_tomography_circuits,
StateTomographyFitter
)
from qiskit.quantum_info import state_fidelity
# Create a Bell state
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# Generate tomography circuits
tomo_circuits = state_tomography_circuits(qc, [0, 1])
# Run tomography circuits
job = simulator.run(tomo_circuits, shots=5000)
result = job.result()
# Fit the result to reconstruct state
fitter = StateTomographyFitter(result, tomo_circuits)
rho = fitter.fit()
# Calculate fidelity
ideal_state = Statevector.from_label('00')
fidelity = state_fidelity(rho, ideal_state)
print(f"State fidelity: {fidelity}")
Process Tomography #
from qiskit.ignis.verification.tomography import (
process_tomography_circuits,
ProcessTomographyFitter
)
# Define the process (e.g., Hadamard gate)
qc = QuantumCircuit(1)
qc.h(0)
# Generate process tomography circuits
tomo_circuits = process_tomography_circuits(qc, [0])
# Run and fit
job = simulator.run(tomo_circuits, shots=5000)
result = job.result()
fitter = ProcessTomographyFitter(result, tomo_circuits)
chi = fitter.fit()
Debugging and Profiling #
Statevector Simulation #
from qiskit.quantum_info import Statevector
# Get the statevector at any point
qc = QuantumCircuit(2)
qc.h(0)
# Get statevector
state = Statevector(qc)
print("Statevector:", state)
# Visualize
from qiskit.visualization import plot_state_qsphere
plot_state_qsphere(state)
Unitary Simulation #
from qiskit.quantum_info import Operator
# Get the unitary matrix
unitary = Operator(qc)
print("Unitary matrix:\n", unitary.data)
Circuit Profiling #
import time
# Time circuit execution
start = time.time()
job = simulator.run(qc, shots=1000)
result = job.result()
elapsed = time.time() - start
print(f"Execution time: {elapsed:.3f}s")
print(f"Circuit depth: {qc.depth()}")
print(f"Gate count: {sum(qc.count_ops().values())}")
Best Practices #
1. Always Transpile for Target Backend #
# Good
transpiled = transpile(qc, backend=backend, optimization_level=3)
job = backend.run(transpiled)
# Avoid running raw circuits on hardware
2. Use Error Mitigation #
# Apply measurement error mitigation
# Use dynamical decoupling
# Implement zero-noise extrapolation
3. Minimize Circuit Depth #
# Use native gates when possible
# Apply optimization passes
# Remove unnecessary gates
4. Handle Job Failures Gracefully #
try:
result = job.result()
except Exception as e:
print(f"Job failed: {e}")
# Implement retry logic
Further Learning Resources #
- Qiskit Textbook: qiskit.org/textbook
- Qiskit Documentation: qiskit.org/documentation
- IBM Quantum Learning: quantum-computing.ibm.com/learn
- Qiskit GitHub: github.com/Qiskit
Conclusion #
You now have the tools to:
- Run circuits on real quantum hardware
- Optimize circuits for specific backends
- Simulate and mitigate noise
- Implement advanced quantum algorithms
- Debug and profile quantum programs
Continue exploring, experimenting, and building quantum applications with Qiskit!