pyqrack-cuda 1.54.1__tar.gz → 1.54.3__tar.gz

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.
Files changed (29) hide show
  1. {pyqrack_cuda-1.54.1/pyqrack_cuda.egg-info → pyqrack_cuda-1.54.3}/PKG-INFO +1 -1
  2. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_ace_backend.py +308 -294
  3. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3/pyqrack_cuda.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/setup.py +1 -1
  5. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/LICENSE +0 -0
  6. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/MANIFEST.in +0 -0
  7. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/Makefile +0 -0
  8. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/README.md +0 -0
  9. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyproject.toml +0 -0
  10. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/__init__.py +0 -0
  11. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/pauli.py +0 -0
  13. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_neuron.py +0 -0
  15. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  16. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_simulator.py +0 -0
  17. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_stabilizer.py +0 -0
  18. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_system/__init__.py +0 -0
  19. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/qrack_system/qrack_system.py +0 -0
  20. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/quimb_circuit_type.py +0 -0
  21. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/stats/__init__.py +0 -0
  22. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/stats/load_quantized_data.py +0 -0
  23. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack/stats/quantize_by_range.py +0 -0
  24. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack_cuda.egg-info/SOURCES.txt +0 -0
  25. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack_cuda.egg-info/dependency_links.txt +0 -0
  26. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack_cuda.egg-info/not-zip-safe +0 -0
  27. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack_cuda.egg-info/requires.txt +0 -0
  28. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/pyqrack_cuda.egg-info/top_level.txt +0 -0
  29. {pyqrack_cuda-1.54.1 → pyqrack_cuda-1.54.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cuda
3
- Version: 1.54.1
3
+ Version: 1.54.3
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -27,6 +27,115 @@ except ImportError:
27
27
  _IS_QISKIT_AER_AVAILABLE = False
28
28
 
29
29
 
30
+ # Initial stub and concept produced through conversation with Elara
31
+ # (the custom OpenAI GPT)
32
+ class LHVQubit:
33
+ def __init__(self):
34
+ # Initial state in "Bloch vector" terms, defaults to |0⟩
35
+ self.reset()
36
+
37
+ def reset(self):
38
+ self.bloch = [0.0, 0.0, 1.0]
39
+
40
+ def h(self):
41
+ # Hadamard: rotate around Y-axis then X-axis (simplified for LHV)
42
+ x, y, z = self.bloch
43
+ self.bloch = [(x + z) / math.sqrt(2), y, (z - x) / math.sqrt(2)]
44
+
45
+ def x(self):
46
+ x, y, z = self.bloch
47
+ self.bloch = [x, y, -z]
48
+
49
+ def z(self):
50
+ x, y, z = self.bloch
51
+ self.bloch = [x, -y, z]
52
+
53
+ def rx(self, theta):
54
+ # Rotate Bloch vector around X-axis by angle theta
55
+ x, y, z = self.bloch
56
+ cos_theta = math.cos(theta)
57
+ sin_theta = math.sin(theta)
58
+ new_y = cos_theta * y - sin_theta * z
59
+ new_z = sin_theta * y + cos_theta * z
60
+ self.bloch = [x, new_y, new_z]
61
+
62
+ def ry(self, theta):
63
+ # Rotate Bloch vector around Y-axis by angle theta
64
+ x, y, z = self.bloch
65
+ cos_theta = math.cos(theta)
66
+ sin_theta = math.sin(theta)
67
+ new_x = cos_theta * x + sin_theta * z
68
+ new_z = -sin_theta * x + cos_theta * z
69
+ self.bloch = [new_x, y, new_z]
70
+
71
+ def rz(self, theta):
72
+ # Rotate Bloch vector around Z-axis by angle theta (in radians)
73
+ x, y, z = self.bloch
74
+ cos_theta = math.cos(theta)
75
+ sin_theta = math.sin(theta)
76
+ new_x = cos_theta * x - sin_theta * y
77
+ new_y = sin_theta * x + cos_theta * y
78
+ self.bloch = [new_x, new_y, z]
79
+
80
+ def s(self):
81
+ self.rz(math.pi / 2)
82
+
83
+ def adjs(self):
84
+ self.rz(-math.pi / 2)
85
+
86
+ def t(self):
87
+ self.rz(math.pi / 4)
88
+
89
+ def adjt(self):
90
+ self.rz(-math.pi / 4)
91
+
92
+ def u(self, theta, phi, lam):
93
+ # Apply general single-qubit unitary gate
94
+ self.rz(lam)
95
+ self.ry(theta)
96
+ self.rz(phi)
97
+
98
+ def prob(self, basis=Pauli.PauliZ):
99
+ """Sample a classical outcome from the current 'quantum' state"""
100
+ if basis == Pauli.PauliZ:
101
+ prob_1 = (1 - self.bloch[2]) / 2
102
+ elif basis == Pauli.PauliX:
103
+ prob_1 = (1 - self.bloch[0]) / 2
104
+ elif basis == Pauli.PauliY:
105
+ prob_1 = (1 - self.bloch[1]) / 2
106
+ else:
107
+ raise ValueError(f"Unsupported basis: {basis}")
108
+ return prob_1
109
+
110
+ def m(self):
111
+ result = random.random() < self.prob()
112
+ self.reset()
113
+ if result:
114
+ self.x()
115
+ return result
116
+
117
+ # Provided by Elara (the custom OpenAI GPT)
118
+ def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
119
+ """
120
+ Apply a 'soft' controlled-Pauli gate: rotate target qubit
121
+ proportionally to control's Z expectation value.
122
+
123
+ theta: full rotation angle if control in |1⟩
124
+ """
125
+ # Control influence is (1 - ctrl.bloch[2]) / 2 = P(|1⟩)
126
+ # BUT we avoid collapse by using the expectation value:
127
+ control_influence = (1 - prob) if anti else prob
128
+
129
+ effective_theta = control_influence * theta
130
+
131
+ # Apply partial rotation to target qubit:
132
+ if axis == Pauli.PauliX:
133
+ targ.rx(effective_theta)
134
+ elif axis == Pauli.PauliY:
135
+ targ.ry(effective_theta)
136
+ elif axis == Puali.PauliZ:
137
+ targ.rz(effective_theta)
138
+
30
139
  class QrackAceBackend:
31
140
  """A back end for elided quantum error correction
32
141
 
@@ -85,8 +194,8 @@ class QrackAceBackend:
85
194
  self._is_col_long_range[-1] = False
86
195
 
87
196
  self._qubit_dict = {}
197
+ self._lhv_dict = {}
88
198
  self._hardware_offset = []
89
- self._ancilla = [0] * sim_count
90
199
  sim_counts = [0] * sim_count
91
200
  sim_id = 0
92
201
  tot_qubits = 0
@@ -97,28 +206,21 @@ class QrackAceBackend:
97
206
  self._qubit_dict[tot_qubits] = (sim_id, sim_counts[sim_id])
98
207
  tot_qubits += 1
99
208
  sim_counts[sim_id] += 1
100
- elif (r & 1):
101
- for _ in range(2):
102
- self._qubit_dict[tot_qubits] = (sim_id, sim_counts[sim_id])
103
- tot_qubits += 1
104
- sim_counts[sim_id] += 1
105
- sim_id = (sim_id + 1) % sim_count
209
+ else:
106
210
  self._qubit_dict[tot_qubits] = (sim_id, sim_counts[sim_id])
107
211
  tot_qubits += 1
108
212
  sim_counts[sim_id] += 1
109
- else:
213
+
214
+ self._lhv_dict[tot_qubits] = LHVQubit()
215
+ tot_qubits += 1
216
+
217
+ sim_id = (sim_id + 1) % sim_count
110
218
  self._qubit_dict[tot_qubits] = (sim_id, sim_counts[sim_id])
111
219
  tot_qubits += 1
112
220
  sim_counts[sim_id] += 1
113
- sim_id = (sim_id + 1) % sim_count
114
- for _ in range(2):
115
- self._qubit_dict[tot_qubits] = (sim_id, sim_counts[sim_id])
116
- tot_qubits += 1
117
- sim_counts[sim_id] += 1
118
221
 
119
222
  self.sim = []
120
223
  for i in range(sim_count):
121
- self._ancilla[i] = sim_counts[i]
122
224
  sim_counts[i] += 1
123
225
  self.sim.append(
124
226
  toClone.sim[i].clone()
@@ -156,8 +258,8 @@ class QrackAceBackend:
156
258
  self._col_length, self._row_length = (row_len, col_len) if is_transpose else (col_len, row_len)
157
259
 
158
260
  def _ct_pair_prob(self, q1, q2):
159
- p1 = self.sim[q1[0]].prob(q1[1])
160
- p2 = self.sim[q2[0]].prob(q2[1])
261
+ p1 = self.sim[q1[0]].prob(q1[1]) if isinstance(q1, tuple) else q1.prob()
262
+ p2 = self.sim[q2[0]].prob(q2[1]) if isinstance(q2, tuple) else q2.prob()
161
263
 
162
264
  if p1 < p2:
163
265
  return p2, q1
@@ -167,52 +269,59 @@ class QrackAceBackend:
167
269
  def _cz_shadow(self, q1, q2):
168
270
  prob_max, t = self._ct_pair_prob(q1, q2)
169
271
  if prob_max > 0.5:
170
- self.sim[t[0]].z(t[1])
272
+ if isinstance(t, tuple):
273
+ self.sim[t[0]].z(t[1])
274
+ else:
275
+ t.z()
276
+
277
+ def _qec_x(self, c):
278
+ if isinstance(c, tuple):
279
+ self.sim[c[0]].x(c[1])
280
+ else:
281
+ c.x()
282
+
283
+ def _qec_h(self, t):
284
+ if isinstance(t, tuple):
285
+ self.sim[t[0]].h(t[1])
286
+ else:
287
+ t.h()
288
+
289
+ def _qec_s(self, t):
290
+ if isinstance(t, tuple):
291
+ self.sim[c[0]].s(t[1])
292
+ else:
293
+ t.s()
294
+
295
+ def _qec_adjs(self, t):
296
+ if isinstance(t, tuple):
297
+ self.sim[c[0]].adjs(t[1])
298
+ else:
299
+ t.adjs()
171
300
 
172
301
  def _anti_cz_shadow(self, c, t):
173
- self.sim[c[0]].x(c[1])
302
+ self._qec_x(c)
174
303
  self._cz_shadow(c, t)
175
- self.sim[c[0]].x(c[1])
304
+ self._qec_x(c)
176
305
 
177
306
  def _cx_shadow(self, c, t):
178
- self.sim[t[0]].h(t[1])
307
+ self._qec_h(t)
179
308
  self._cz_shadow(c, t)
180
- self.sim[t[0]].h(t[1])
309
+ self._qec_h(t)
181
310
 
182
311
  def _anti_cx_shadow(self, c, t):
183
- self.sim[c[0]].x(c[1])
312
+ self._qec_x(c)
184
313
  self._cx_shadow(c, t)
185
- self.sim[c[0]].x(c[1])
314
+ self._qec_x(c)
186
315
 
187
316
  def _cy_shadow(self, c, t):
188
- self.sim[t[0]].adjs(t[1])
317
+ self._qec_adjs(t)
189
318
  self._cx_shadow(c, t)
190
- self.sim[t[0]].s(t[1])
319
+ self._qec_s(t)
191
320
 
192
321
  def _anti_cy_shadow(self, c, t):
193
- self.sim[c[0]].x(c[1])
322
+ self._qec_x(c)
194
323
  self._cy_shadow(c, t)
195
- self.sim[c[0]].x(c[1])
196
-
197
- def _ccz_shadow(self, c1, q2, q3):
198
- self.sim[q2[0]].mcx([q2[1]], q3[1])
199
- self.sim[q3[0]].adjt(q3[1])
200
- self._cx_shadow(c1, q3)
201
- self.sim[q3[0]].t(q3[1])
202
- self.sim[q2[0]].mcx([q2[1]], q3[1])
203
- self.sim[q3[0]].adjt(q3[1])
204
- self._cx_shadow(c1, q3)
205
- self.sim[q3[0]].t(q3[1])
206
- self.sim[q2[0]].t(q2[1])
207
- self._cx_shadow(c1, q2)
208
- self.sim[q2[0]].adjt(q2[1])
209
- self.sim[c1[0]].t(c1[1])
210
- self._cx_shadow(c1, q2)
211
-
212
- def _ccx_shadow(self, c1, q2, t):
213
- self.sim[t[0]].h(t[1])
214
- self._ccz_shadow(c1, q2, t)
215
- self.sim[t[0]].h(t[1])
324
+ self._qec_x(c)
216
325
 
217
326
  def _unpack(self, lq):
218
327
  offset = self._hardware_offset[lq]
@@ -222,133 +331,43 @@ class QrackAceBackend:
222
331
 
223
332
  return [
224
333
  self._qubit_dict[offset],
225
- self._qubit_dict[offset + 1],
334
+ self._lhv_dict[offset + 1],
226
335
  self._qubit_dict[offset + 2],
227
336
  ]
228
337
 
229
- def _encode_decode(self, hq):
230
- if len(hq) < 2:
231
- return
232
- if hq[0][0] == hq[1][0]:
233
- b = hq[0]
234
- else:
235
- b = hq[2]
236
- self.sim[b[0]].mcx([b[1]], hq[1][1])
237
-
238
338
  def _correct(self, lq, phase=False):
239
- if self._is_col_long_range[lq % self._row_length]:
339
+ if self._is_col_long_range[lq % self._row_length] or (self._row_length == 2):
240
340
  return
241
- # We can't use true syndrome-based error correction,
242
- # because one of the qubits in the code is separated.
243
- # However, we can get pretty close!
244
- shots = 512
245
341
 
246
- single_bit = 0
247
- other_bits = []
248
342
  hq = self._unpack(lq)
249
343
 
250
344
  if phase:
251
- for b in hq:
252
- self.sim[b[0]].h(b[1])
253
-
254
- if hq[0][0] == hq[1][0]:
255
- single_bit = 2
256
- other_bits = [0, 1]
257
- elif hq[1][0] == hq[2][0]:
258
- single_bit = 0
259
- other_bits = [1, 2]
260
- else:
261
- raise RuntimeError("Invalid boundary qubit!")
262
-
263
- ancilla_sim = hq[other_bits[0]][0]
264
- ancilla = self._ancilla[ancilla_sim]
265
-
266
- single_bit_value = self.sim[hq[single_bit][0]].prob(hq[single_bit][1])
267
- single_bit_polarization = max(single_bit_value, 1 - single_bit_value)
268
-
269
- # Suggestion from Elara (the custom OpenAI GPT):
270
- # Create phase parity tie before measurement.
271
- self._ccx_shadow(hq[single_bit], hq[other_bits[0]], [ancilla_sim, ancilla])
272
- self.sim[ancilla_sim].mcx([hq[other_bits[1]][1]], ancilla)
273
- self.sim[ancilla_sim].force_m(ancilla, False)
274
-
275
- samples = self.sim[ancilla_sim].measure_shots(
276
- [hq[other_bits[0]][1], hq[other_bits[1]][1]], shots
277
- )
278
-
279
- syndrome_indices = (
280
- [other_bits[1], other_bits[0]]
281
- if (single_bit_value >= 0.5)
282
- else [other_bits[0], other_bits[1]]
283
- )
284
- syndrome = [0, 0, 0]
285
- values = []
286
- for sample in samples:
287
- match sample:
288
- case 0:
289
- value = single_bit_value
290
- syndrome[single_bit] += value
291
- case 1:
292
- value = single_bit_polarization
293
- syndrome[syndrome_indices[0]] += value
294
- case 2:
295
- value = single_bit_polarization
296
- syndrome[syndrome_indices[1]] += value
297
- case 3:
298
- value = 1 - single_bit_value
299
- syndrome[single_bit] += value
300
- values.append(value)
301
-
302
- # Suggestion from Elara (custom OpenAI GPT):
303
- # Compute the standard deviation and only correct if we're outside a confidence interval.
304
- # (This helps avoid limit-point over-correction.)
305
- syndrome_sum = sum(syndrome)
306
- z_score_numer = syndrome_sum - shots / 2
307
- z_score = 0
308
- if z_score_numer > 0:
309
- syndrome_component_mean = syndrome_sum / shots
310
- syndrome_total_variance = sum(
311
- (value - syndrome_component_mean) ** 2 for value in values
312
- )
313
- z_score_denom = math.sqrt(syndrome_total_variance)
314
- z_score = (
315
- math.inf
316
- if math.isclose(z_score_denom, 0)
317
- else (z_score_numer / z_score_denom)
318
- )
345
+ b = hq[0]
346
+ self.sim[b[0]].h(b[1])
347
+ b = hq[1]
348
+ b.h()
349
+ b = hq[2]
350
+ self.sim[b[0]].h(b[1])
319
351
 
320
- force_syndrome = True
321
- # (From Elara, this is the value that minimizes the sum of Type I and Type II error.)
322
- if z_score >= (497 / 999):
323
- # There is an error.
324
- error_bit = syndrome.index(max(syndrome))
325
- if error_bit == single_bit:
326
- # The stand-alone bit carries the error.
327
- self.sim[hq[error_bit][0]].x(hq[error_bit][1])
328
- else:
329
- # The coherent bits carry the error.
330
- force_syndrome = False
331
- # Form their syndrome.
332
- self.sim[ancilla_sim].mcx([hq[other_bits[0]][1]], ancilla)
333
- self.sim[ancilla_sim].mcx([hq[other_bits[1]][1]], ancilla)
334
- # Force the syndrome pathological
335
- self.sim[ancilla_sim].force_m(ancilla, True)
336
- # Reset the ancilla.
337
- self.sim[ancilla_sim].x(ancilla)
338
- # Correct the bit flip.
339
- self.sim[ancilla_sim].x(hq[error_bit][1])
340
-
341
- # There is no error.
342
- if force_syndrome:
343
- # Form the syndrome of the coherent bits.
344
- self.sim[ancilla_sim].mcx([hq[other_bits[0]][1]], ancilla)
345
- self.sim[ancilla_sim].mcx([hq[other_bits[1]][1]], ancilla)
346
- # Force the syndrome non-pathological.
347
- self.sim[ancilla_sim].force_m(ancilla, False)
352
+ p = [self.sim[hq[0][0]].prob(hq[0][1]), hq[1].prob(), self.sim[hq[2][0]].prob(hq[2][1])]
353
+ avg = sum(p) / 3
354
+ result = (avg >= 0.5)
355
+ syndrome = [1 - p[0], 1 - p[1], 1 - p[2]] if result else [p[0], p[1], p[2]]
356
+
357
+ for q in range(3):
358
+ if syndrome[q] > 0.5:
359
+ if q == 1:
360
+ hq[q].x()
361
+ else:
362
+ self.sim[hq[q][0]].x(hq[q][1])
348
363
 
349
364
  if phase:
350
- for b in hq:
351
- self.sim[b[0]].h(b[1])
365
+ b = hq[0]
366
+ self.sim[b[0]].h(b[1])
367
+ b = hq[1]
368
+ b.h()
369
+ b = hq[2]
370
+ self.sim[b[0]].h(b[1])
352
371
 
353
372
  def u(self, lq, th, ph, lm):
354
373
  hq = self._unpack(lq)
@@ -357,28 +376,15 @@ class QrackAceBackend:
357
376
  self.sim[b[0]].u(b[1], th, ph, lm)
358
377
  return
359
378
 
360
- while ph > math.pi:
361
- ph -= 2 * math.pi
362
- while ph <= -math.pi:
363
- ph += 2 * math.pi
364
- while lm > math.pi:
365
- lm -= 2 * math.pi
366
- while lm <= -math.pi:
367
- lm += 2 * math.pi
368
-
369
- if not math.isclose(ph, -lm) and not math.isclose(abs(ph), math.pi / 2):
370
- # Produces/destroys superposition
371
- self._encode_decode(hq)
372
- b = hq[0]
373
- self.sim[b[0]].u(b[1], th, ph, lm)
374
- b = hq[2]
375
- self.sim[b[0]].u(b[1], th, ph, lm)
376
- self._encode_decode(hq)
377
- self._correct(lq)
378
- else:
379
- # Shouldn't produce/destroy superposition
380
- for b in hq:
381
- self.sim[b[0]].u(b[1], th, ph, lm)
379
+ b = hq[0]
380
+ self.sim[b[0]].u(b[1], th, ph, lm)
381
+ b = hq[1]
382
+ b.u(th, ph, lm)
383
+ b = hq[2]
384
+ self.sim[b[0]].u(b[1], th, ph, lm)
385
+
386
+ self._correct(lq, False)
387
+ self._correct(lq, True)
382
388
 
383
389
  def r(self, p, th, lq):
384
390
  hq = self._unpack(lq)
@@ -387,23 +393,22 @@ class QrackAceBackend:
387
393
  self.sim[b[0]].r(p, th, b[1])
388
394
  return
389
395
 
390
- while th > math.pi:
391
- th -= 2 * math.pi
392
- while th <= -math.pi:
393
- th += 2 * math.pi
394
- if (p == Pauli.PauliZ) or math.isclose(abs(th), math.pi):
395
- # Doesn't produce/destroy superposition
396
- for b in hq:
397
- self.sim[b[0]].r(p, th, b[1])
398
- else:
399
- # Produces/destroys superposition
400
- self._encode_decode(hq)
401
- b = hq[0]
402
- self.sim[b[0]].r(p, th, b[1])
403
- b = hq[2]
404
- self.sim[b[0]].r(p, th, b[1])
405
- self._encode_decode(hq)
406
- self._correct(lq)
396
+ b = hq[0]
397
+ self.sim[b[0]].r(p, th, b[1])
398
+ b = hq[1]
399
+ if p == Pauli.PauliX:
400
+ b.rx(th)
401
+ elif p == Pauli.PauliY:
402
+ b.ry(th)
403
+ elif p == Pauli.PauliZ:
404
+ b.rz(th)
405
+ b = hq[2]
406
+ self.sim[b[0]].r(p, th, b[1])
407
+
408
+ if p != Pauli.PauliZ:
409
+ self._correct(lq, False)
410
+ if p != Pauli.PauliX:
411
+ self._correct(lq, True)
407
412
 
408
413
  def h(self, lq):
409
414
  hq = self._unpack(lq)
@@ -412,12 +417,13 @@ class QrackAceBackend:
412
417
  self.sim[b[0]].h(b[1])
413
418
  return
414
419
 
415
- self._encode_decode(hq)
420
+ self._correct(lq)
416
421
  b = hq[0]
417
422
  self.sim[b[0]].h(b[1])
423
+ b = hq[1]
424
+ b.h()
418
425
  b = hq[2]
419
426
  self.sim[b[0]].h(b[1])
420
- self._encode_decode(hq)
421
427
  self._correct(lq)
422
428
 
423
429
  def s(self, lq):
@@ -427,8 +433,12 @@ class QrackAceBackend:
427
433
  self.sim[b[0]].s(b[1])
428
434
  return
429
435
 
430
- for b in hq:
431
- self.sim[b[0]].s(b[1])
436
+ b = hq[0]
437
+ self.sim[b[0]].s(b[1])
438
+ b = hq[1]
439
+ b.s()
440
+ b = hq[2]
441
+ self.sim[b[0]].s(b[1])
432
442
 
433
443
  def adjs(self, lq):
434
444
  hq = self._unpack(lq)
@@ -437,8 +447,12 @@ class QrackAceBackend:
437
447
  self.sim[b[0]].adjs(b[1])
438
448
  return
439
449
 
440
- for b in hq:
441
- self.sim[b[0]].adjs(b[1])
450
+ b = hq[0]
451
+ self.sim[b[0]].adjs(b[1])
452
+ b = hq[1]
453
+ b.adjs()
454
+ b = hq[2]
455
+ self.sim[b[0]].adjs(b[1])
442
456
 
443
457
  def x(self, lq):
444
458
  hq = self._unpack(lq)
@@ -447,8 +461,12 @@ class QrackAceBackend:
447
461
  self.sim[b[0]].x(b[1])
448
462
  return
449
463
 
450
- for b in hq:
451
- self.sim[b[0]].x(b[1])
464
+ b = hq[0]
465
+ self.sim[b[0]].x(b[1])
466
+ b = hq[1]
467
+ b.x()
468
+ b = hq[2]
469
+ self.sim[b[0]].x(b[1])
452
470
 
453
471
  def y(self, lq):
454
472
  hq = self._unpack(lq)
@@ -457,8 +475,12 @@ class QrackAceBackend:
457
475
  self.sim[b[0]].y(b[1])
458
476
  return
459
477
 
460
- for b in hq:
461
- self.sim[b[0]].y(b[1])
478
+ b = hq[0]
479
+ self.sim[b[0]].y(b[1])
480
+ b = hq[1]
481
+ b.y()
482
+ b = hq[2]
483
+ self.sim[b[0]].y(b[1])
462
484
 
463
485
  def z(self, lq):
464
486
  hq = self._unpack(lq)
@@ -467,8 +489,12 @@ class QrackAceBackend:
467
489
  self.sim[b[0]].z(b[1])
468
490
  return
469
491
 
470
- for b in hq:
471
- self.sim[b[0]].z(b[1])
492
+ b = hq[0]
493
+ self.sim[b[0]].z(b[1])
494
+ b = hq[1]
495
+ b.z()
496
+ b = hq[2]
497
+ self.sim[b[0]].z(b[1])
472
498
 
473
499
  def t(self, lq):
474
500
  hq = self._unpack(lq)
@@ -477,8 +503,12 @@ class QrackAceBackend:
477
503
  self.sim[b[0]].t(b[1])
478
504
  return
479
505
 
480
- for b in hq:
481
- self.sim[b[0]].t(b[1])
506
+ b = hq[0]
507
+ self.sim[b[0]].t(b[1])
508
+ b = hq[1]
509
+ b.t()
510
+ b = hq[2]
511
+ self.sim[b[0]].t(b[1])
482
512
 
483
513
  def adjt(self, lq):
484
514
  hq = self._unpack(lq)
@@ -487,8 +517,12 @@ class QrackAceBackend:
487
517
  self.sim[b[0]].adjt(b[1])
488
518
  return
489
519
 
490
- for b in hq:
491
- self.sim[b[0]].adjt(b[1])
520
+ b = hq[0]
521
+ self.sim[b[0]].adjt(b[1])
522
+ b = hq[1]
523
+ b.adjt()
524
+ b = hq[2]
525
+ self.sim[b[0]].adjt(b[1])
492
526
 
493
527
  def _get_gate(self, pauli, anti, sim_id):
494
528
  gate = None
@@ -541,6 +575,14 @@ class QrackAceBackend:
541
575
  shadow(b1, b2)
542
576
  return
543
577
 
578
+ if self._row_length == 2:
579
+ gate, shadow = self._get_gate(pauli, anti, hq1[2][0])
580
+ gate([hq1[2][1]], hq2[0][1])
581
+ gate, shadow = self._get_gate(pauli, anti, hq1[0][0])
582
+ gate([hq1[0][1]], hq2[2][1])
583
+ _cpauli_lhv(hq1[1].prob(), hq2[1], pauli, anti)
584
+ return
585
+
544
586
  connected_cols = []
545
587
  c = (lq1_col - 1) % self._row_length
546
588
  while self._is_col_long_range[c] and (
@@ -564,64 +606,55 @@ class QrackAceBackend:
564
606
 
565
607
  if (lq2_col in connected_cols) and (connected_cols.index(lq2_col) < boundary):
566
608
  # lq2_col < lq1_col
567
- self._encode_decode(hq1)
568
- self._encode_decode(hq2)
569
609
  gate, shadow = self._get_gate(pauli, anti, hq1[0][0])
570
610
  if lq1_lr:
571
611
  gate([hq1[0][1]], hq2[2][1])
572
612
  shadow(hq1[0], hq2[0])
613
+ _cpauli_lhv(self.sim[hq1[0][0]].prob(hq1[0][1]), hq2[1], pauli, anti)
573
614
  elif lq2_lr:
574
615
  gate([hq1[0][1]], hq2[0][1])
575
616
  else:
576
617
  gate([hq1[0][1]], hq2[2][1])
577
618
  shadow(hq1[2], hq2[0])
578
- self._encode_decode(hq2)
579
- self._encode_decode(hq1)
619
+ _cpauli_lhv(hq1[1].prob(), hq2[1], pauli, anti)
580
620
  elif lq2_col in connected_cols:
581
621
  # lq1_col < lq2_col
582
- self._encode_decode(hq1)
583
- self._encode_decode(hq2)
584
622
  gate, shadow = self._get_gate(pauli, anti, hq2[0][0])
585
623
  if lq1_lr:
586
624
  gate([hq1[0][1]], hq2[0][1])
587
625
  shadow(hq1[0], hq2[2])
626
+ _cpauli_lhv(self.sim[hq1[0][0]].prob(hq1[0][1]), hq2[1], pauli, anti)
588
627
  elif lq2_lr:
589
628
  gate([hq1[2][1]], hq2[0][1])
590
629
  else:
591
630
  gate([hq1[2][1]], hq2[0][1])
592
631
  shadow(hq1[0], hq2[2])
593
- self._encode_decode(hq2)
594
- self._encode_decode(hq1)
632
+ _cpauli_lhv(hq1[1].prob(), hq2[1], pauli, anti)
595
633
  elif lq1_col == lq2_col:
596
634
  # Both are in the same boundary column.
597
- self._encode_decode(hq1)
598
- self._encode_decode(hq2)
599
635
  b = hq1[0]
600
636
  gate, shadow = self._get_gate(pauli, anti, b[0])
601
637
  gate([b[1]], hq2[0][1])
638
+ _cpauli_lhv(hq1[1].prob(), hq2[1], pauli, anti)
602
639
  b = hq1[2]
603
640
  gate, shadow = self._get_gate(pauli, anti, b[0])
604
641
  gate([b[1]], hq2[2][1])
605
- self._encode_decode(hq1)
606
- self._encode_decode(hq2)
607
642
  else:
608
643
  # The qubits have no quantum connection.
609
644
  gate, shadow = self._get_gate(pauli, anti, hq1[0][0])
610
- self._encode_decode(hq1)
611
- self._encode_decode(hq2)
612
645
  shadow(hq1[0], hq2[0])
613
646
  if lq1_lr:
614
647
  shadow(hq1[0], hq2[2])
648
+ shadow(hq1[0], hq2[1])
615
649
  elif not lq2_lr:
650
+ _cpauli_lhv(hq1[1].prob(), hq2[1], pauli, anti)
616
651
  shadow(hq1[2], hq2[2])
617
- self._encode_decode(hq2)
618
- self._encode_decode(hq1)
619
652
 
620
653
  self._correct(lq1, True)
621
654
  if pauli != Pauli.PauliZ:
622
655
  self._correct(lq2, False)
623
656
  if pauli != Pauli.PauliX:
624
- self._correct(lq2, True)
657
+ self._corrent(lq2, True)
625
658
 
626
659
  def cx(self, lq1, lq2):
627
660
  self._cpauli(lq1, lq2, False, Pauli.PauliX)
@@ -700,31 +733,36 @@ class QrackAceBackend:
700
733
  self.cz(lq1, lq2)
701
734
  self.swap(lq1, lq2)
702
735
 
736
+ def prob(self, lq):
737
+ hq = self._unpack(lq)
738
+ if len(hq) < 2:
739
+ b = hq[0]
740
+ return self.sim[b[0]].prob(b[1])
741
+
742
+ self._correct(lq)
743
+ b0 = hq[0]
744
+ b2 = hq[2]
745
+ result = (self.sim[b0[0]].prob(b0[1]) + self.sim[b2[0]].prob(b2[1])) / 2
746
+
747
+ return result
748
+
703
749
  def m(self, lq):
704
750
  hq = self._unpack(lq)
705
751
  if len(hq) < 2:
706
752
  b = hq[0]
707
753
  return self.sim[b[0]].m(b[1])
708
754
 
709
- if hq[0][0] == hq[0][1]:
710
- single_bit = 2
711
- other_bits = [0, 1]
712
- else:
713
- single_bit = 0
714
- other_bits = [1, 2]
715
- # The syndrome of "other_bits" is guaranteed to be fixed, after this.
716
- self._correct(lq)
717
- b = hq[other_bits[0]]
718
- syndrome = self.sim[b[0]].m(b[1])
719
- b = hq[other_bits[1]]
720
- syndrome += self.sim[b[0]].force_m(b[1], bool(syndrome))
721
- # The two separable parts of the code are correlated,
722
- # but not non-locally, via entanglement.
723
- # Collapse the other separable part toward agreement.
724
- b = hq[single_bit]
725
- syndrome += self.sim[b[0]].force_m(b[1], bool(syndrome))
726
-
727
- return True if (syndrome > 1) else False
755
+ result = (random.random() < self.prob(lq))
756
+ b = hq[0]
757
+ self.sim[b[0]].force_m(b[1], result)
758
+ b = hq[1]
759
+ b.reset()
760
+ if result:
761
+ b.x()
762
+ b = hq[2]
763
+ self.sim[b[0]].force_m(b[1], result)
764
+
765
+ return result
728
766
 
729
767
  def force_m(self, lq, c):
730
768
  hq = self._unpack(lq)
@@ -733,33 +771,28 @@ class QrackAceBackend:
733
771
  return self.sim[b[0]].force_m(b[1])
734
772
 
735
773
  self._correct(lq)
736
- for q in hq:
737
- self.sim[q[0]].force_m(q[1], c)
774
+ b = hq[1]
775
+ b.reset()
776
+ if c:
777
+ b.x()
778
+ b = hq[0]
779
+ self.sim[b[0]].force_m(b[1], c)
780
+ b = hq[2]
781
+ self.sim[b[0]].force_m(b[1], c)
738
782
 
739
783
  return c
740
784
 
741
785
  def m_all(self):
742
- result = 0
743
786
  # Randomize the order of measurement to amortize error.
744
- # However, locality of collapse matters:
745
- # measure in row pairs, and always across rows,
746
- # and by row directionality.
747
- row_pairs = list(range((self._col_length + 1) // 2))
748
- random.shuffle(row_pairs)
749
- for row_pair in row_pairs:
750
- col_offset = random.randint(0, self._row_length - 1)
751
- lq_row = row_pair << 1
752
- for c in range(self._row_length):
753
- lq_col = (c + col_offset) % self._row_length
754
- lq = lq_row * self._row_length + lq_col
755
- if self.m(lq):
756
- result |= 1 << lq
757
- lq_row += 1
758
- if lq_row == self._col_length:
759
- continue
760
- for c in range(self._row_length):
761
- lq_col = ((self._row_length - (c + 1)) + col_offset) % self._row_length
762
- lq = lq_row * self._row_length + lq_col
787
+ result = 0
788
+ rows = list(range(self._col_length))
789
+ random.shuffle(rows)
790
+ for lq_row in rows:
791
+ row_offset = lq_row * self._row_length
792
+ cols = list(range(self._row_length))
793
+ random.shuffle(cols)
794
+ for lq_col in cols:
795
+ lq = row_offset + lq_col
763
796
  if self.m(lq):
764
797
  result |= 1 << lq
765
798
 
@@ -778,25 +811,6 @@ class QrackAceBackend:
778
811
 
779
812
  return samples
780
813
 
781
- def prob(self, lq):
782
- hq = self._unpack(lq)
783
- if len(hq) < 2:
784
- b = hq[0]
785
- return self.sim[b[0]].prob(b[1])
786
-
787
- self._correct(lq)
788
- if hq[0][0] == hq[1][0]:
789
- other_bits = [0, 1]
790
- else:
791
- other_bits = [1, 2]
792
- b0 = hq[other_bits[0]]
793
- b1 = hq[other_bits[1]]
794
- self.sim[b0[0]].mcx([b0[1]], b1[1])
795
- result = self.sim[b0[0]].prob(b0[1])
796
- self.sim[b0[0]].mcx([b0[1]], b1[1])
797
-
798
- return result
799
-
800
814
  def _apply_op(self, operation):
801
815
  name = operation.name
802
816
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cuda
3
- Version: 1.54.1
3
+ Version: 1.54.3
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -7,7 +7,7 @@ from setuptools import setup
7
7
  from setuptools.command.build_py import build_py
8
8
 
9
9
 
10
- VERSION = "1.54.1"
10
+ VERSION = "1.54.3"
11
11
 
12
12
  # Read long description from README.
13
13
  README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')
File without changes
File without changes
File without changes
File without changes
File without changes