superquantx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- superquantx/__init__.py +321 -0
- superquantx/algorithms/__init__.py +55 -0
- superquantx/algorithms/base_algorithm.py +413 -0
- superquantx/algorithms/hybrid_classifier.py +628 -0
- superquantx/algorithms/qaoa.py +406 -0
- superquantx/algorithms/quantum_agents.py +1006 -0
- superquantx/algorithms/quantum_kmeans.py +575 -0
- superquantx/algorithms/quantum_nn.py +544 -0
- superquantx/algorithms/quantum_pca.py +499 -0
- superquantx/algorithms/quantum_svm.py +346 -0
- superquantx/algorithms/vqe.py +553 -0
- superquantx/algorithms.py +863 -0
- superquantx/backends/__init__.py +265 -0
- superquantx/backends/base_backend.py +321 -0
- superquantx/backends/braket_backend.py +420 -0
- superquantx/backends/cirq_backend.py +466 -0
- superquantx/backends/ocean_backend.py +491 -0
- superquantx/backends/pennylane_backend.py +419 -0
- superquantx/backends/qiskit_backend.py +451 -0
- superquantx/backends/simulator_backend.py +455 -0
- superquantx/backends/tket_backend.py +519 -0
- superquantx/circuits.py +447 -0
- superquantx/cli/__init__.py +28 -0
- superquantx/cli/commands.py +528 -0
- superquantx/cli/main.py +254 -0
- superquantx/client.py +298 -0
- superquantx/config.py +326 -0
- superquantx/exceptions.py +287 -0
- superquantx/gates.py +588 -0
- superquantx/logging_config.py +347 -0
- superquantx/measurements.py +702 -0
- superquantx/ml.py +936 -0
- superquantx/noise.py +760 -0
- superquantx/utils/__init__.py +83 -0
- superquantx/utils/benchmarking.py +523 -0
- superquantx/utils/classical_utils.py +575 -0
- superquantx/utils/feature_mapping.py +467 -0
- superquantx/utils/optimization.py +410 -0
- superquantx/utils/quantum_utils.py +456 -0
- superquantx/utils/visualization.py +654 -0
- superquantx/version.py +33 -0
- superquantx-0.1.0.dist-info/METADATA +365 -0
- superquantx-0.1.0.dist-info/RECORD +46 -0
- superquantx-0.1.0.dist-info/WHEEL +4 -0
- superquantx-0.1.0.dist-info/entry_points.txt +2 -0
- superquantx-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
"""Quantum computing backends for SuperQuantX.
|
2
|
+
|
3
|
+
This module provides a unified interface to various quantum computing
|
4
|
+
platforms and simulators, including PennyLane, Qiskit, Cirq, and others.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Any, Dict, Optional, Union
|
9
|
+
|
10
|
+
from .base_backend import BaseBackend
|
11
|
+
from .simulator_backend import SimulatorBackend
|
12
|
+
|
13
|
+
|
14
|
+
# Import backends with graceful failure handling
|
15
|
+
try:
|
16
|
+
from .pennylane_backend import PennyLaneBackend
|
17
|
+
except ImportError:
|
18
|
+
PennyLaneBackend = None
|
19
|
+
|
20
|
+
try:
|
21
|
+
from .qiskit_backend import QiskitBackend
|
22
|
+
except ImportError:
|
23
|
+
QiskitBackend = None
|
24
|
+
|
25
|
+
try:
|
26
|
+
from .cirq_backend import CirqBackend
|
27
|
+
except ImportError:
|
28
|
+
CirqBackend = None
|
29
|
+
|
30
|
+
try:
|
31
|
+
from .braket_backend import BraketBackend
|
32
|
+
except ImportError:
|
33
|
+
BraketBackend = None
|
34
|
+
|
35
|
+
try:
|
36
|
+
from .tket_backend import TKETBackend
|
37
|
+
except ImportError:
|
38
|
+
TKETBackend = None
|
39
|
+
|
40
|
+
try:
|
41
|
+
from .ocean_backend import OceanBackend
|
42
|
+
except ImportError:
|
43
|
+
OceanBackend = None
|
44
|
+
|
45
|
+
logger = logging.getLogger(__name__)
|
46
|
+
|
47
|
+
# Registry of available backends
|
48
|
+
BACKEND_REGISTRY = {
|
49
|
+
'simulator': SimulatorBackend,
|
50
|
+
'auto': None, # Auto-selection
|
51
|
+
}
|
52
|
+
|
53
|
+
# Add backends if they were imported successfully
|
54
|
+
if PennyLaneBackend is not None:
|
55
|
+
BACKEND_REGISTRY['pennylane'] = PennyLaneBackend
|
56
|
+
if QiskitBackend is not None:
|
57
|
+
BACKEND_REGISTRY['qiskit'] = QiskitBackend
|
58
|
+
if CirqBackend is not None:
|
59
|
+
BACKEND_REGISTRY['cirq'] = CirqBackend
|
60
|
+
if BraketBackend is not None:
|
61
|
+
BACKEND_REGISTRY['braket'] = BraketBackend
|
62
|
+
if TKETBackend is not None:
|
63
|
+
BACKEND_REGISTRY['tket'] = TKETBackend
|
64
|
+
BACKEND_REGISTRY['quantinuum'] = TKETBackend # Alias for Quantinuum
|
65
|
+
if OceanBackend is not None:
|
66
|
+
BACKEND_REGISTRY['ocean'] = OceanBackend
|
67
|
+
BACKEND_REGISTRY['dwave'] = OceanBackend # Alias for D-Wave
|
68
|
+
|
69
|
+
# Aliases for common backends
|
70
|
+
BACKEND_ALIASES = {
|
71
|
+
'pl': 'pennylane',
|
72
|
+
'penny': 'pennylane',
|
73
|
+
'qk': 'qiskit',
|
74
|
+
'ibm': 'qiskit',
|
75
|
+
'google': 'cirq',
|
76
|
+
'aws': 'braket',
|
77
|
+
'amazon': 'braket',
|
78
|
+
'tk': 'tket',
|
79
|
+
'pytket': 'tket',
|
80
|
+
'h1': 'quantinuum', # Quantinuum H-Series
|
81
|
+
'h2': 'quantinuum',
|
82
|
+
'annealing': 'ocean',
|
83
|
+
'quantum_annealing': 'ocean',
|
84
|
+
'sim': 'simulator',
|
85
|
+
'local': 'simulator',
|
86
|
+
}
|
87
|
+
|
88
|
+
def get_backend(backend: Union[str, BaseBackend], **kwargs) -> BaseBackend:
|
89
|
+
"""Get a quantum backend instance.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
backend: Backend name or instance
|
93
|
+
**kwargs: Backend configuration parameters
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
Backend instance
|
97
|
+
|
98
|
+
Raises:
|
99
|
+
ValueError: If backend is not supported
|
100
|
+
ImportError: If backend dependencies are missing
|
101
|
+
|
102
|
+
Example:
|
103
|
+
>>> backend = get_backend('pennylane', device='default.qubit')
|
104
|
+
>>> backend = get_backend('qiskit', provider='IBMQ')
|
105
|
+
|
106
|
+
"""
|
107
|
+
if isinstance(backend, BaseBackend):
|
108
|
+
return backend
|
109
|
+
|
110
|
+
if not isinstance(backend, str):
|
111
|
+
raise ValueError(f"Backend must be string or BaseBackend instance, got {type(backend)}")
|
112
|
+
|
113
|
+
# Resolve aliases
|
114
|
+
backend_name = BACKEND_ALIASES.get(backend.lower(), backend.lower())
|
115
|
+
|
116
|
+
# Auto-select backend if requested
|
117
|
+
if backend_name == 'auto':
|
118
|
+
backend_name = _auto_select_backend()
|
119
|
+
|
120
|
+
# Get backend class
|
121
|
+
if backend_name not in BACKEND_REGISTRY:
|
122
|
+
available = list(BACKEND_REGISTRY.keys())
|
123
|
+
raise ValueError(f"Backend '{backend_name}' not supported. Available: {available}")
|
124
|
+
|
125
|
+
backend_class = BACKEND_REGISTRY[backend_name]
|
126
|
+
|
127
|
+
try:
|
128
|
+
logger.info(f"Initializing {backend_name} backend")
|
129
|
+
return backend_class(**kwargs)
|
130
|
+
except ImportError as e:
|
131
|
+
logger.error(f"Failed to import {backend_name} backend: {e}")
|
132
|
+
raise ImportError(f"Backend '{backend_name}' requires additional dependencies: {e}")
|
133
|
+
except Exception as e:
|
134
|
+
logger.error(f"Failed to initialize {backend_name} backend: {e}")
|
135
|
+
raise
|
136
|
+
|
137
|
+
def _auto_select_backend() -> str:
|
138
|
+
"""Automatically select the best available backend.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
Backend name
|
142
|
+
|
143
|
+
"""
|
144
|
+
# Try backends in order of preference
|
145
|
+
preference_order = ['pennylane', 'qiskit', 'cirq', 'braket', 'tket', 'simulator']
|
146
|
+
|
147
|
+
for backend_name in preference_order:
|
148
|
+
try:
|
149
|
+
backend_class = BACKEND_REGISTRY[backend_name]
|
150
|
+
# Try to instantiate with minimal config
|
151
|
+
test_backend = backend_class()
|
152
|
+
logger.info(f"Auto-selected backend: {backend_name}")
|
153
|
+
return backend_name
|
154
|
+
except (ImportError, Exception) as e:
|
155
|
+
logger.debug(f"Backend {backend_name} not available: {e}")
|
156
|
+
continue
|
157
|
+
|
158
|
+
# Fallback to simulator
|
159
|
+
logger.warning("No preferred backends available, falling back to simulator")
|
160
|
+
return 'simulator'
|
161
|
+
|
162
|
+
def list_available_backends() -> Dict[str, Dict[str, Any]]:
|
163
|
+
"""List all available backends and their status.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
Dictionary with backend information
|
167
|
+
|
168
|
+
"""
|
169
|
+
backend_info = {}
|
170
|
+
|
171
|
+
for name, backend_class in BACKEND_REGISTRY.items():
|
172
|
+
if name == 'auto' or backend_class is None:
|
173
|
+
continue
|
174
|
+
|
175
|
+
try:
|
176
|
+
# Try to instantiate to check availability
|
177
|
+
test_instance = backend_class()
|
178
|
+
backend_info[name] = {
|
179
|
+
'available': True,
|
180
|
+
'class': backend_class.__name__,
|
181
|
+
'description': getattr(backend_class, '__doc__', '').split('\n')[0] if backend_class.__doc__ else '',
|
182
|
+
'capabilities': getattr(test_instance, 'capabilities', {}),
|
183
|
+
}
|
184
|
+
except ImportError:
|
185
|
+
backend_info[name] = {
|
186
|
+
'available': False,
|
187
|
+
'class': backend_class.__name__,
|
188
|
+
'reason': 'Missing dependencies',
|
189
|
+
}
|
190
|
+
except Exception as e:
|
191
|
+
backend_info[name] = {
|
192
|
+
'available': False,
|
193
|
+
'class': backend_class.__name__,
|
194
|
+
'reason': str(e),
|
195
|
+
}
|
196
|
+
|
197
|
+
return backend_info
|
198
|
+
|
199
|
+
def check_backend_compatibility(backend_name: str) -> Dict[str, Any]:
|
200
|
+
"""Check compatibility and requirements for a specific backend.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
backend_name: Name of the backend to check
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Compatibility information
|
207
|
+
|
208
|
+
"""
|
209
|
+
backend_name = BACKEND_ALIASES.get(backend_name.lower(), backend_name.lower())
|
210
|
+
|
211
|
+
if backend_name not in BACKEND_REGISTRY:
|
212
|
+
return {'compatible': False, 'reason': 'Backend not supported'}
|
213
|
+
|
214
|
+
backend_class = BACKEND_REGISTRY[backend_name]
|
215
|
+
|
216
|
+
try:
|
217
|
+
# Try basic instantiation
|
218
|
+
test_backend = backend_class()
|
219
|
+
|
220
|
+
return {
|
221
|
+
'compatible': True,
|
222
|
+
'backend_class': backend_class.__name__,
|
223
|
+
'requirements_met': True,
|
224
|
+
'capabilities': getattr(test_backend, 'capabilities', {}),
|
225
|
+
'version_info': getattr(test_backend, 'get_version_info', lambda: {})(),
|
226
|
+
}
|
227
|
+
|
228
|
+
except ImportError as e:
|
229
|
+
return {
|
230
|
+
'compatible': False,
|
231
|
+
'reason': 'Missing dependencies',
|
232
|
+
'missing_packages': str(e),
|
233
|
+
'requirements_met': False,
|
234
|
+
}
|
235
|
+
except Exception as e:
|
236
|
+
return {
|
237
|
+
'compatible': False,
|
238
|
+
'reason': str(e),
|
239
|
+
'requirements_met': False,
|
240
|
+
}
|
241
|
+
|
242
|
+
# Make backend classes available at module level
|
243
|
+
__all__ = [
|
244
|
+
'BaseBackend',
|
245
|
+
'SimulatorBackend',
|
246
|
+
'get_backend',
|
247
|
+
'list_available_backends',
|
248
|
+
'check_backend_compatibility',
|
249
|
+
'BACKEND_REGISTRY',
|
250
|
+
'BACKEND_ALIASES',
|
251
|
+
]
|
252
|
+
|
253
|
+
# Add available backends to __all__
|
254
|
+
if PennyLaneBackend is not None:
|
255
|
+
__all__.append('PennyLaneBackend')
|
256
|
+
if QiskitBackend is not None:
|
257
|
+
__all__.append('QiskitBackend')
|
258
|
+
if CirqBackend is not None:
|
259
|
+
__all__.append('CirqBackend')
|
260
|
+
if BraketBackend is not None:
|
261
|
+
__all__.append('BraketBackend')
|
262
|
+
if TKETBackend is not None:
|
263
|
+
__all__.append('TKETBackend')
|
264
|
+
if OceanBackend is not None:
|
265
|
+
__all__.append('OceanBackend')
|
@@ -0,0 +1,321 @@
|
|
1
|
+
"""Base backend interface for quantum computing platforms.
|
2
|
+
|
3
|
+
This module defines the abstract interface that all quantum backends
|
4
|
+
must implement to work with SuperQuantX algorithms.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any, Dict, List, Optional, Union
|
10
|
+
|
11
|
+
import numpy as np
|
12
|
+
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
class BaseBackend(ABC):
|
17
|
+
"""Abstract base class for quantum computing backends.
|
18
|
+
|
19
|
+
This class defines the interface that all quantum backends must implement
|
20
|
+
to provide quantum circuit execution, measurement, and algorithm-specific
|
21
|
+
operations for SuperQuantX.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
device: Device or simulator to use
|
25
|
+
shots: Default number of measurement shots
|
26
|
+
**kwargs: Backend-specific configuration
|
27
|
+
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, device: Optional[str] = None, shots: int = 1024, **kwargs):
|
31
|
+
self.device = device
|
32
|
+
self.shots = shots
|
33
|
+
self.config = kwargs
|
34
|
+
self.capabilities = {}
|
35
|
+
|
36
|
+
self._initialize_backend()
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
def _initialize_backend(self) -> None:
|
40
|
+
"""Initialize backend-specific components."""
|
41
|
+
pass
|
42
|
+
|
43
|
+
# ========================================================================
|
44
|
+
# Core Circuit Operations
|
45
|
+
# ========================================================================
|
46
|
+
|
47
|
+
@abstractmethod
|
48
|
+
def create_circuit(self, n_qubits: int) -> Any:
|
49
|
+
"""Create a quantum circuit with n qubits."""
|
50
|
+
pass
|
51
|
+
|
52
|
+
@abstractmethod
|
53
|
+
def add_gate(self, circuit: Any, gate: str, qubits: Union[int, List[int]],
|
54
|
+
params: Optional[List[float]] = None) -> Any:
|
55
|
+
"""Add a quantum gate to the circuit."""
|
56
|
+
pass
|
57
|
+
|
58
|
+
@abstractmethod
|
59
|
+
def add_measurement(self, circuit: Any, qubits: Optional[List[int]] = None) -> Any:
|
60
|
+
"""Add measurement operations to the circuit."""
|
61
|
+
pass
|
62
|
+
|
63
|
+
@abstractmethod
|
64
|
+
def execute_circuit(self, circuit: Any, shots: Optional[int] = None) -> Dict[str, Any]:
|
65
|
+
"""Execute quantum circuit and return results."""
|
66
|
+
pass
|
67
|
+
|
68
|
+
@abstractmethod
|
69
|
+
def get_statevector(self, circuit: Any) -> np.ndarray:
|
70
|
+
"""Get the statevector from a quantum circuit."""
|
71
|
+
pass
|
72
|
+
|
73
|
+
# ========================================================================
|
74
|
+
# Quantum Algorithm Support
|
75
|
+
# ========================================================================
|
76
|
+
|
77
|
+
def create_feature_map(self, n_features: int, feature_map: str, reps: int = 1) -> Any:
|
78
|
+
"""Create quantum feature map for data encoding."""
|
79
|
+
logger.warning(f"Feature map '{feature_map}' not implemented in {self.__class__.__name__}")
|
80
|
+
return self.create_circuit(n_features)
|
81
|
+
|
82
|
+
def compute_kernel_matrix(self, X1: np.ndarray, X2: np.ndarray,
|
83
|
+
feature_map: Any, shots: Optional[int] = None) -> np.ndarray:
|
84
|
+
"""Compute quantum kernel matrix between data points."""
|
85
|
+
logger.warning(f"Kernel matrix computation not implemented in {self.__class__.__name__}")
|
86
|
+
# Fallback to RBF kernel
|
87
|
+
from sklearn.metrics.pairwise import rbf_kernel
|
88
|
+
return rbf_kernel(X1, X2)
|
89
|
+
|
90
|
+
def create_ansatz(self, ansatz_type: str, n_qubits: int, params: np.ndarray,
|
91
|
+
include_custom_gates: bool = False) -> Any:
|
92
|
+
"""Create parameterized ansatz circuit."""
|
93
|
+
logger.warning(f"Ansatz '{ansatz_type}' not implemented in {self.__class__.__name__}")
|
94
|
+
return self.create_circuit(n_qubits)
|
95
|
+
|
96
|
+
def compute_expectation(self, circuit: Any, hamiltonian: Any,
|
97
|
+
shots: Optional[int] = None) -> float:
|
98
|
+
"""Compute expectation value of Hamiltonian."""
|
99
|
+
logger.warning(f"Expectation computation not implemented in {self.__class__.__name__}")
|
100
|
+
return 0.0
|
101
|
+
|
102
|
+
def create_qaoa_circuit(self, n_qubits: int, gammas: np.ndarray, betas: np.ndarray,
|
103
|
+
problem_hamiltonian: Any, mixer_hamiltonian: Any,
|
104
|
+
initial_state: Any, problem_instance: Any) -> Any:
|
105
|
+
"""Create QAOA circuit with given parameters."""
|
106
|
+
logger.warning(f"QAOA circuit not implemented in {self.__class__.__name__}")
|
107
|
+
return self.create_circuit(n_qubits)
|
108
|
+
|
109
|
+
def execute_qaoa(self, circuit: Any, problem_hamiltonian: Any, problem_instance: Any,
|
110
|
+
shots: Optional[int] = None) -> float:
|
111
|
+
"""Execute QAOA circuit and return expectation value."""
|
112
|
+
logger.warning(f"QAOA execution not implemented in {self.__class__.__name__}")
|
113
|
+
return 0.0
|
114
|
+
|
115
|
+
def sample_circuit(self, circuit: Any, shots: Optional[int] = None) -> np.ndarray:
|
116
|
+
"""Sample bit strings from quantum circuit."""
|
117
|
+
result = self.execute_circuit(circuit, shots)
|
118
|
+
# Convert result to bit string array
|
119
|
+
return self._result_to_bitstrings(result)
|
120
|
+
|
121
|
+
# ========================================================================
|
122
|
+
# Data Encoding and Processing
|
123
|
+
# ========================================================================
|
124
|
+
|
125
|
+
def encode_data_point(self, data: np.ndarray, encoding: str, n_qubits: int) -> Any:
|
126
|
+
"""Encode classical data into quantum state."""
|
127
|
+
circuit = self.create_circuit(n_qubits)
|
128
|
+
|
129
|
+
if encoding == 'amplitude':
|
130
|
+
return self._amplitude_encoding(circuit, data)
|
131
|
+
elif encoding == 'angle':
|
132
|
+
return self._angle_encoding(circuit, data)
|
133
|
+
elif encoding == 'basis':
|
134
|
+
return self._basis_encoding(circuit, data)
|
135
|
+
else:
|
136
|
+
logger.warning(f"Encoding '{encoding}' not implemented, using angle encoding")
|
137
|
+
return self._angle_encoding(circuit, data)
|
138
|
+
|
139
|
+
def _amplitude_encoding(self, circuit: Any, data: np.ndarray) -> Any:
|
140
|
+
"""Amplitude encoding of data."""
|
141
|
+
# Normalize data
|
142
|
+
norm = np.linalg.norm(data)
|
143
|
+
if norm > 0:
|
144
|
+
normalized_data = data / norm
|
145
|
+
else:
|
146
|
+
normalized_data = data
|
147
|
+
|
148
|
+
# Simple implementation - would need proper state preparation
|
149
|
+
logger.warning("Amplitude encoding not fully implemented")
|
150
|
+
return circuit
|
151
|
+
|
152
|
+
def _angle_encoding(self, circuit: Any, data: np.ndarray) -> Any:
|
153
|
+
"""Angle encoding of data into rotation gates."""
|
154
|
+
for i, value in enumerate(data):
|
155
|
+
if i < self._get_n_qubits(circuit):
|
156
|
+
self.add_gate(circuit, 'RY', i, [value])
|
157
|
+
return circuit
|
158
|
+
|
159
|
+
def _basis_encoding(self, circuit: Any, data: np.ndarray) -> Any:
|
160
|
+
"""Basis encoding of data."""
|
161
|
+
# Convert to binary and encode
|
162
|
+
binary_data = np.unpackbits(data.astype(np.uint8))
|
163
|
+
for i, bit in enumerate(binary_data):
|
164
|
+
if i < self._get_n_qubits(circuit) and bit:
|
165
|
+
self.add_gate(circuit, 'X', i)
|
166
|
+
return circuit
|
167
|
+
|
168
|
+
# ========================================================================
|
169
|
+
# Machine Learning Specific Operations
|
170
|
+
# ========================================================================
|
171
|
+
|
172
|
+
def compute_quantum_distance(self, x1: np.ndarray, x2: np.ndarray, metric: str,
|
173
|
+
encoding: str, n_qubits: int, shots: int) -> float:
|
174
|
+
"""Compute quantum distance between two data points."""
|
175
|
+
# Encode both data points
|
176
|
+
circuit1 = self.encode_data_point(x1, encoding, n_qubits)
|
177
|
+
circuit2 = self.encode_data_point(x2, encoding, n_qubits)
|
178
|
+
|
179
|
+
# Simple distance approximation using overlap
|
180
|
+
# This is a placeholder - actual implementation would use swap test or similar
|
181
|
+
if metric == 'euclidean':
|
182
|
+
return np.linalg.norm(x1 - x2)
|
183
|
+
elif metric == 'manhattan':
|
184
|
+
return np.sum(np.abs(x1 - x2))
|
185
|
+
else:
|
186
|
+
return np.linalg.norm(x1 - x2)
|
187
|
+
|
188
|
+
def execute_qnn(self, input_data: np.ndarray, weights: np.ndarray,
|
189
|
+
quantum_layers: List[Dict], classical_layers: List[Dict],
|
190
|
+
encoding: str, measurement: str, shots: int) -> np.ndarray:
|
191
|
+
"""Execute quantum neural network."""
|
192
|
+
batch_size = input_data.shape[0]
|
193
|
+
n_qubits = len(weights) // len(quantum_layers) if quantum_layers else 1
|
194
|
+
|
195
|
+
# Get expected output size from classical layers
|
196
|
+
expected_output_size = None
|
197
|
+
if classical_layers:
|
198
|
+
for layer in classical_layers:
|
199
|
+
if 'units' in layer:
|
200
|
+
expected_output_size = layer['units']
|
201
|
+
break
|
202
|
+
|
203
|
+
|
204
|
+
outputs = []
|
205
|
+
for sample in input_data:
|
206
|
+
# Create circuit
|
207
|
+
circuit = self.create_circuit(n_qubits)
|
208
|
+
|
209
|
+
# Encode input data
|
210
|
+
circuit = self.encode_data_point(sample, encoding, n_qubits)
|
211
|
+
|
212
|
+
# Add variational layers
|
213
|
+
param_idx = 0
|
214
|
+
for layer in quantum_layers:
|
215
|
+
layer_params = weights[param_idx:param_idx + n_qubits * 2]
|
216
|
+
circuit = self._add_variational_layer(circuit, layer_params)
|
217
|
+
param_idx += n_qubits * 2
|
218
|
+
|
219
|
+
# Measure
|
220
|
+
if measurement == 'expectation':
|
221
|
+
# Compute expectation values
|
222
|
+
result = self._compute_pauli_expectation(circuit, ['Z'] * n_qubits)
|
223
|
+
else:
|
224
|
+
# Sample and get probabilities
|
225
|
+
result = self.execute_circuit(circuit, shots)
|
226
|
+
result = self._result_to_probabilities(result)
|
227
|
+
|
228
|
+
# Apply classical layers if present
|
229
|
+
original_length = len(result)
|
230
|
+
if classical_layers and expected_output_size:
|
231
|
+
# Simple linear transformation to get desired output size
|
232
|
+
if len(result) != expected_output_size:
|
233
|
+
# Reshape/transform the quantum output to match expected size
|
234
|
+
if len(result) > expected_output_size:
|
235
|
+
# Take first n elements or average
|
236
|
+
result = result[:expected_output_size]
|
237
|
+
else:
|
238
|
+
# Pad with zeros or repeat
|
239
|
+
result = np.pad(result, (0, expected_output_size - len(result)), mode='constant')
|
240
|
+
|
241
|
+
outputs.append(result)
|
242
|
+
|
243
|
+
return np.array(outputs)
|
244
|
+
|
245
|
+
def _add_variational_layer(self, circuit: Any, params: np.ndarray) -> Any:
|
246
|
+
"""Add a variational layer to circuit."""
|
247
|
+
n_qubits = self._get_n_qubits(circuit)
|
248
|
+
|
249
|
+
# RY rotations
|
250
|
+
for i in range(n_qubits):
|
251
|
+
if i < len(params) // 2:
|
252
|
+
self.add_gate(circuit, 'RY', i, [params[i]])
|
253
|
+
|
254
|
+
# Entangling gates
|
255
|
+
for i in range(n_qubits - 1):
|
256
|
+
self.add_gate(circuit, 'CNOT', [i, i + 1])
|
257
|
+
|
258
|
+
# RZ rotations
|
259
|
+
for i in range(n_qubits):
|
260
|
+
if i + n_qubits < len(params):
|
261
|
+
self.add_gate(circuit, 'RZ', i, [params[i + n_qubits]])
|
262
|
+
|
263
|
+
return circuit
|
264
|
+
|
265
|
+
# ========================================================================
|
266
|
+
# Utility Methods
|
267
|
+
# ========================================================================
|
268
|
+
|
269
|
+
@abstractmethod
|
270
|
+
def _get_n_qubits(self, circuit: Any) -> int:
|
271
|
+
"""Get number of qubits in circuit."""
|
272
|
+
pass
|
273
|
+
|
274
|
+
def _result_to_bitstrings(self, result: Dict[str, Any]) -> np.ndarray:
|
275
|
+
"""Convert execution result to bit string array."""
|
276
|
+
# This is backend-specific and should be overridden
|
277
|
+
if 'counts' in result:
|
278
|
+
counts = result['counts']
|
279
|
+
bitstrings = []
|
280
|
+
for bitstring, count in counts.items():
|
281
|
+
for _ in range(count):
|
282
|
+
bitstrings.append([int(b) for b in bitstring])
|
283
|
+
return np.array(bitstrings)
|
284
|
+
else:
|
285
|
+
return np.array([[0, 1]]) # Placeholder
|
286
|
+
|
287
|
+
def _result_to_probabilities(self, result: Dict[str, Any]) -> np.ndarray:
|
288
|
+
"""Convert execution result to probability array."""
|
289
|
+
if 'counts' in result:
|
290
|
+
counts = result['counts']
|
291
|
+
total_shots = sum(counts.values())
|
292
|
+
probs = []
|
293
|
+
for bitstring in sorted(counts.keys()):
|
294
|
+
probs.append(counts[bitstring] / total_shots)
|
295
|
+
return np.array(probs)
|
296
|
+
else:
|
297
|
+
return np.array([0.5, 0.5]) # Placeholder
|
298
|
+
|
299
|
+
def _compute_pauli_expectation(self, circuit: Any, pauli_strings: List[str]) -> np.ndarray:
|
300
|
+
"""Compute expectation values of Pauli strings."""
|
301
|
+
# Placeholder implementation
|
302
|
+
return np.random.random(len(pauli_strings)) * 2 - 1
|
303
|
+
|
304
|
+
def get_version_info(self) -> Dict[str, Any]:
|
305
|
+
"""Get backend version information."""
|
306
|
+
return {
|
307
|
+
'backend_name': self.__class__.__name__,
|
308
|
+
'device': self.device,
|
309
|
+
'capabilities': self.capabilities,
|
310
|
+
}
|
311
|
+
|
312
|
+
def get_device_info(self) -> Dict[str, Any]:
|
313
|
+
"""Get information about the quantum device."""
|
314
|
+
return {
|
315
|
+
'device': self.device,
|
316
|
+
'n_qubits': getattr(self, 'n_qubits', 'Unknown'),
|
317
|
+
'topology': getattr(self, 'topology', 'Unknown'),
|
318
|
+
}
|
319
|
+
|
320
|
+
def __repr__(self) -> str:
|
321
|
+
return f"{self.__class__.__name__}(device='{self.device}', shots={self.shots})"
|