"""
IBM Quantum Hardware Deployment Testing
Validates quantum CTEM implementation for deployment on IBM Quantum systems.
Tests real hardware constraints, connectivity, basis gates, and error mitigation.
Week 4 Task 1.8: IBM Hardware Validation
Author: QuScope Development Team
Date: January 2025
"""
import time
import warnings
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import Statevector, state_fidelity
from qiskit.transpiler import CouplingMap
from .circuit_optimization import HardwareTranspiler
from .classical_integration import QuantumClassicalBridge
from .quantum_wave_function import QuantumWaveFunction
[docs]
@dataclass
class IBMDeviceProfile:
"""
IBM Quantum device specifications.
Attributes:
name: Device name (e.g., 'ibm_kyoto')
num_qubits: Total number of qubits
basis_gates: List of native gates
coupling_map: Qubit connectivity as list of edges
single_qubit_error: Average single-qubit gate error rate
two_qubit_error: Average two-qubit gate error rate
readout_error: Average measurement error rate
t1_us: T1 coherence time in microseconds
t2_us: T2 coherence time in microseconds
"""
name: str
num_qubits: int
basis_gates: List[str]
coupling_map: Optional[List[List[int]]]
single_qubit_error: float # Average 1q gate error
two_qubit_error: float # Average 2q gate error
readout_error: float # Average readout error
t1_us: float # T1 coherence time (microseconds)
t2_us: float # T2 coherence time (microseconds)
[docs]
@staticmethod
def ibm_kyoto() -> "IBMDeviceProfile":
"""
IBM Kyoto device profile (127 qubits, heavy-hex topology).
One of the most advanced IBM Quantum systems available.
"""
# Create a simple heavy-hex-inspired coupling map
coupling_list = []
for i in range(126):
coupling_list.append([i, i + 1]) # Linear backbone
if i % 3 == 0 and i + 3 < 127:
coupling_list.append([i, i + 3]) # Hexagonal connections
return IBMDeviceProfile(
name="ibm_kyoto",
num_qubits=127,
basis_gates=["id", "rz", "sx", "x", "cx", "reset"],
coupling_map=coupling_list,
single_qubit_error=1.0e-4,
two_qubit_error=1.0e-2,
readout_error=1.5e-2,
t1_us=100.0,
t2_us=150.0,
)
[docs]
@staticmethod
def ibm_brisbane() -> "IBMDeviceProfile":
"""
IBM Brisbane device profile (127 qubits, heavy-hex topology).
Slightly better single-qubit performance than Kyoto.
"""
coupling_list = []
for i in range(126):
coupling_list.append([i, i + 1])
if i % 3 == 0 and i + 3 < 127:
coupling_list.append([i, i + 3])
return IBMDeviceProfile(
name="ibm_brisbane",
num_qubits=127,
basis_gates=["id", "rz", "sx", "x", "cx", "reset"],
coupling_map=coupling_list,
single_qubit_error=8.0e-5,
two_qubit_error=9.0e-3,
readout_error=1.2e-2,
t1_us=120.0,
t2_us=180.0,
)
[docs]
@staticmethod
def ibm_nazca() -> "IBMDeviceProfile":
"""
IBM Nazca device profile (127 qubits, heavy-hex topology).
Good balance of qubit count and error rates.
"""
coupling_list = []
for i in range(126):
coupling_list.append([i, i + 1])
if i % 3 == 0 and i + 3 < 127:
coupling_list.append([i, i + 3])
return IBMDeviceProfile(
name="ibm_nazca",
num_qubits=127,
basis_gates=["id", "rz", "sx", "x", "cx", "reset"],
coupling_map=coupling_list,
single_qubit_error=9.0e-5,
two_qubit_error=1.1e-2,
readout_error=1.3e-2,
t1_us=110.0,
t2_us=160.0,
)
[docs]
@staticmethod
def ibm_sherbrooke() -> "IBMDeviceProfile":
"""
IBM Sherbrooke device profile (127 qubits, heavy-hex topology).
Best overall coherence times.
"""
coupling_list = []
for i in range(126):
coupling_list.append([i, i + 1])
if i % 3 == 0 and i + 3 < 127:
coupling_list.append([i, i + 3])
return IBMDeviceProfile(
name="ibm_sherbrooke",
num_qubits=127,
basis_gates=["id", "rz", "sx", "x", "cx", "reset"],
coupling_map=coupling_list,
single_qubit_error=8.5e-5,
two_qubit_error=9.5e-3,
readout_error=1.1e-2,
t1_us=130.0,
t2_us=190.0,
)
[docs]
def create_backend(self) -> GenericBackendV2:
"""
Create a GenericBackendV2 instance with this device's specifications.
Returns:
GenericBackendV2 configured with device parameters
"""
return GenericBackendV2(
num_qubits=self.num_qubits,
basis_gates=self.basis_gates,
coupling_map=self.coupling_map,
)
[docs]
def estimate_fidelity(circuit: QuantumCircuit, device: IBMDeviceProfile) -> float:
"""
Estimate circuit fidelity on a given IBM device based on gate counts and error rates.
Args:
circuit: Quantum circuit to estimate fidelity for
device: IBM device profile with error rates
Returns:
Estimated fidelity (0 to 1)
"""
# Count gates
gate_counts = circuit.count_ops()
# Single-qubit gates
single_q_gates = sum(
gate_counts.get(gate, 0)
for gate in ["id", "rz", "sx", "x", "h", "u1", "u2", "u3"]
)
# Two-qubit gates
two_q_gates = gate_counts.get("cx", 0) + gate_counts.get("cz", 0)
# Measurements
num_measurements = circuit.num_qubits # Assume all qubits measured
# Estimate fidelity using error propagation
fidelity = 1.0
fidelity *= (1 - device.single_qubit_error) ** single_q_gates
fidelity *= (1 - device.two_qubit_error) ** two_q_gates
fidelity *= (1 - device.readout_error) ** num_measurements
return fidelity
[docs]
class IBMHardwareValidator:
"""
Validates quantum circuits for deployment on IBM Quantum hardware.
Features:
- Circuit transpilation to IBM basis gates
- Connectivity validation for heavy-hex topology
- Fidelity estimation based on gate counts
- Device comparison and recommendations
- Qubit mapping optimization
Example:
validator = IBMHardwareValidator()
results = validator.validate_for_device('ibm_kyoto', n_qubits=4)
guide = validator.generate_deployment_guide(results)
"""
def __init__(self):
"""Initialize the IBM hardware validator."""
self.devices = {
"ibm_kyoto": IBMDeviceProfile.ibm_kyoto(),
"ibm_brisbane": IBMDeviceProfile.ibm_brisbane(),
"ibm_nazca": IBMDeviceProfile.ibm_nazca(),
"ibm_sherbrooke": IBMDeviceProfile.ibm_sherbrooke(),
}
# Don't instantiate encoder here - create per validation with specific qubit count
[docs]
def validate_circuit_for_ibm(
self, circuit: QuantumCircuit, device_name: str
) -> Dict:
"""
Validate a quantum circuit for a specific IBM device.
Args:
circuit: Quantum circuit to validate
device_name: Name of IBM device ('ibm_kyoto', etc.)
Returns:
Dictionary with validation results including:
- transpiled_circuit: Circuit transpiled to device basis
- original_depth: Original circuit depth
- transpiled_depth: Depth after transpilation
- original_gates: Original gate count
- transpiled_gates: Gate count after transpilation
- estimated_fidelity: Expected fidelity on device
- execution_time_us: Estimated execution time
- warnings: List of any issues
"""
if device_name not in self.devices:
raise ValueError(f"Unknown device: {device_name}")
device = self.devices[device_name]
start_time = time.time()
# Get original circuit metrics
original_depth = circuit.depth()
original_gates = sum(circuit.count_ops().values())
# Transpile to device
backend = device.create_backend()
transpiled = transpile(
circuit, backend=backend, optimization_level=3, seed_transpiler=42
)
# Get transpiled metrics
transpiled_depth = transpiled.depth()
transpiled_gates = sum(transpiled.count_ops().values())
# Estimate fidelity
fidelity = estimate_fidelity(transpiled, device)
# Estimate execution time (gates + readout)
gate_counts = transpiled.count_ops()
single_q_gates = sum(
gate_counts.get(gate, 0) for gate in ["id", "rz", "sx", "x"]
)
two_q_gates = gate_counts.get("cx", 0)
execution_time_us = (
single_q_gates * 0.1 + two_q_gates * 0.5 + circuit.num_qubits * 1.0
) # Rough estimate
# Check for warnings
warnings_list = []
if fidelity < 0.8:
warnings_list.append(
f"Low estimated fidelity ({fidelity:.1%}). "
f"Consider reducing circuit depth or using error mitigation."
)
if execution_time_us > device.t2_us:
warnings_list.append(
f"Execution time ({execution_time_us:.1f}µs) exceeds T2 "
f"({device.t2_us:.1f}µs). Circuit may decohere."
)
if transpiled_depth > 2 * original_depth:
warnings_list.append(
f"Transpilation significantly increased depth "
f"({original_depth} → {transpiled_depth})"
)
elapsed_time = time.time() - start_time
return {
"device_name": device_name,
"transpiled_circuit": transpiled,
"original_depth": original_depth,
"transpiled_depth": transpiled_depth,
"original_gates": original_gates,
"transpiled_gates": transpiled_gates,
"estimated_fidelity": fidelity,
"execution_time_us": execution_time_us,
"t1_us": device.t1_us,
"t2_us": device.t2_us,
"warnings": warnings_list,
"validation_time": elapsed_time,
}
[docs]
def validate_for_device(self, device_name: str, n_qubits: int = 4) -> Dict:
"""
Complete validation workflow for a specific device.
Creates test circuit, validates for device, returns comprehensive results.
Args:
device_name: Name of IBM device
n_qubits: Number of qubits for test circuit (total, will be split for 2D)
Returns:
Validation results dictionary
"""
# Split qubits for 2D representation
n_qubits_per_dim = max(1, n_qubits // 2)
# Create wave function encoder for this test
qwf_encoder = QuantumWaveFunction(n_qubits_per_dim, n_qubits_per_dim)
# Create test wave function (2D Gaussian)
grid_size = 2**n_qubits_per_dim
x = np.linspace(-2, 2, grid_size)
y = np.linspace(-2, 2, grid_size)
X, Y = np.meshgrid(x, y)
psi = np.exp(-(X**2 + Y**2) / 2) # 2D Gaussian
psi = psi / np.linalg.norm(psi) # Normalize
# Encode to quantum circuit
circuit = qwf_encoder.prepare_arbitrary_wave(psi)
# Validate
return self.validate_circuit_for_ibm(circuit, device_name)
[docs]
def compare_devices(self, n_qubits: int = 4) -> Dict[str, Dict]:
"""
Compare all IBM devices for the same test circuit.
Args:
n_qubits: Number of qubits for test circuit
Returns:
Dictionary mapping device names to validation results
"""
results = {}
for device_name in self.devices.keys():
results[device_name] = self.validate_for_device(device_name, n_qubits)
return results
[docs]
def test_qubit_mapping(self, device_name: str, n_qubits: int = 4) -> Dict:
"""
Test different transpilation optimization levels and qubit mappings.
Args:
device_name: Name of IBM device
n_qubits: Number of qubits for test circuit (total)
Returns:
Dictionary with results for different optimization levels
"""
device = self.devices[device_name]
backend = device.create_backend()
# Split qubits for 2D
n_qubits_per_dim = max(1, n_qubits // 2)
qwf_encoder = QuantumWaveFunction(n_qubits_per_dim, n_qubits_per_dim)
# Create test circuit
grid_size = 2**n_qubits_per_dim
x = np.linspace(-2, 2, grid_size)
y = np.linspace(-2, 2, grid_size)
X, Y = np.meshgrid(x, y)
psi = np.exp(-(X**2 + Y**2) / 2)
psi = psi / np.linalg.norm(psi)
circuit = qwf_encoder.prepare_arbitrary_wave(psi)
results = {}
for opt_level in range(4):
transpiled = transpile(
circuit,
backend=backend,
optimization_level=opt_level,
seed_transpiler=42,
)
results[f"opt_level_{opt_level}"] = {
"depth": transpiled.depth(),
"gates": sum(transpiled.count_ops().values()),
"fidelity": estimate_fidelity(transpiled, device),
}
return results
[docs]
def generate_deployment_guide(self, device_results: Dict[str, Dict]) -> str:
"""
Generate microscopist-friendly deployment guide.
Args:
device_results: Results from compare_devices()
Returns:
Markdown-formatted deployment guide
"""
guide = "# IBM Quantum Deployment Guide\n\n"
guide += "## Device Comparison\n\n"
# Sort devices by fidelity
sorted_devices = sorted(
device_results.items(),
key=lambda x: x[1]["estimated_fidelity"],
reverse=True,
)
for device_name, results in sorted_devices:
fidelity = results["estimated_fidelity"]
# Recommendation icon
if fidelity > 0.95:
icon = "✅ EXCELLENT"
elif fidelity > 0.90:
icon = "✅ GOOD"
elif fidelity > 0.80:
icon = "⚠️ ACCEPTABLE"
else:
icon = "❌ NOT RECOMMENDED"
guide += f"### {device_name.upper()} {icon}\n\n"
guide += f"- **Estimated Fidelity**: {fidelity:.1%}\n"
guide += f"- **Circuit Depth**: {results['transpiled_depth']} gates\n"
guide += f"- **Total Gates**: {results['transpiled_gates']}\n"
guide += f"- **Execution Time**: {results['execution_time_us']:.1f} µs\n"
guide += f"- **Coherence Time (T2)**: {results['t2_us']:.1f} µs\n"
if results["warnings"]:
guide += "\n**⚠️ Warnings:**\n"
for warning in results["warnings"]:
guide += f"- {warning}\n"
guide += "\n"
# Best device recommendation
best_device, best_results = sorted_devices[0]
guide += "## Recommendation\n\n"
guide += f"**Use `{best_device}` for best results.**\n\n"
guide += f"Expected fidelity: {best_results['estimated_fidelity']:.1%}\n\n"
# User instructions
guide += "## How to Deploy\n\n"
guide += "```python\n"
guide += "from quscope.quantum_ctem import validate_ibm_deployment\n\n"
guide += f"# Validate for {best_device}\n"
guide += (
f"results = validate_ibm_deployment(device='{best_device}', n_qubits=4)\n"
)
guide += "```\n\n"
return guide
[docs]
def validate_ibm_deployment(device: str = "ibm_kyoto", n_qubits: int = 4) -> Dict:
"""
Convenience function for IBM deployment validation.
Args:
device: IBM device name ('ibm_kyoto', 'ibm_brisbane', 'ibm_nazca', 'ibm_sherbrooke')
n_qubits: Number of qubits for test circuit
Returns:
Validation results dictionary
Example:
>>> results = validate_ibm_deployment('ibm_kyoto', n_qubits=4)
>>> print(f"Estimated fidelity: {results['estimated_fidelity']:.1%}")
"""
validator = IBMHardwareValidator()
print(f"\n{'='*60}")
print(f"IBM Quantum Deployment Validation")
print(f"{'='*60}")
print(f"Device: {device}")
print(f"Qubits: {n_qubits}")
print(f"{'='*60}\n")
# Validate single device
results = validator.validate_for_device(device, n_qubits)
# Print summary
print(f"✓ Circuit validated successfully!")
print(f" Estimated Fidelity: {results['estimated_fidelity']:.1%}")
print(f" Circuit Depth: {results['transpiled_depth']}")
print(f" Total Gates: {results['transpiled_gates']}")
print(f" Execution Time: {results['execution_time_us']:.1f} µs")
if results["warnings"]:
print(f"\n⚠️ Warnings:")
for warning in results["warnings"]:
print(f" • {warning}")
print(f"\n{'='*60}\n")
return results