quantumflow-sdk 0.3.0__py3-none-any.whl → 0.4.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.
@@ -8,11 +8,16 @@ Key Results:
8
8
  - Compression ratio: 2.1×
9
9
  - Token reduction: 53.3%
10
10
  - Memory: O(log n) vs O(n) classical
11
+
12
+ Enhanced with Gray-code measurement protocol from arXiv:2601.00247:
13
+ - Measurement settings reduced from O(N) to 2n+1
14
+ - Efficient amplitude and phase extraction
15
+ - Logarithmic qubit encoding preserved
11
16
  """
12
17
 
13
18
  import math
14
19
  from dataclasses import dataclass, field
15
- from typing import Optional
20
+ from typing import Optional, Literal
16
21
  import numpy as np
17
22
  from qiskit import QuantumCircuit
18
23
 
@@ -81,6 +86,313 @@ class CompressedResult:
81
86
  return reconstructed
82
87
 
83
88
 
89
+ def generate_gray_code(n_bits: int) -> list[int]:
90
+ """
91
+ Generate Gray code sequence for n bits.
92
+
93
+ Gray code ensures adjacent codewords differ by exactly one bit,
94
+ enabling efficient phase extraction with minimal measurements.
95
+
96
+ Based on arXiv:2601.00247 Section IV.B
97
+
98
+ Args:
99
+ n_bits: Number of bits in the Gray code
100
+
101
+ Returns:
102
+ List of integers in Gray code order
103
+ """
104
+ if n_bits == 0:
105
+ return [0]
106
+
107
+ gray_sequence = []
108
+ for i in range(2 ** n_bits):
109
+ # Gray code: i XOR (i >> 1)
110
+ gray_sequence.append(i ^ (i >> 1))
111
+ return gray_sequence
112
+
113
+
114
+ def gray_code_to_binary(gray: int) -> int:
115
+ """Convert Gray code to binary."""
116
+ binary = gray
117
+ mask = gray >> 1
118
+ while mask:
119
+ binary ^= mask
120
+ mask >>= 1
121
+ return binary
122
+
123
+
124
+ def binary_to_gray(binary: int) -> int:
125
+ """Convert binary to Gray code."""
126
+ return binary ^ (binary >> 1)
127
+
128
+
129
+ @dataclass
130
+ class GrayCodeMeasurementResult:
131
+ """
132
+ Result from Gray-code optimized measurement.
133
+
134
+ Based on arXiv:2601.00247 - reduces measurement settings
135
+ from O(N) to 2n+1 where n = log₂(N).
136
+ """
137
+
138
+ amplitudes: np.ndarray
139
+ phases: np.ndarray
140
+ n_qubits: int
141
+ n_measurement_settings: int
142
+ measurement_type: str # 'z', 'xx', 'xy'
143
+
144
+ @property
145
+ def complex_amplitudes(self) -> np.ndarray:
146
+ """Get full complex amplitudes α_j = |α_j|e^(iθ_j)."""
147
+ return self.amplitudes * np.exp(1j * self.phases)
148
+
149
+ @property
150
+ def probabilities(self) -> np.ndarray:
151
+ """Get measurement probabilities |α_j|²."""
152
+ return np.abs(self.amplitudes) ** 2
153
+
154
+
155
+ class GrayCodeMeasurement:
156
+ """
157
+ Gray-code optimized measurement protocol.
158
+
159
+ Implements efficient amplitude and phase extraction from
160
+ arXiv:2601.00247 Section IV.B.
161
+
162
+ Key insight: By arranging codewords along a cyclic Gray code path,
163
+ each measurement operator couples exactly one pair of basis states,
164
+ enabling unique phase reconstruction.
165
+
166
+ Measurement Protocol:
167
+ 1. M_Z: One Z-basis measurement → amplitudes |α_j|
168
+ 2. M_XX: n X-basis measurements → cos(θ_{j+1} - θ_j)
169
+ 3. M_XY: n XY-basis measurements → sin(θ_{j+1} - θ_j)
170
+
171
+ Total: 2n+1 measurement settings (vs O(N) standard)
172
+
173
+ Example:
174
+ >>> gm = GrayCodeMeasurement(n_qubits=4)
175
+ >>> result = gm.extract_amplitudes_and_phases(execution_result)
176
+ >>> print(f"Used {result.n_measurement_settings} measurement settings")
177
+ """
178
+
179
+ def __init__(self, n_qubits: int):
180
+ """
181
+ Initialize Gray-code measurement protocol.
182
+
183
+ Args:
184
+ n_qubits: Number of qubits in the system
185
+ """
186
+ self.n_qubits = n_qubits
187
+ self.n_states = 2 ** n_qubits
188
+ self.gray_sequence = generate_gray_code(n_qubits)
189
+
190
+ def get_measurement_settings_count(self) -> int:
191
+ """
192
+ Get total number of measurement settings needed.
193
+
194
+ Returns 2n+1 where n is number of qubits.
195
+ """
196
+ return 2 * self.n_qubits + 1
197
+
198
+ def extract_amplitudes(self, z_counts: dict[str, int], total_shots: int) -> np.ndarray:
199
+ """
200
+ Extract amplitudes from Z-basis measurement.
201
+
202
+ |α_j| = sqrt((1 - <Z_j>) / 2)
203
+
204
+ Args:
205
+ z_counts: Measurement counts from Z-basis
206
+ total_shots: Total number of shots
207
+
208
+ Returns:
209
+ Array of amplitude magnitudes
210
+ """
211
+ amplitudes = np.zeros(self.n_states)
212
+
213
+ for bitstring, count in z_counts.items():
214
+ # Convert bitstring to state index (Gray code order)
215
+ state_idx = int(bitstring, 2)
216
+ prob = count / total_shots
217
+ amplitudes[state_idx] = np.sqrt(prob)
218
+
219
+ # Normalize
220
+ norm = np.linalg.norm(amplitudes)
221
+ if norm > 0:
222
+ amplitudes /= norm
223
+
224
+ return amplitudes
225
+
226
+ def extract_phase_differences(
227
+ self,
228
+ xx_correlators: np.ndarray,
229
+ xy_correlators: np.ndarray,
230
+ amplitudes: np.ndarray,
231
+ ) -> np.ndarray:
232
+ """
233
+ Extract relative phases using Gray-code path.
234
+
235
+ θ_{j+1} - θ_j = arctan(<X_j Y_{j+1}> / <X_j X_{j+1}>)
236
+
237
+ Based on Eq. (13) and (22) from arXiv:2601.00247.
238
+
239
+ Args:
240
+ xx_correlators: <X_j X_{j+1}> for neighboring pairs
241
+ xy_correlators: <X_j Y_{j+1}> for neighboring pairs
242
+ amplitudes: Previously extracted amplitudes
243
+
244
+ Returns:
245
+ Array of phases (relative to first state)
246
+ """
247
+ phases = np.zeros(self.n_states)
248
+
249
+ # Walk along Gray code path to accumulate phases
250
+ for i in range(len(self.gray_sequence) - 1):
251
+ j = self.gray_sequence[i]
252
+ k = self.gray_sequence[i + 1]
253
+
254
+ # Get amplitude product
255
+ amp_product = amplitudes[j] * amplitudes[k]
256
+
257
+ if amp_product > 1e-10: # Avoid division by zero
258
+ # Normalize correlators
259
+ cos_diff = xx_correlators[i] / (2 * amp_product)
260
+ sin_diff = xy_correlators[i] / (2 * amp_product)
261
+
262
+ # Clamp to valid range
263
+ cos_diff = np.clip(cos_diff, -1, 1)
264
+ sin_diff = np.clip(sin_diff, -1, 1)
265
+
266
+ # Phase difference
267
+ phase_diff = np.arctan2(sin_diff, cos_diff)
268
+ else:
269
+ phase_diff = 0.0
270
+
271
+ # Accumulate phase along path
272
+ phases[k] = phases[j] + phase_diff
273
+
274
+ return phases
275
+
276
+ def build_xx_measurement_circuits(
277
+ self,
278
+ base_circuit: QuantumCircuit,
279
+ ) -> list[QuantumCircuit]:
280
+ """
281
+ Build circuits for XX correlator measurements.
282
+
283
+ For each neighboring pair in Gray code, measure in X basis
284
+ on qubits that differ, Z basis on qubits that match.
285
+
286
+ Args:
287
+ base_circuit: The state preparation circuit
288
+
289
+ Returns:
290
+ List of measurement circuits
291
+ """
292
+ circuits = []
293
+
294
+ for i in range(self.n_qubits):
295
+ qc = base_circuit.copy()
296
+ qc.name = f"xx_measure_bit{i}"
297
+
298
+ # Apply H to qubit i (measure in X basis)
299
+ qc.h(i)
300
+ qc.measure_all()
301
+ circuits.append(qc)
302
+
303
+ return circuits
304
+
305
+ def build_xy_measurement_circuits(
306
+ self,
307
+ base_circuit: QuantumCircuit,
308
+ ) -> list[QuantumCircuit]:
309
+ """
310
+ Build circuits for XY correlator measurements.
311
+
312
+ Alternating X and Y basis for phase extraction.
313
+
314
+ Args:
315
+ base_circuit: The state preparation circuit
316
+
317
+ Returns:
318
+ List of measurement circuits
319
+ """
320
+ circuits = []
321
+
322
+ for i in range(self.n_qubits):
323
+ qc = base_circuit.copy()
324
+ qc.name = f"xy_measure_bit{i}"
325
+
326
+ # Apply H to qubit i (X basis)
327
+ qc.h(i)
328
+
329
+ # Apply S†H to qubit (i+1) mod n (Y basis)
330
+ next_qubit = (i + 1) % self.n_qubits
331
+ qc.sdg(next_qubit)
332
+ qc.h(next_qubit)
333
+
334
+ qc.measure_all()
335
+ circuits.append(qc)
336
+
337
+ return circuits
338
+
339
+ def full_reconstruction(
340
+ self,
341
+ z_result: dict[str, int],
342
+ xx_results: list[dict[str, int]],
343
+ xy_results: list[dict[str, int]],
344
+ total_shots: int,
345
+ ) -> GrayCodeMeasurementResult:
346
+ """
347
+ Perform full amplitude and phase reconstruction.
348
+
349
+ Args:
350
+ z_result: Z-basis measurement counts
351
+ xx_results: List of XX measurement counts
352
+ xy_results: List of XY measurement counts
353
+ total_shots: Shots per measurement
354
+
355
+ Returns:
356
+ GrayCodeMeasurementResult with amplitudes and phases
357
+ """
358
+ # Step 1: Extract amplitudes from Z measurement
359
+ amplitudes = self.extract_amplitudes(z_result, total_shots)
360
+
361
+ # Step 2: Compute XX correlators
362
+ xx_correlators = np.zeros(self.n_qubits)
363
+ for i, counts in enumerate(xx_results):
364
+ correlator = 0.0
365
+ for bitstring, count in counts.items():
366
+ # Parity of bits i and (i+1)
367
+ bits = [int(b) for b in bitstring]
368
+ parity = (-1) ** (bits[i] + bits[(i + 1) % self.n_qubits])
369
+ correlator += parity * count / total_shots
370
+ xx_correlators[i] = correlator
371
+
372
+ # Step 3: Compute XY correlators
373
+ xy_correlators = np.zeros(self.n_qubits)
374
+ for i, counts in enumerate(xy_results):
375
+ correlator = 0.0
376
+ for bitstring, count in counts.items():
377
+ bits = [int(b) for b in bitstring]
378
+ parity = (-1) ** (bits[i] + bits[(i + 1) % self.n_qubits])
379
+ correlator += parity * count / total_shots
380
+ xy_correlators[i] = correlator
381
+
382
+ # Step 4: Extract phases
383
+ phases = self.extract_phase_differences(
384
+ xx_correlators, xy_correlators, amplitudes
385
+ )
386
+
387
+ return GrayCodeMeasurementResult(
388
+ amplitudes=amplitudes,
389
+ phases=phases,
390
+ n_qubits=self.n_qubits,
391
+ n_measurement_settings=2 * self.n_qubits + 1,
392
+ measurement_type="full_reconstruction",
393
+ )
394
+
395
+
84
396
  class QuantumCompressor:
85
397
  """
86
398
  Quantum token compression using amplitude encoding.
@@ -288,6 +600,72 @@ class QuantumCompressor:
288
600
 
289
601
  return float(fidelity)
290
602
 
603
+ def compress_with_gray_code(
604
+ self,
605
+ tokens: list[int],
606
+ n_qubits: Optional[int] = None,
607
+ compression_level: float = 1.0,
608
+ shots: int = 1024,
609
+ ) -> tuple[CompressedResult, GrayCodeMeasurementResult]:
610
+ """
611
+ Compress tokens and extract using Gray-code measurement protocol.
612
+
613
+ This method uses the efficient measurement protocol from
614
+ arXiv:2601.00247 which requires only 2n+1 measurement settings
615
+ instead of O(N) for full state tomography.
616
+
617
+ Args:
618
+ tokens: List of integer tokens to compress
619
+ n_qubits: Number of qubits (auto-calculated if None)
620
+ compression_level: 0.0-1.0, higher = more compression
621
+ shots: Measurement shots per setting
622
+
623
+ Returns:
624
+ Tuple of (CompressedResult, GrayCodeMeasurementResult)
625
+
626
+ Example:
627
+ >>> compressor = QuantumCompressor(backend="simulator")
628
+ >>> result, gray_result = compressor.compress_with_gray_code([100, 200, 150, 175])
629
+ >>> print(f"Measurement settings used: {gray_result.n_measurement_settings}")
630
+ >>> print(f"Reconstructed amplitudes: {gray_result.amplitudes}")
631
+ """
632
+ # First compress normally
633
+ result = self.compress(tokens, n_qubits, compression_level)
634
+
635
+ # Initialize Gray-code measurement
636
+ gray_measure = GrayCodeMeasurement(result.n_qubits)
637
+
638
+ # Execute Z-basis measurement
639
+ z_circuit = result.compressed_circuit.copy()
640
+ z_circuit.measure_all()
641
+ z_result = self.backend.execute(z_circuit, shots=shots)
642
+
643
+ # Execute XX measurements
644
+ xx_circuits = gray_measure.build_xx_measurement_circuits(result.compressed_circuit)
645
+ xx_results = [
646
+ self.backend.execute(qc, shots=shots).counts
647
+ for qc in xx_circuits
648
+ ]
649
+
650
+ # Execute XY measurements
651
+ xy_circuits = gray_measure.build_xy_measurement_circuits(result.compressed_circuit)
652
+ xy_results = [
653
+ self.backend.execute(qc, shots=shots).counts
654
+ for qc in xy_circuits
655
+ ]
656
+
657
+ # Full reconstruction
658
+ gray_result = gray_measure.full_reconstruction(
659
+ z_result.counts, xx_results, xy_results, shots
660
+ )
661
+
662
+ # Update result with execution info
663
+ result.execution_result = z_result
664
+ result.metadata["gray_code_measurement"] = True
665
+ result.metadata["measurement_settings"] = gray_result.n_measurement_settings
666
+
667
+ return result, gray_result
668
+
291
669
 
292
670
  def compress_tokens(
293
671
  tokens: list[int],