"""
Abstract Base Classes for Quantum Backends.
Defines the interface that all quantum backends must implement,
ensuring consistent behavior across simulators and real hardware.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Union
import numpy as np
from qiskit import QuantumCircuit
from qiskit.result import Result
[docs]
@dataclass
class BackendConfig:
"""Configuration for quantum backend execution."""
shots: int = 1024
optimization_level: int = 3
seed_simulator: Optional[int] = None
memory: bool = False
init_qubits: bool = True
# Hardware-specific options
dynamic_decoupling: bool = False
resilience_level: int = 0
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Convert config to dictionary for backend options."""
return {k: v for k, v in self.__dict__.items() if v is not None}
[docs]
@dataclass
class ExecutionResult:
"""Standardized result from quantum circuit execution."""
# Core results
counts: Dict[str, int] = field(default_factory=dict)
statevector: Optional[np.ndarray] = None
probabilities: Optional[np.ndarray] = None
# Metadata
shots: int = 0
success: bool = True
backend_name: str = ""
execution_time: float = 0.0
job_id: Optional[str] = None
# Circuit info
num_qubits: int = 0
depth: int = 0
gate_counts: Dict[str, int] = field(default_factory=dict)
# Error information
error_message: Optional[str] = None
[docs]
def get_statevector_2d(self, nx: int, ny: int) -> np.ndarray:
"""
Reshape statevector to 2D grid for CTEM wavefunction.
Args:
nx: Number of pixels in x direction
ny: Number of pixels in y direction
Returns:
Complex 2D array of shape (ny, nx) representing the wavefunction
"""
if self.statevector is None:
raise ValueError("No statevector available. Run with statevector simulator.")
expected_size = nx * ny
if len(self.statevector) != expected_size:
raise ValueError(
f"Statevector size {len(self.statevector)} doesn't match "
f"grid size {nx}x{ny}={expected_size}"
)
return self.statevector.reshape((ny, nx))
[docs]
def get_intensity(self, nx: int, ny: int) -> np.ndarray:
"""
Get intensity (|ψ|²) as 2D array.
Args:
nx: Number of pixels in x direction
ny: Number of pixels in y direction
Returns:
Real 2D array of intensity values
"""
psi = self.get_statevector_2d(nx, ny)
return np.abs(psi) ** 2
[docs]
class Backend(ABC):
"""
Abstract base class for quantum backends.
All backends (simulator, IBM hardware, etc.) must implement this interface
to ensure consistent behavior across the quantum CTEM package.
"""
def __init__(self, name: str = "base"):
self.name = name
self._is_connected = False
@property
def is_connected(self) -> bool:
"""Check if backend is ready for execution."""
return self._is_connected
[docs]
@abstractmethod
def connect(self) -> bool:
"""
Establish connection to the backend.
Returns:
True if connection successful, False otherwise
"""
pass
[docs]
@abstractmethod
def run(
self,
circuit: QuantumCircuit,
config: Optional[BackendConfig] = None,
) -> ExecutionResult:
"""
Execute a quantum circuit on this backend.
Args:
circuit: Qiskit QuantumCircuit to execute
config: Execution configuration options
Returns:
ExecutionResult with counts, statevector (if available), and metadata
"""
pass
[docs]
@abstractmethod
def run_batch(
self,
circuits: List[QuantumCircuit],
config: Optional[BackendConfig] = None,
) -> List[ExecutionResult]:
"""
Execute multiple circuits in a batch.
Args:
circuits: List of QuantumCircuit objects
config: Execution configuration options
Returns:
List of ExecutionResult objects
"""
pass
[docs]
def transpile(
self,
circuit: QuantumCircuit,
optimization_level: int = 3,
) -> QuantumCircuit:
"""
Transpile circuit for this backend.
Args:
circuit: Circuit to transpile
optimization_level: Qiskit optimization level (0-3)
Returns:
Transpiled circuit
"""
from qiskit import transpile
return transpile(
circuit,
backend=self._get_qiskit_backend(),
optimization_level=optimization_level,
)
@abstractmethod
def _get_qiskit_backend(self):
"""Get the underlying Qiskit backend object."""
pass
[docs]
def get_circuit_metrics(self, circuit: QuantumCircuit) -> Dict[str, Any]:
"""
Get metrics for a circuit on this backend.
Args:
circuit: Circuit to analyze
Returns:
Dictionary with depth, gate counts, etc.
"""
transpiled = self.transpile(circuit)
return {
"original_depth": circuit.depth(),
"transpiled_depth": transpiled.depth(),
"num_qubits": circuit.num_qubits,
"gate_counts": dict(transpiled.count_ops()),
"two_qubit_gates": sum(
count
for gate, count in transpiled.count_ops().items()
if gate in ["cx", "cz", "ecr", "rzz"]
),
}
def __repr__(self) -> str:
status = "connected" if self.is_connected else "disconnected"
return f"{self.__class__.__name__}(name='{self.name}', status='{status}')"