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,702 @@
|
|
1
|
+
"""Quantum measurement and result handling for SuperQuantX
|
2
|
+
"""
|
3
|
+
|
4
|
+
import json
|
5
|
+
from collections import Counter, defaultdict
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple
|
7
|
+
|
8
|
+
import matplotlib.pyplot as plt
|
9
|
+
import numpy as np
|
10
|
+
from pydantic import BaseModel, Field
|
11
|
+
|
12
|
+
|
13
|
+
class MeasurementResult(BaseModel):
|
14
|
+
"""Represents the result of quantum measurements
|
15
|
+
"""
|
16
|
+
|
17
|
+
counts: Dict[str, int] = Field(..., description="Measurement outcome counts")
|
18
|
+
shots: int = Field(..., description="Total number of shots")
|
19
|
+
memory: Optional[List[str]] = Field(default=None, description="Individual shot outcomes")
|
20
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
21
|
+
|
22
|
+
def model_post_init(self, __context):
|
23
|
+
"""Validate measurement result"""
|
24
|
+
if sum(self.counts.values()) != self.shots:
|
25
|
+
raise ValueError("Sum of counts must equal total shots")
|
26
|
+
|
27
|
+
@property
|
28
|
+
def probabilities(self) -> Dict[str, float]:
|
29
|
+
"""Get measurement probabilities"""
|
30
|
+
return {outcome: count / self.shots for outcome, count in self.counts.items()}
|
31
|
+
|
32
|
+
@property
|
33
|
+
def most_frequent(self) -> Tuple[str, int]:
|
34
|
+
"""Get most frequent measurement outcome"""
|
35
|
+
return max(self.counts.items(), key=lambda x: x[1])
|
36
|
+
|
37
|
+
def marginal_counts(self, qubits: List[int]) -> Dict[str, int]:
|
38
|
+
"""Get marginal counts for specific qubits
|
39
|
+
|
40
|
+
Args:
|
41
|
+
qubits: List of qubit indices to marginalize over
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
Marginal counts dictionary
|
45
|
+
|
46
|
+
"""
|
47
|
+
marginal = defaultdict(int)
|
48
|
+
|
49
|
+
for outcome, count in self.counts.items():
|
50
|
+
# Extract bits for specified qubits (reverse order due to endianness)
|
51
|
+
marginal_outcome = ''.join(outcome[-(i+1)] for i in qubits)
|
52
|
+
marginal[marginal_outcome] += count
|
53
|
+
|
54
|
+
return dict(marginal)
|
55
|
+
|
56
|
+
def expectation_value(self, observable: str) -> float:
|
57
|
+
"""Calculate expectation value for Pauli observable
|
58
|
+
|
59
|
+
Args:
|
60
|
+
observable: Pauli string (e.g., "ZZI")
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
Expectation value
|
64
|
+
|
65
|
+
"""
|
66
|
+
if len(observable) == 0:
|
67
|
+
return 1.0
|
68
|
+
|
69
|
+
expectation = 0.0
|
70
|
+
|
71
|
+
for outcome, count in self.counts.items():
|
72
|
+
# Calculate parity for non-identity Pauli operators
|
73
|
+
parity = 1
|
74
|
+
for i, pauli_op in enumerate(observable):
|
75
|
+
if pauli_op == 'Z' and i < len(outcome):
|
76
|
+
bit = int(outcome[-(i+1)]) # Reverse order
|
77
|
+
parity *= (-1) ** bit
|
78
|
+
elif pauli_op in ['X', 'Y']:
|
79
|
+
# X and Y measurements require basis rotation
|
80
|
+
raise ValueError(f"Cannot compute expectation for {pauli_op} from Z-basis measurements")
|
81
|
+
|
82
|
+
expectation += parity * count / self.shots
|
83
|
+
|
84
|
+
return expectation
|
85
|
+
|
86
|
+
def entropy(self) -> float:
|
87
|
+
"""Calculate measurement entropy"""
|
88
|
+
entropy = 0.0
|
89
|
+
for prob in self.probabilities.values():
|
90
|
+
if prob > 0:
|
91
|
+
entropy -= prob * np.log2(prob)
|
92
|
+
return entropy
|
93
|
+
|
94
|
+
def plot_histogram(
|
95
|
+
self,
|
96
|
+
title: str = "Measurement Results",
|
97
|
+
figsize: Tuple[int, int] = (10, 6),
|
98
|
+
max_outcomes: int = 20
|
99
|
+
) -> plt.Figure:
|
100
|
+
"""Plot measurement histogram
|
101
|
+
|
102
|
+
Args:
|
103
|
+
title: Plot title
|
104
|
+
figsize: Figure size
|
105
|
+
max_outcomes: Maximum number of outcomes to show
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
Matplotlib figure
|
109
|
+
|
110
|
+
"""
|
111
|
+
fig, ax = plt.subplots(figsize=figsize)
|
112
|
+
|
113
|
+
# Sort outcomes by count (descending)
|
114
|
+
sorted_outcomes = sorted(self.counts.items(), key=lambda x: x[1], reverse=True)
|
115
|
+
|
116
|
+
# Take top outcomes
|
117
|
+
if len(sorted_outcomes) > max_outcomes:
|
118
|
+
sorted_outcomes = sorted_outcomes[:max_outcomes]
|
119
|
+
|
120
|
+
outcomes, counts = zip(*sorted_outcomes) if sorted_outcomes else ([], [])
|
121
|
+
|
122
|
+
ax.bar(range(len(outcomes)), counts)
|
123
|
+
ax.set_xlabel('Measurement Outcome')
|
124
|
+
ax.set_ylabel('Count')
|
125
|
+
ax.set_title(title)
|
126
|
+
ax.set_xticks(range(len(outcomes)))
|
127
|
+
ax.set_xticklabels(outcomes, rotation=45, ha='right')
|
128
|
+
|
129
|
+
plt.tight_layout()
|
130
|
+
return fig
|
131
|
+
|
132
|
+
def to_dict(self) -> Dict[str, Any]:
|
133
|
+
"""Convert to dictionary"""
|
134
|
+
return {
|
135
|
+
"counts": self.counts,
|
136
|
+
"shots": self.shots,
|
137
|
+
"memory": self.memory,
|
138
|
+
"metadata": self.metadata
|
139
|
+
}
|
140
|
+
|
141
|
+
def to_json(self, indent: int = 2) -> str:
|
142
|
+
"""Convert to JSON string"""
|
143
|
+
return json.dumps(self.to_dict(), indent=indent)
|
144
|
+
|
145
|
+
@classmethod
|
146
|
+
def from_dict(cls, data: Dict[str, Any]) -> "MeasurementResult":
|
147
|
+
"""Create from dictionary"""
|
148
|
+
return cls(**data)
|
149
|
+
|
150
|
+
@classmethod
|
151
|
+
def from_json(cls, json_str: str) -> "MeasurementResult":
|
152
|
+
"""Create from JSON string"""
|
153
|
+
data = json.loads(json_str)
|
154
|
+
return cls.from_dict(data)
|
155
|
+
|
156
|
+
def __add__(self, other: "MeasurementResult") -> "MeasurementResult":
|
157
|
+
"""Add two measurement results"""
|
158
|
+
combined_counts = dict(self.counts)
|
159
|
+
|
160
|
+
for outcome, count in other.counts.items():
|
161
|
+
combined_counts[outcome] = combined_counts.get(outcome, 0) + count
|
162
|
+
|
163
|
+
combined_memory = None
|
164
|
+
if self.memory is not None and other.memory is not None:
|
165
|
+
combined_memory = self.memory + other.memory
|
166
|
+
|
167
|
+
return MeasurementResult(
|
168
|
+
counts=combined_counts,
|
169
|
+
shots=self.shots + other.shots,
|
170
|
+
memory=combined_memory,
|
171
|
+
metadata={**self.metadata, **other.metadata}
|
172
|
+
)
|
173
|
+
|
174
|
+
|
175
|
+
class QuantumMeasurement:
|
176
|
+
"""Quantum measurement operations and analysis
|
177
|
+
"""
|
178
|
+
|
179
|
+
def __init__(self, backend: Optional[str] = "simulator"):
|
180
|
+
"""Initialize measurement system
|
181
|
+
|
182
|
+
Args:
|
183
|
+
backend: Quantum backend for measurements
|
184
|
+
|
185
|
+
"""
|
186
|
+
self.backend = backend
|
187
|
+
self.measurement_history: List[MeasurementResult] = []
|
188
|
+
|
189
|
+
def measure_circuit(
|
190
|
+
self,
|
191
|
+
circuit: "QuantumCircuit",
|
192
|
+
shots: int = 1024,
|
193
|
+
memory: bool = False
|
194
|
+
) -> MeasurementResult:
|
195
|
+
"""Measure quantum circuit
|
196
|
+
|
197
|
+
Args:
|
198
|
+
circuit: Quantum circuit to measure
|
199
|
+
shots: Number of measurement shots
|
200
|
+
memory: Whether to store individual shot outcomes
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
Measurement result
|
204
|
+
|
205
|
+
"""
|
206
|
+
# This would interface with actual quantum hardware/simulator
|
207
|
+
# For now, create simulated results
|
208
|
+
|
209
|
+
num_classical_bits = circuit.num_classical_bits
|
210
|
+
|
211
|
+
# Simulate measurement outcomes
|
212
|
+
if self.backend == "simulator":
|
213
|
+
counts = self._simulate_measurements(circuit, shots)
|
214
|
+
else:
|
215
|
+
counts = self._execute_measurements(circuit, shots)
|
216
|
+
|
217
|
+
# Generate memory if requested
|
218
|
+
shot_memory = None
|
219
|
+
if memory:
|
220
|
+
shot_memory = []
|
221
|
+
for outcome, count in counts.items():
|
222
|
+
shot_memory.extend([outcome] * count)
|
223
|
+
np.random.shuffle(shot_memory) # Randomize order
|
224
|
+
|
225
|
+
result = MeasurementResult(
|
226
|
+
counts=counts,
|
227
|
+
shots=shots,
|
228
|
+
memory=shot_memory,
|
229
|
+
metadata={"backend": self.backend, "circuit_name": circuit.name}
|
230
|
+
)
|
231
|
+
|
232
|
+
self.measurement_history.append(result)
|
233
|
+
return result
|
234
|
+
|
235
|
+
def _simulate_measurements(self, circuit: "QuantumCircuit", shots: int) -> Dict[str, int]:
|
236
|
+
"""Simulate measurement outcomes"""
|
237
|
+
num_bits = circuit.num_classical_bits
|
238
|
+
|
239
|
+
# Simple simulation: uniform random outcomes for now
|
240
|
+
# In practice, would run full quantum simulation
|
241
|
+
outcomes = []
|
242
|
+
for _ in range(shots):
|
243
|
+
outcome = ''.join(str(np.random.randint(2)) for _ in range(num_bits))
|
244
|
+
outcomes.append(outcome)
|
245
|
+
|
246
|
+
return dict(Counter(outcomes))
|
247
|
+
|
248
|
+
def _execute_measurements(self, circuit: "QuantumCircuit", shots: int) -> Dict[str, int]:
|
249
|
+
"""Execute measurements on quantum hardware"""
|
250
|
+
# This would interface with quantum hardware via client
|
251
|
+
# Placeholder for now
|
252
|
+
return self._simulate_measurements(circuit, shots)
|
253
|
+
|
254
|
+
def measure_observable(
|
255
|
+
self,
|
256
|
+
circuit: "QuantumCircuit",
|
257
|
+
observable: str,
|
258
|
+
shots: int = 1024
|
259
|
+
) -> float:
|
260
|
+
"""Measure expectation value of Pauli observable
|
261
|
+
|
262
|
+
Args:
|
263
|
+
circuit: Quantum circuit (without measurements)
|
264
|
+
observable: Pauli string observable
|
265
|
+
shots: Number of shots
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
Expectation value
|
269
|
+
|
270
|
+
"""
|
271
|
+
# Create measurement circuit with basis rotations
|
272
|
+
measurement_circuit = self._prepare_observable_measurement(circuit, observable)
|
273
|
+
|
274
|
+
# Measure circuit
|
275
|
+
result = self.measure_circuit(measurement_circuit, shots)
|
276
|
+
|
277
|
+
# Calculate expectation value
|
278
|
+
return result.expectation_value('Z' * len(observable))
|
279
|
+
|
280
|
+
def _prepare_observable_measurement(
|
281
|
+
self,
|
282
|
+
circuit: "QuantumCircuit",
|
283
|
+
observable: str
|
284
|
+
) -> "QuantumCircuit":
|
285
|
+
"""Prepare circuit for observable measurement"""
|
286
|
+
# Copy original circuit
|
287
|
+
measurement_circuit = circuit.copy()
|
288
|
+
|
289
|
+
# Add basis rotation gates
|
290
|
+
for i, pauli_op in enumerate(observable):
|
291
|
+
if i >= circuit.num_qubits:
|
292
|
+
break
|
293
|
+
|
294
|
+
if pauli_op == 'X':
|
295
|
+
measurement_circuit.ry(-np.pi/2, i) # Rotate Y→Z basis
|
296
|
+
elif pauli_op == 'Y':
|
297
|
+
measurement_circuit.rx(np.pi/2, i) # Rotate X→Z basis
|
298
|
+
# Z measurements don't need rotation
|
299
|
+
|
300
|
+
# Add measurements
|
301
|
+
for i in range(min(len(observable), circuit.num_qubits)):
|
302
|
+
measurement_circuit.measure(i, i)
|
303
|
+
|
304
|
+
return measurement_circuit
|
305
|
+
|
306
|
+
def tomography_measurements(
|
307
|
+
self,
|
308
|
+
circuit: "QuantumCircuit",
|
309
|
+
qubits: Optional[List[int]] = None,
|
310
|
+
shots_per_measurement: int = 1024
|
311
|
+
) -> Dict[str, MeasurementResult]:
|
312
|
+
"""Perform quantum state tomography measurements
|
313
|
+
|
314
|
+
Args:
|
315
|
+
circuit: Quantum circuit to tomographically reconstruct
|
316
|
+
qubits: Qubits to perform tomography on (default: all)
|
317
|
+
shots_per_measurement: Shots per Pauli measurement
|
318
|
+
|
319
|
+
Returns:
|
320
|
+
Dictionary of measurement results for each Pauli basis
|
321
|
+
|
322
|
+
"""
|
323
|
+
if qubits is None:
|
324
|
+
qubits = list(range(circuit.num_qubits))
|
325
|
+
|
326
|
+
num_qubits = len(qubits)
|
327
|
+
pauli_bases = ['I', 'X', 'Y', 'Z']
|
328
|
+
|
329
|
+
# Generate all Pauli measurement settings
|
330
|
+
measurements = {}
|
331
|
+
|
332
|
+
def generate_pauli_strings(n):
|
333
|
+
if n == 0:
|
334
|
+
yield ''
|
335
|
+
else:
|
336
|
+
for base in pauli_bases:
|
337
|
+
for rest in generate_pauli_strings(n - 1):
|
338
|
+
yield base + rest
|
339
|
+
|
340
|
+
for pauli_string in generate_pauli_strings(num_qubits):
|
341
|
+
if 'I' in pauli_string:
|
342
|
+
continue # Skip identity measurements
|
343
|
+
|
344
|
+
measurement_circuit = self._prepare_tomography_measurement(
|
345
|
+
circuit, pauli_string, qubits
|
346
|
+
)
|
347
|
+
|
348
|
+
result = self.measure_circuit(measurement_circuit, shots_per_measurement)
|
349
|
+
measurements[pauli_string] = result
|
350
|
+
|
351
|
+
return measurements
|
352
|
+
|
353
|
+
def _prepare_tomography_measurement(
|
354
|
+
self,
|
355
|
+
circuit: "QuantumCircuit",
|
356
|
+
pauli_string: str,
|
357
|
+
qubits: List[int]
|
358
|
+
) -> "QuantumCircuit":
|
359
|
+
"""Prepare circuit for tomography measurement"""
|
360
|
+
measurement_circuit = circuit.copy()
|
361
|
+
|
362
|
+
for i, pauli_op in enumerate(pauli_string):
|
363
|
+
if i >= len(qubits):
|
364
|
+
break
|
365
|
+
|
366
|
+
qubit = qubits[i]
|
367
|
+
|
368
|
+
if pauli_op == 'X':
|
369
|
+
measurement_circuit.ry(-np.pi/2, qubit)
|
370
|
+
elif pauli_op == 'Y':
|
371
|
+
measurement_circuit.rx(np.pi/2, qubit)
|
372
|
+
|
373
|
+
# Measure selected qubits
|
374
|
+
for i, qubit in enumerate(qubits):
|
375
|
+
if i < len(pauli_string):
|
376
|
+
measurement_circuit.measure(qubit, i)
|
377
|
+
|
378
|
+
return measurement_circuit
|
379
|
+
|
380
|
+
def reconstruct_state(
|
381
|
+
self,
|
382
|
+
tomography_results: Dict[str, MeasurementResult]
|
383
|
+
) -> np.ndarray:
|
384
|
+
"""Reconstruct quantum state from tomography measurements
|
385
|
+
|
386
|
+
Args:
|
387
|
+
tomography_results: Results from tomography_measurements
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
Reconstructed density matrix
|
391
|
+
|
392
|
+
"""
|
393
|
+
# This is a simplified implementation
|
394
|
+
# Full tomography would use maximum likelihood estimation
|
395
|
+
|
396
|
+
num_qubits = len(next(iter(tomography_results.keys())))
|
397
|
+
dim = 2 ** num_qubits
|
398
|
+
|
399
|
+
# Initialize density matrix
|
400
|
+
rho = np.eye(dim, dtype=complex) / dim
|
401
|
+
|
402
|
+
# This is a placeholder implementation
|
403
|
+
# Real tomography would involve solving a constrained optimization problem
|
404
|
+
|
405
|
+
return rho
|
406
|
+
|
407
|
+
def fidelity(
|
408
|
+
self,
|
409
|
+
state1: np.ndarray,
|
410
|
+
state2: np.ndarray
|
411
|
+
) -> float:
|
412
|
+
"""Calculate quantum state fidelity
|
413
|
+
|
414
|
+
Args:
|
415
|
+
state1: First quantum state (vector or density matrix)
|
416
|
+
state2: Second quantum state (vector or density matrix)
|
417
|
+
|
418
|
+
Returns:
|
419
|
+
Fidelity between states
|
420
|
+
|
421
|
+
"""
|
422
|
+
# Convert to density matrices if needed
|
423
|
+
if state1.ndim == 1:
|
424
|
+
rho1 = np.outer(state1, np.conj(state1))
|
425
|
+
else:
|
426
|
+
rho1 = state1
|
427
|
+
|
428
|
+
if state2.ndim == 1:
|
429
|
+
rho2 = np.outer(state2, np.conj(state2))
|
430
|
+
else:
|
431
|
+
rho2 = state2
|
432
|
+
|
433
|
+
# Calculate fidelity using proper matrix square root
|
434
|
+
eigenvals_rho1, eigenvecs_rho1 = np.linalg.eigh(rho1)
|
435
|
+
eigenvals_rho1 = np.maximum(eigenvals_rho1, 0) # Ensure non-negative
|
436
|
+
sqrt_rho1 = eigenvecs_rho1 @ np.diag(np.sqrt(eigenvals_rho1)) @ eigenvecs_rho1.conj().T
|
437
|
+
|
438
|
+
product = sqrt_rho1 @ rho2 @ sqrt_rho1
|
439
|
+
eigenvals = np.linalg.eigvals(product)
|
440
|
+
eigenvals = np.maximum(eigenvals.real, 0) # Take real part and ensure non-negative
|
441
|
+
|
442
|
+
return float(np.sum(np.sqrt(eigenvals)))
|
443
|
+
|
444
|
+
def trace_distance(
|
445
|
+
self,
|
446
|
+
state1: np.ndarray,
|
447
|
+
state2: np.ndarray
|
448
|
+
) -> float:
|
449
|
+
"""Calculate trace distance between quantum states
|
450
|
+
|
451
|
+
Args:
|
452
|
+
state1: First quantum state
|
453
|
+
state2: Second quantum state
|
454
|
+
|
455
|
+
Returns:
|
456
|
+
Trace distance
|
457
|
+
|
458
|
+
"""
|
459
|
+
# Convert to density matrices if needed
|
460
|
+
if state1.ndim == 1:
|
461
|
+
rho1 = np.outer(state1, np.conj(state1))
|
462
|
+
else:
|
463
|
+
rho1 = state1
|
464
|
+
|
465
|
+
if state2.ndim == 1:
|
466
|
+
rho2 = np.outer(state2, np.conj(state2))
|
467
|
+
else:
|
468
|
+
rho2 = state2
|
469
|
+
|
470
|
+
diff = rho1 - rho2
|
471
|
+
eigenvals = np.linalg.eigvals(diff)
|
472
|
+
|
473
|
+
return 0.5 * np.sum(np.abs(eigenvals))
|
474
|
+
|
475
|
+
def quantum_volume(
|
476
|
+
self,
|
477
|
+
num_qubits: int,
|
478
|
+
depth: int,
|
479
|
+
trials: int = 100,
|
480
|
+
shots_per_trial: int = 1024
|
481
|
+
) -> Dict[str, Any]:
|
482
|
+
"""Perform quantum volume benchmark
|
483
|
+
|
484
|
+
Args:
|
485
|
+
num_qubits: Number of qubits
|
486
|
+
depth: Circuit depth
|
487
|
+
trials: Number of random circuits to test
|
488
|
+
shots_per_trial: Shots per circuit
|
489
|
+
|
490
|
+
Returns:
|
491
|
+
Quantum volume benchmark results
|
492
|
+
|
493
|
+
"""
|
494
|
+
import random
|
495
|
+
|
496
|
+
from .circuits import QuantumCircuit
|
497
|
+
|
498
|
+
successes = 0
|
499
|
+
|
500
|
+
for trial in range(trials):
|
501
|
+
# Generate random quantum volume circuit
|
502
|
+
circuit = QuantumCircuit(num_qubits)
|
503
|
+
|
504
|
+
for layer in range(depth):
|
505
|
+
# Random SU(4) gates on random qubit pairs
|
506
|
+
available_qubits = list(range(num_qubits))
|
507
|
+
random.shuffle(available_qubits)
|
508
|
+
|
509
|
+
for i in range(0, num_qubits - 1, 2):
|
510
|
+
q1, q2 = available_qubits[i], available_qubits[i + 1]
|
511
|
+
|
512
|
+
# Random single-qubit gates
|
513
|
+
circuit.u(
|
514
|
+
random.uniform(0, 2*np.pi),
|
515
|
+
random.uniform(0, 2*np.pi),
|
516
|
+
random.uniform(0, 2*np.pi),
|
517
|
+
q1
|
518
|
+
)
|
519
|
+
circuit.u(
|
520
|
+
random.uniform(0, 2*np.pi),
|
521
|
+
random.uniform(0, 2*np.pi),
|
522
|
+
random.uniform(0, 2*np.pi),
|
523
|
+
q2
|
524
|
+
)
|
525
|
+
|
526
|
+
# CNOT gate
|
527
|
+
circuit.cnot(q1, q2)
|
528
|
+
|
529
|
+
# Measure circuit
|
530
|
+
circuit.measure_all()
|
531
|
+
result = self.measure_circuit(circuit, shots_per_trial)
|
532
|
+
|
533
|
+
# Check if heavy output (simplified check)
|
534
|
+
# Real quantum volume would compute ideal probabilities
|
535
|
+
most_frequent = result.most_frequent
|
536
|
+
if most_frequent[1] > shots_per_trial // 4: # Simplified threshold
|
537
|
+
successes += 1
|
538
|
+
|
539
|
+
success_rate = successes / trials
|
540
|
+
|
541
|
+
return {
|
542
|
+
"num_qubits": num_qubits,
|
543
|
+
"depth": depth,
|
544
|
+
"trials": trials,
|
545
|
+
"successes": successes,
|
546
|
+
"success_rate": success_rate,
|
547
|
+
"passed": success_rate > 2/3, # Standard QV threshold
|
548
|
+
"quantum_volume": 2 ** num_qubits if success_rate > 2/3 else 0
|
549
|
+
}
|
550
|
+
|
551
|
+
|
552
|
+
class ResultAnalyzer:
|
553
|
+
"""Advanced analysis of quantum measurement results
|
554
|
+
"""
|
555
|
+
|
556
|
+
@staticmethod
|
557
|
+
def compare_results(
|
558
|
+
results1: MeasurementResult,
|
559
|
+
results2: MeasurementResult
|
560
|
+
) -> Dict[str, float]:
|
561
|
+
"""Compare two measurement results
|
562
|
+
|
563
|
+
Args:
|
564
|
+
results1: First measurement result
|
565
|
+
results2: Second measurement result
|
566
|
+
|
567
|
+
Returns:
|
568
|
+
Comparison metrics
|
569
|
+
|
570
|
+
"""
|
571
|
+
# Hellinger distance between probability distributions
|
572
|
+
prob1 = results1.probabilities
|
573
|
+
prob2 = results2.probabilities
|
574
|
+
|
575
|
+
all_outcomes = set(prob1.keys()) | set(prob2.keys())
|
576
|
+
|
577
|
+
hellinger = 0.0
|
578
|
+
kl_divergence = 0.0
|
579
|
+
|
580
|
+
for outcome in all_outcomes:
|
581
|
+
p1 = prob1.get(outcome, 0)
|
582
|
+
p2 = prob2.get(outcome, 0)
|
583
|
+
|
584
|
+
# Hellinger distance
|
585
|
+
hellinger += (np.sqrt(p1) - np.sqrt(p2)) ** 2
|
586
|
+
|
587
|
+
# KL divergence (with smoothing to avoid log(0))
|
588
|
+
p1_smooth = p1 + 1e-10
|
589
|
+
p2_smooth = p2 + 1e-10
|
590
|
+
kl_divergence += p1_smooth * np.log(p1_smooth / p2_smooth)
|
591
|
+
|
592
|
+
hellinger = np.sqrt(hellinger / 2)
|
593
|
+
|
594
|
+
# Total variation distance
|
595
|
+
tv_distance = 0.5 * sum(abs(prob1.get(outcome, 0) - prob2.get(outcome, 0))
|
596
|
+
for outcome in all_outcomes)
|
597
|
+
|
598
|
+
return {
|
599
|
+
"hellinger_distance": hellinger,
|
600
|
+
"kl_divergence": kl_divergence,
|
601
|
+
"total_variation_distance": tv_distance
|
602
|
+
}
|
603
|
+
|
604
|
+
@staticmethod
|
605
|
+
def error_mitigation_zero_noise_extrapolation(
|
606
|
+
noise_levels: List[float],
|
607
|
+
measurement_results: List[MeasurementResult],
|
608
|
+
observable: str = "Z"
|
609
|
+
) -> Tuple[float, Dict[str, Any]]:
|
610
|
+
"""Perform zero-noise extrapolation error mitigation
|
611
|
+
|
612
|
+
Args:
|
613
|
+
noise_levels: List of noise levels (e.g., [1, 2, 3])
|
614
|
+
measurement_results: Results for each noise level
|
615
|
+
observable: Observable to extrapolate
|
616
|
+
|
617
|
+
Returns:
|
618
|
+
Zero-noise extrapolated value and fitting info
|
619
|
+
|
620
|
+
"""
|
621
|
+
if len(noise_levels) != len(measurement_results):
|
622
|
+
raise ValueError("Noise levels and results must have same length")
|
623
|
+
|
624
|
+
# Extract expectation values
|
625
|
+
expectation_values = []
|
626
|
+
for result in measurement_results:
|
627
|
+
exp_val = result.expectation_value(observable)
|
628
|
+
expectation_values.append(exp_val)
|
629
|
+
|
630
|
+
# Fit exponential decay model: f(x) = a * exp(-b * x) + c
|
631
|
+
from scipy.optimize import curve_fit
|
632
|
+
|
633
|
+
def exponential_model(x, a, b, c):
|
634
|
+
return a * np.exp(-b * x) + c
|
635
|
+
|
636
|
+
try:
|
637
|
+
popt, pcov = curve_fit(
|
638
|
+
exponential_model,
|
639
|
+
noise_levels,
|
640
|
+
expectation_values,
|
641
|
+
p0=[expectation_values[0], 0.1, 0]
|
642
|
+
)
|
643
|
+
|
644
|
+
# Extrapolate to zero noise
|
645
|
+
zero_noise_value = exponential_model(0, *popt)
|
646
|
+
|
647
|
+
# Calculate fit quality
|
648
|
+
fitted_values = [exponential_model(x, *popt) for x in noise_levels]
|
649
|
+
r_squared = 1 - np.sum((np.array(expectation_values) - fitted_values)**2) / \
|
650
|
+
np.sum((np.array(expectation_values) - np.mean(expectation_values))**2)
|
651
|
+
|
652
|
+
return zero_noise_value, {
|
653
|
+
"fit_parameters": popt,
|
654
|
+
"fit_covariance": pcov,
|
655
|
+
"r_squared": r_squared,
|
656
|
+
"fitted_values": fitted_values,
|
657
|
+
"raw_values": expectation_values
|
658
|
+
}
|
659
|
+
|
660
|
+
except Exception as e:
|
661
|
+
# Fallback to linear extrapolation
|
662
|
+
coeffs = np.polyfit(noise_levels, expectation_values, 1)
|
663
|
+
zero_noise_value = coeffs[1] # y-intercept
|
664
|
+
|
665
|
+
return zero_noise_value, {
|
666
|
+
"method": "linear_fallback",
|
667
|
+
"coefficients": coeffs,
|
668
|
+
"error": str(e)
|
669
|
+
}
|
670
|
+
|
671
|
+
@staticmethod
|
672
|
+
def readout_error_mitigation(
|
673
|
+
calibration_results: Dict[str, MeasurementResult],
|
674
|
+
measurement_result: MeasurementResult
|
675
|
+
) -> MeasurementResult:
|
676
|
+
"""Apply readout error mitigation
|
677
|
+
|
678
|
+
Args:
|
679
|
+
calibration_results: Results from measuring |0⟩ and |1⟩ states
|
680
|
+
measurement_result: Result to correct
|
681
|
+
|
682
|
+
Returns:
|
683
|
+
Error-mitigated result
|
684
|
+
|
685
|
+
"""
|
686
|
+
# Build calibration matrix
|
687
|
+
num_qubits = len(next(iter(calibration_results.keys())))
|
688
|
+
|
689
|
+
# This is a simplified implementation
|
690
|
+
# Full readout error mitigation would build complete confusion matrix
|
691
|
+
|
692
|
+
corrected_counts = dict(measurement_result.counts)
|
693
|
+
|
694
|
+
# Apply simple correction (placeholder)
|
695
|
+
# Real implementation would invert the calibration matrix
|
696
|
+
|
697
|
+
return MeasurementResult(
|
698
|
+
counts=corrected_counts,
|
699
|
+
shots=measurement_result.shots,
|
700
|
+
memory=measurement_result.memory,
|
701
|
+
metadata={**measurement_result.metadata, "error_mitigated": True}
|
702
|
+
)
|