pyqrack-cpu-complex128 2.1.2__tar.gz → 2.2.1__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 (30) hide show
  1. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/Makefile +1 -1
  2. {pyqrack_cpu_complex128-2.1.2/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.2.1}/PKG-INFO +1 -1
  3. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_ace_backend.py +83 -346
  4. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_simulator.py +19 -6
  5. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_system/qrack_system.py +2 -0
  6. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  7. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/setup.py +1 -1
  8. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/LICENSE +0 -0
  9. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/MANIFEST.in +0 -0
  10. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/README.md +0 -0
  11. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyproject.toml +0 -0
  12. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/__init__.py +0 -0
  13. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/neuron_activation_fn.py +0 -0
  14. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/pauli.py +0 -0
  15. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_circuit.py +0 -0
  16. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  17. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_neuron.py +0 -0
  18. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  19. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_stabilizer.py +0 -0
  20. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/qrack_system/__init__.py +0 -0
  21. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.1.2 → pyqrack_cpu_complex128-2.2.1}/setup.cfg +0 -0
@@ -30,7 +30,7 @@ build-deps:
30
30
  rm -rf pyqrack/qrack_system/qrack_cl_precompile
31
31
  ifneq ($(OS),Windows_NT)
32
32
  ifeq ($(QRACK_PRESENT),)
33
- git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout 7df813ab81978d06decac14b1744b19a9b0cadee; cd ..
33
+ git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout ccb9cca0726e48c598696b51a66fb984ff8ab0f1; cd ..
34
34
  endif
35
35
  mkdir -p qrack/build
36
36
  ifeq ($(UNAME_S),Linux)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.1.2
3
+ Version: 2.2.1
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -29,169 +29,6 @@ except ImportError:
29
29
  _IS_QISKIT_AER_AVAILABLE = False
30
30
 
31
31
 
32
- # Initial stub and concept produced through conversation with Elara
33
- # (the custom OpenAI GPT)
34
- class LHVQubit:
35
- def __init__(self, to_clone=None):
36
- # Initial state in "Bloch vector" terms, defaults to |0⟩
37
- if to_clone:
38
- self.bloch = to_clone.bloch.copy()
39
- else:
40
- self.reset()
41
-
42
- def reset(self):
43
- self.bloch = [0.0, 0.0, 1.0]
44
-
45
- def h(self):
46
- # Hadamard: rotate around Y-axis then X-axis (simplified for LHV)
47
- x, y, z = self.bloch
48
- self.bloch = [(x + z) / math.sqrt(2), y, (z - x) / math.sqrt(2)]
49
-
50
- def x(self):
51
- x, y, z = self.bloch
52
- self.bloch = [x, y, -z]
53
-
54
- def y(self):
55
- x, y, z = self.bloch
56
- self.bloch = [-x, y, z]
57
-
58
- def z(self):
59
- x, y, z = self.bloch
60
- self.bloch = [x, -y, z]
61
-
62
- def rx(self, theta):
63
- # Rotate Bloch vector around X-axis by angle theta
64
- x, y, z = self.bloch
65
- cos_theta = math.cos(theta)
66
- sin_theta = math.sin(theta)
67
- new_y = cos_theta * y - sin_theta * z
68
- new_z = sin_theta * y + cos_theta * z
69
- self.bloch = [x, new_y, new_z]
70
-
71
- def ry(self, theta):
72
- # Rotate Bloch vector around Y-axis by angle theta
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 * z
77
- new_z = -sin_theta * x + cos_theta * z
78
- self.bloch = [new_x, y, new_z]
79
-
80
- def rz(self, theta):
81
- # Rotate Bloch vector around Z-axis by angle theta (in radians)
82
- x, y, z = self.bloch
83
- cos_theta = math.cos(theta)
84
- sin_theta = math.sin(theta)
85
- new_x = cos_theta * x - sin_theta * y
86
- new_y = sin_theta * x + cos_theta * y
87
- self.bloch = [new_x, new_y, z]
88
-
89
- def s(self):
90
- self.rz(math.pi / 2)
91
-
92
- def adjs(self):
93
- self.rz(-math.pi / 2)
94
-
95
- def t(self):
96
- self.rz(math.pi / 4)
97
-
98
- def adjt(self):
99
- self.rz(-math.pi / 4)
100
-
101
- def u(self, theta, phi, lam):
102
- # Apply general single-qubit unitary gate
103
- self.rz(lam)
104
- self.ry(theta)
105
- self.rz(phi)
106
-
107
- # Provided verbatim by Elara (the custom OpenAI GPT):
108
- def mtrx(self, matrix):
109
- """
110
- Apply a 2x2 unitary matrix to the LHV Bloch vector using only standard math/cmath.
111
- Matrix format: [a, b, c, d] for [[a, b], [c, d]]
112
- """
113
- a, b, c, d = matrix
114
-
115
- # Current Bloch vector
116
- x, y, z = self.bloch
117
-
118
- # Convert to density matrix ρ = ½ (I + xσx + yσy + zσz)
119
- rho = [[(1 + z) / 2, (x - 1j * y) / 2], [(x + 1j * y) / 2, (1 - z) / 2]]
120
-
121
- # Compute U * ρ
122
- u_rho = [
123
- [a * rho[0][0] + b * rho[1][0], a * rho[0][1] + b * rho[1][1]],
124
- [c * rho[0][0] + d * rho[1][0], c * rho[0][1] + d * rho[1][1]],
125
- ]
126
-
127
- # Compute (U * ρ) * U†
128
- rho_prime = [
129
- [
130
- u_rho[0][0] * a.conjugate() + u_rho[0][1] * b.conjugate(),
131
- u_rho[0][0] * c.conjugate() + u_rho[0][1] * d.conjugate(),
132
- ],
133
- [
134
- u_rho[1][0] * a.conjugate() + u_rho[1][1] * b.conjugate(),
135
- u_rho[1][0] * c.conjugate() + u_rho[1][1] * d.conjugate(),
136
- ],
137
- ]
138
-
139
- # Extract Bloch components: Tr(ρ'σi) = 2 * Re[...]
140
- new_x = 2 * rho_prime[0][1].real + 2 * rho_prime[1][0].real
141
- new_y = 2 * (rho_prime[0][1].imag - rho_prime[1][0].imag)
142
- new_z = 2 * rho_prime[0][0].real - 1 # since Tr(ρ') = 1
143
-
144
- p = math.sqrt(new_x**2 + new_y**2 + new_z**2)
145
-
146
- new_x /= p
147
- new_y /= p
148
- new_z /= p
149
-
150
- self.bloch = [new_x, new_y, new_z]
151
-
152
- def prob(self, basis=Pauli.PauliZ):
153
- """Sample a classical outcome from the current 'quantum' state"""
154
- if basis == Pauli.PauliZ:
155
- prob_1 = (1 - self.bloch[2]) / 2
156
- elif basis == Pauli.PauliX:
157
- prob_1 = (1 - self.bloch[0]) / 2
158
- elif basis == Pauli.PauliY:
159
- prob_1 = (1 - self.bloch[1]) / 2
160
- else:
161
- raise ValueError(f"Unsupported basis: {basis}")
162
- return prob_1
163
-
164
- def m(self):
165
- result = random.random() < self.prob()
166
- self.reset()
167
- if result:
168
- self.x()
169
- return result
170
-
171
-
172
- # Provided by Elara (the custom OpenAI GPT)
173
- def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
174
- """
175
- Apply a 'soft' controlled-Pauli gate: rotate target qubit
176
- proportionally to control's Z expectation value.
177
-
178
- theta: full rotation angle if control in |1⟩
179
- """
180
- # Control influence is (1 - ctrl.bloch[2]) / 2 = P(|1⟩)
181
- # BUT we avoid collapse by using the expectation value:
182
- control_influence = (1 - prob) if anti else prob
183
-
184
- effective_theta = control_influence * theta
185
-
186
- # Apply partial rotation to target qubit:
187
- if axis == Pauli.PauliX:
188
- targ.rx(effective_theta)
189
- elif axis == Pauli.PauliY:
190
- targ.ry(effective_theta)
191
- elif axis == Pauli.PauliZ:
192
- targ.rz(effective_theta)
193
-
194
-
195
32
  class QrackAceBackend:
196
33
  """A back end for elided quantum error correction
197
34
 
@@ -276,6 +113,18 @@ class QrackAceBackend:
276
113
  self._is_row_long_range[-1] = False
277
114
  sim_count = col_patch_count * row_patch_count
278
115
 
116
+ # Boundary qubits no longer carry a private classical LHV proxy.
117
+ # Instead, every boundary site (row- and/or column-boundary alike)
118
+ # gets a real qubit in one single, shared "crossbar" QrackSimulator.
119
+ # That simulator's own greedy elision (set_sdrp) is trusted to
120
+ # automatically factor apart whatever boundary sites turn out to be
121
+ # separable (e.g. disjoint rails, rail intersections), exactly the
122
+ # same way it already factors apart unentangled subspaces within
123
+ # any other single QrackSimulator instance. We don't need to special
124
+ # -case "crossbar intersections" by hand; the elision does it for us.
125
+ boundary_sim_id = sim_count
126
+ boundary_count = 0
127
+
279
128
  self._qubits = []
280
129
  sim_counts = [0] * sim_count
281
130
  sim_id = 0
@@ -290,9 +139,8 @@ class QrackAceBackend:
290
139
  qubit.append((t_sim_id, sim_counts[t_sim_id]))
291
140
  sim_counts[t_sim_id] += 1
292
141
 
293
- qubit.append(
294
- LHVQubit(to_clone=(to_clone._qubits[tot_qubits][2] if to_clone else None))
295
- )
142
+ qubit.append((boundary_sim_id, boundary_count))
143
+ boundary_count += 1
296
144
 
297
145
  if (not c) and (not r):
298
146
  t_sim_id = (sim_id + col_patch_count) % sim_count
@@ -309,8 +157,23 @@ class QrackAceBackend:
309
157
  self._qubits.append(qubit)
310
158
  tot_qubits += 1
311
159
 
160
+ # The crossbar's size is fixed by how many boundary sites exist.
161
+ # When there are none (e.g. a grid small enough, relative to
162
+ # long_range_rows/columns, that the whole thing is "fully
163
+ # connected" with no QEC boundary at all), we must NOT allocate a
164
+ # 0-qubit QrackSimulator for the crossbar: a 0-qubit QrackSimulator
165
+ # can be constructed, but calling .clone() on one crashes the
166
+ # native Qrack core (segfault), and clone() is called routinely by
167
+ # measure_shots()/clone(). So the boundary sim is only created when
168
+ # boundary_count > 0, exactly mirroring how the original LHV-based
169
+ # code never instantiated anything for the boundary case when
170
+ # there were no boundary sites.
171
+ has_boundary = boundary_count > 0
172
+ if has_boundary:
173
+ sim_counts.append(boundary_count)
174
+
312
175
  self.sim = []
313
- for i in range(sim_count):
176
+ for i in range(sim_count + (1 if has_boundary else 0)):
314
177
  self.sim.append(
315
178
  to_clone.sim[i].clone()
316
179
  if to_clone
@@ -331,6 +194,8 @@ class QrackAceBackend:
331
194
  # (1 - 1 / sqrt(2)) / 4 (but empirically tuned)
332
195
  self.sim[i].set_sdrp(0.073223304703363119)
333
196
 
197
+ self._boundary_sim_id = boundary_sim_id if has_boundary else None
198
+
334
199
  def clone(self):
335
200
  return QrackAceBackend(to_clone=self)
336
201
 
@@ -468,35 +333,23 @@ class QrackAceBackend:
468
333
 
469
334
  @staticmethod
470
335
  def _get_qb_lhv_indices(hq):
471
- qb = []
336
+ # Historically, index 2 (when present) pointed at a private
337
+ # classical LHVQubit proxy and had to be special-cased everywhere.
338
+ # It is now an ordinary (sim_id, idx) tuple into the shared
339
+ # boundary "crossbar" QrackSimulator, so it is just one more
340
+ # coupling target like every other index. We keep this helper's
341
+ # name and signature for minimal call-site churn; "lhv" is now
342
+ # always -1 (no index needs special-casing any more).
472
343
  if len(hq) < 2:
473
344
  qb = [0]
474
- lhv = -1
475
345
  elif len(hq) < 4:
476
- qb = [0, 1]
477
- lhv = 2
346
+ qb = [0, 1, 2]
478
347
  else:
479
- qb = [0, 1, 3, 4]
480
- lhv = 2
348
+ qb = [0, 1, 2, 3, 4]
349
+ lhv = -1
481
350
 
482
351
  return qb, lhv
483
352
 
484
- @staticmethod
485
- def _get_lhv_bloch_angles(sim):
486
- # Z axis
487
- z = sim.bloch[2]
488
-
489
- # X axis
490
- x = sim.bloch[0]
491
-
492
- # Y axis
493
- y = sim.bloch[1]
494
-
495
- inclination = math.atan2(math.sqrt(x**2 + y**2), z)
496
- azimuth = math.atan2(y, x)
497
-
498
- return azimuth, inclination
499
-
500
353
  def _get_bloch_angles(self, hq):
501
354
  sim = self.sim[hq[0]].clone()
502
355
  q = hq[1]
@@ -539,42 +392,25 @@ class QrackAceBackend:
539
392
 
540
393
  sim.mtrx([m00, m01, m10, m11], q)
541
394
 
542
- @staticmethod
543
- def _rotate_lhv_to_bloch(sim, delta_azimuth, delta_inclination):
544
- # Apply rotation as "Azimuth, Inclination" (AI)
545
- cosA = math.cos(delta_azimuth)
546
- sinA = math.sin(delta_azimuth)
547
- cosI = math.cos(delta_inclination / 2)
548
- sinI = math.sin(delta_inclination / 2)
549
-
550
- m00 = complex(cosI, 0)
551
- m01 = complex(-cosA, sinA) * sinI
552
- m10 = complex(cosA, sinA) * sinI
553
- m11 = complex(cosI, 0)
554
-
555
- sim.mtrx([m00, m01, m10, m11])
556
-
557
395
  def _correct(self, lq, phase=False, skip_rotation=False):
558
396
  hq = self._unpack(lq)
559
397
 
560
398
  if len(hq) == 1:
561
399
  return
562
400
 
563
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
401
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
564
402
 
565
403
  if phase:
566
404
  for q in qb:
567
405
  b = hq[q]
568
406
  self.sim[b[0]].h(b[1])
569
- b = hq[lhv]
570
- b.h()
571
407
 
572
408
  if len(hq) == 5:
573
409
  # RMS
574
410
  p = [
575
411
  self.sim[hq[0][0]].prob(hq[0][1]),
576
412
  self.sim[hq[1][0]].prob(hq[1][1]),
577
- hq[2].prob(),
413
+ self.sim[hq[2][0]].prob(hq[2][1]),
578
414
  self.sim[hq[3][0]].prob(hq[3][1]),
579
415
  self.sim[hq[4][0]].prob(hq[4][1]),
580
416
  ]
@@ -598,43 +434,27 @@ class QrackAceBackend:
598
434
  )
599
435
  for q in range(5):
600
436
  if syndrome[q] > (0.5 + self._epsilon):
601
- if q == 2:
602
- hq[q].x()
603
- else:
604
- self.sim[hq[q][0]].x(hq[q][1])
437
+ self.sim[hq[q][0]].x(hq[q][1])
605
438
 
606
439
  if not skip_rotation:
607
440
  a, i = [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]
608
441
  a[0], i[0] = self._get_bloch_angles(hq[0])
609
442
  a[1], i[1] = self._get_bloch_angles(hq[1])
610
- a[2], i[2] = QrackAceBackend._get_lhv_bloch_angles(hq[2])
443
+ a[2], i[2] = self._get_bloch_angles(hq[2])
611
444
  a[3], i[3] = self._get_bloch_angles(hq[3])
612
445
  a[4], i[4] = self._get_bloch_angles(hq[4])
613
446
 
614
- a_target = 0
615
- i_target = 0
616
- for x in range(5):
617
- if x == 2:
618
- continue
619
- a_target += a[x]
620
- i_target += i[x]
621
-
622
- a_target /= 5
623
- i_target /= 5
447
+ a_target = sum(a) / 5
448
+ i_target = sum(i) / 5
624
449
  for x in range(5):
625
- if x == 2:
626
- QrackAceBackend._rotate_lhv_to_bloch(
627
- hq[x], a_target - a[x], i_target - i[x]
628
- )
629
- else:
630
- self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
450
+ self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
631
451
 
632
452
  else:
633
453
  # RMS
634
454
  p = [
635
455
  self.sim[hq[0][0]].prob(hq[0][1]),
636
456
  self.sim[hq[1][0]].prob(hq[1][1]),
637
- hq[2].prob(),
457
+ self.sim[hq[2][0]].prob(hq[2][1]),
638
458
  ]
639
459
  # Balancing suggestion from Elara (the custom OpenAI GPT)
640
460
  prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
@@ -643,41 +463,23 @@ class QrackAceBackend:
643
463
  syndrome = [1 - p[0], 1 - p[1], 1 - p[2]] if result else [p[0], p[1], p[2]]
644
464
  for q in range(3):
645
465
  if syndrome[q] > (0.5 + self._epsilon):
646
- if q == 2:
647
- hq[q].x()
648
- else:
649
- self.sim[hq[q][0]].x(hq[q][1])
466
+ self.sim[hq[q][0]].x(hq[q][1])
650
467
 
651
468
  if not skip_rotation:
652
469
  a, i = [0, 0, 0], [0, 0, 0]
653
470
  a[0], i[0] = self._get_bloch_angles(hq[0])
654
471
  a[1], i[1] = self._get_bloch_angles(hq[1])
655
- a[2], i[2] = QrackAceBackend._get_lhv_bloch_angles(hq[2])
656
-
657
- a_target = 0
658
- i_target = 0
659
- for x in range(3):
660
- if x == 2:
661
- continue
662
- a_target += a[x]
663
- i_target += i[x]
472
+ a[2], i[2] = self._get_bloch_angles(hq[2])
664
473
 
665
- a_target /= 3
666
- i_target /= 3
474
+ a_target = sum(a) / 3
475
+ i_target = sum(i) / 3
667
476
  for x in range(3):
668
- if x == 2:
669
- QrackAceBackend._rotate_lhv_to_bloch(
670
- hq[x], a_target - a[x], i_target - i[x]
671
- )
672
- else:
673
- self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
477
+ self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
674
478
 
675
479
  if phase:
676
480
  for q in qb:
677
481
  b = hq[q]
678
482
  self.sim[b[0]].h(b[1])
679
- b = hq[lhv]
680
- b.h()
681
483
 
682
484
  def apply_magnetic_bias(self, q, b):
683
485
  if b == 0:
@@ -685,22 +487,13 @@ class QrackAceBackend:
685
487
  b = math.exp(b)
686
488
  for x in q:
687
489
  hq = self._unpack(x)
688
- for c in range(len(hq)):
689
- h = hq[c]
690
- if c == 2:
691
- a, i = QrackAceBackend._get_lhv_bloch_angles(h)
692
- QrackAceBackend._rotate_lhv_to_bloch(
693
- h,
694
- math.atan(math.tan(a) * b) - a,
695
- math.atan(math.tan(i) * b) - i,
696
- )
697
- else:
698
- a, i = self._get_bloch_angles(h)
699
- self._rotate_to_bloch(
700
- h,
701
- math.atan(math.tan(a) * b) - a,
702
- math.atan(math.tan(i) * b) - i,
703
- )
490
+ for h in hq:
491
+ a, i = self._get_bloch_angles(h)
492
+ self._rotate_to_bloch(
493
+ h,
494
+ math.atan(math.tan(a) * b) - a,
495
+ math.atan(math.tan(i) * b) - i,
496
+ )
704
497
 
705
498
  def u(self, lq, th, ph, lm):
706
499
  hq = self._unpack(lq)
@@ -709,15 +502,12 @@ class QrackAceBackend:
709
502
  self.sim[b[0]].u(b[1], th, ph, lm)
710
503
  return
711
504
 
712
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
505
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
713
506
 
714
507
  for q in qb:
715
508
  b = hq[q]
716
509
  self.sim[b[0]].u(b[1], th, ph, lm)
717
510
 
718
- b = hq[lhv]
719
- b.u(th, ph, lm)
720
-
721
511
  # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
722
512
 
723
513
  def r(self, p, th, lq):
@@ -727,20 +517,12 @@ class QrackAceBackend:
727
517
  self.sim[b[0]].r(p, th, b[1])
728
518
  return
729
519
 
730
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
520
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
731
521
 
732
522
  for q in qb:
733
523
  b = hq[q]
734
524
  self.sim[b[0]].r(p, th, b[1])
735
525
 
736
- b = hq[lhv]
737
- if p == Pauli.PauliX:
738
- b.rx(th)
739
- elif p == Pauli.PauliY:
740
- b.ry(th)
741
- elif p == Pauli.PauliZ:
742
- b.rz(th)
743
-
744
526
  # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
745
527
 
746
528
  def h(self, lq):
@@ -752,15 +534,12 @@ class QrackAceBackend:
752
534
 
753
535
  self._correct(lq)
754
536
 
755
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
537
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
756
538
 
757
539
  for q in qb:
758
540
  b = hq[q]
759
541
  self.sim[b[0]].h(b[1])
760
542
 
761
- b = hq[lhv]
762
- b.h()
763
-
764
543
  # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
765
544
 
766
545
  def s(self, lq):
@@ -770,15 +549,12 @@ class QrackAceBackend:
770
549
  self.sim[b[0]].s(b[1])
771
550
  return
772
551
 
773
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
552
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
774
553
 
775
554
  for q in qb:
776
555
  b = hq[q]
777
556
  self.sim[b[0]].s(b[1])
778
557
 
779
- b = hq[lhv]
780
- b.s()
781
-
782
558
  def adjs(self, lq):
783
559
  hq = self._unpack(lq)
784
560
  if len(hq) < 2:
@@ -786,15 +562,12 @@ class QrackAceBackend:
786
562
  self.sim[b[0]].adjs(b[1])
787
563
  return
788
564
 
789
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
565
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
790
566
 
791
567
  for q in qb:
792
568
  b = hq[q]
793
569
  self.sim[b[0]].adjs(b[1])
794
570
 
795
- b = hq[lhv]
796
- b.adjs()
797
-
798
571
  def x(self, lq):
799
572
  hq = self._unpack(lq)
800
573
  if len(hq) < 2:
@@ -802,15 +575,12 @@ class QrackAceBackend:
802
575
  self.sim[b[0]].x(b[1])
803
576
  return
804
577
 
805
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
578
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
806
579
 
807
580
  for q in qb:
808
581
  b = hq[q]
809
582
  self.sim[b[0]].x(b[1])
810
583
 
811
- b = hq[lhv]
812
- b.x()
813
-
814
584
  def y(self, lq):
815
585
  hq = self._unpack(lq)
816
586
  if len(hq) < 2:
@@ -818,15 +588,12 @@ class QrackAceBackend:
818
588
  self.sim[b[0]].y(b[1])
819
589
  return
820
590
 
821
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
591
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
822
592
 
823
593
  for q in qb:
824
594
  b = hq[q]
825
595
  self.sim[b[0]].y(b[1])
826
596
 
827
- b = hq[lhv]
828
- b.y()
829
-
830
597
  def z(self, lq):
831
598
  hq = self._unpack(lq)
832
599
  if len(hq) < 2:
@@ -834,15 +601,12 @@ class QrackAceBackend:
834
601
  self.sim[b[0]].z(b[1])
835
602
  return
836
603
 
837
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
604
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
838
605
 
839
606
  for q in qb:
840
607
  b = hq[q]
841
608
  self.sim[b[0]].z(b[1])
842
609
 
843
- b = hq[lhv]
844
- b.z()
845
-
846
610
  def t(self, lq):
847
611
  hq = self._unpack(lq)
848
612
  if len(hq) < 2:
@@ -850,15 +614,12 @@ class QrackAceBackend:
850
614
  self.sim[b[0]].t(b[1])
851
615
  return
852
616
 
853
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
617
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
854
618
 
855
619
  for q in qb:
856
620
  b = hq[q]
857
621
  self.sim[b[0]].t(b[1])
858
622
 
859
- b = hq[lhv]
860
- b.t()
861
-
862
623
  def adjt(self, lq):
863
624
  hq = self._unpack(lq)
864
625
  if len(hq) < 2:
@@ -866,15 +627,12 @@ class QrackAceBackend:
866
627
  self.sim[b[0]].adjt(b[1])
867
628
  return
868
629
 
869
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
630
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
870
631
 
871
632
  for q in qb:
872
633
  b = hq[q]
873
634
  self.sim[b[0]].adjt(b[1])
874
635
 
875
- b = hq[lhv]
876
- b.adjt()
877
-
878
636
  def _get_gate(self, pauli, anti, sim_id):
879
637
  gate = None
880
638
  shadow = None
@@ -913,15 +671,11 @@ class QrackAceBackend:
913
671
 
914
672
  return connected, boundary
915
673
 
916
- def _apply_coupling(self, pauli, anti, qb1, lhv1, hq1, qb2, lhv2, hq2, lq1_lr):
674
+ def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr):
917
675
  for q1 in qb1:
918
- if q1 == lhv1:
919
- continue
920
676
  b1 = hq1[q1]
921
677
  gate_fn, shadow_fn = self._get_gate(pauli, anti, b1[0])
922
678
  for q2 in qb2:
923
- if q2 == lhv2:
924
- continue
925
679
  b2 = hq2[q2]
926
680
  if b1[0] == b2[0]:
927
681
  gate_fn([b1[1]], b2[1])
@@ -942,18 +696,11 @@ class QrackAceBackend:
942
696
 
943
697
  self._correct(lq1)
944
698
 
945
- qb1, lhv1 = QrackAceBackend._get_qb_lhv_indices(hq1)
946
- qb2, lhv2 = QrackAceBackend._get_qb_lhv_indices(hq2)
947
- # Apply cross coupling on hardware qubits first
948
- self._apply_coupling(pauli, anti, qb1, lhv1, hq1, qb2, lhv2, hq2, lq1_lr)
949
- # Apply coupling to the local-hidden-variable target
950
- if lhv2 >= 0:
951
- _cpauli_lhv(
952
- hq1[lhv1].prob() if lhv1 >= 0 else self.sim[hq1[0][0]].prob(hq1[0][1]),
953
- hq2[lhv2],
954
- pauli,
955
- anti,
956
- )
699
+ qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
700
+ qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
701
+ # Apply cross coupling on every qubit, including former-LHV boundary
702
+ # qubits, which now live as real qubits in the shared boundary sim.
703
+ self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr)
957
704
 
958
705
  self._correct(lq1, True)
959
706
  if pauli != Pauli.PauliZ:
@@ -1050,7 +797,7 @@ class QrackAceBackend:
1050
797
  p = [
1051
798
  self.sim[hq[0][0]].prob(hq[0][1]),
1052
799
  self.sim[hq[1][0]].prob(hq[1][1]),
1053
- hq[2].prob(),
800
+ self.sim[hq[2][0]].prob(hq[2][1]),
1054
801
  self.sim[hq[3][0]].prob(hq[3][1]),
1055
802
  self.sim[hq[4][0]].prob(hq[4][1]),
1056
803
  ]
@@ -1071,7 +818,7 @@ class QrackAceBackend:
1071
818
  p = [
1072
819
  self.sim[hq[0][0]].prob(hq[0][1]),
1073
820
  self.sim[hq[1][0]].prob(hq[1][1]),
1074
- hq[2].prob(),
821
+ self.sim[hq[2][0]].prob(hq[2][1]),
1075
822
  ]
1076
823
  # Balancing suggestion from Elara (the custom OpenAI GPT)
1077
824
  prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
@@ -1088,7 +835,7 @@ class QrackAceBackend:
1088
835
  p = self.prob(lq)
1089
836
  result = ((p + self._epsilon) >= 1) or (random.random() < p)
1090
837
 
1091
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
838
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1092
839
 
1093
840
  for q in qb:
1094
841
  b = hq[q]
@@ -1099,11 +846,6 @@ class QrackAceBackend:
1099
846
  else:
1100
847
  self.sim[b[0]].force_m(b[1], result)
1101
848
 
1102
- b = hq[lhv]
1103
- b.reset()
1104
- if result:
1105
- b.x()
1106
-
1107
849
  return result
1108
850
 
1109
851
  def force_m(self, lq, result):
@@ -1114,7 +856,7 @@ class QrackAceBackend:
1114
856
 
1115
857
  self._correct(lq)
1116
858
 
1117
- qb, lhv = QrackAceBackend._get_qb_lhv_indices(hq)
859
+ qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
1118
860
 
1119
861
  for q in qb:
1120
862
  b = hq[q]
@@ -1125,12 +867,7 @@ class QrackAceBackend:
1125
867
  else:
1126
868
  self.sim[b[0]].force_m(b[1], result)
1127
869
 
1128
- b = hq[1]
1129
- b.reset()
1130
- if result:
1131
- b.x()
1132
-
1133
- return c
870
+ return result
1134
871
 
1135
872
  def m_all(self):
1136
873
  # Randomize the order of measurement to amortize error.
@@ -554,8 +554,8 @@ class QrackSimulator:
554
554
  Qrack.qrack_lib.MCAdjT(self.sid, len(c), QrackSimulator._ulonglong_byref(c), q)
555
555
  self._throw_if_error()
556
556
 
557
- def mcu(self, c, q, th, ph, la):
558
- """Multi-controlled arbitraty unitary
557
+ def mcu(self, c, q, th, ph, la, gm=0.0):
558
+ """Multi-controlled arbitrary unitary
559
559
 
560
560
  If all controlled qubits are `|1>` then the unitary gate described by
561
561
  parameters is applied to the target qubit.
@@ -566,6 +566,7 @@ class QrackSimulator:
566
566
  th: theta
567
567
  ph: phi
568
568
  la: lambda
569
+ gm: gamma
569
570
 
570
571
  Raises:
571
572
  RuntimeError: QrackSimulator raised an exception.
@@ -578,6 +579,7 @@ class QrackSimulator:
578
579
  ctypes.c_double(th),
579
580
  ctypes.c_double(ph),
580
581
  ctypes.c_double(la),
582
+ ctypes.c_double(gm),
581
583
  )
582
584
  self._throw_if_error()
583
585
 
@@ -736,8 +738,8 @@ class QrackSimulator:
736
738
  Qrack.qrack_lib.MACAdjT(self.sid, len(c), QrackSimulator._ulonglong_byref(c), q)
737
739
  self._throw_if_error()
738
740
 
739
- def macu(self, c, q, th, ph, la):
740
- """Anti multi-controlled arbitraty unitary
741
+ def macu(self, c, q, th, ph, la, gm=0.0):
742
+ """Anti multi-controlled arbitrary unitary
741
743
 
742
744
  If all controlled qubits are `|0>` then the unitary gate described by
743
745
  parameters is applied to the target qubit.
@@ -748,6 +750,7 @@ class QrackSimulator:
748
750
  th: theta
749
751
  ph: phi
750
752
  la: lambda
753
+ gm: gamma
751
754
 
752
755
  Raises:
753
756
  RuntimeError: QrackSimulator raised an exception.
@@ -760,11 +763,12 @@ class QrackSimulator:
760
763
  ctypes.c_double(th),
761
764
  ctypes.c_double(ph),
762
765
  ctypes.c_double(la),
766
+ ctypes.c_double(gm),
763
767
  )
764
768
  self._throw_if_error()
765
769
 
766
770
  def macmtrx(self, c, m, q):
767
- """Anti multi-controlled arbitraty operator
771
+ """Anti multi-controlled arbitrary operator
768
772
 
769
773
  If all controlled qubits are `|0>` then the arbitrary operation by
770
774
  parameters is applied to the target qubit.
@@ -4194,7 +4198,7 @@ class QrackSimulator:
4194
4198
  float(operation.params[0]),
4195
4199
  float(operation.params[1]),
4196
4200
  )
4197
- elif (name == "cu3") or (name == "cu"):
4201
+ elif name == "cu3":
4198
4202
  self._sim.mcu(
4199
4203
  [q._index for q in operation.qubits[0:1]],
4200
4204
  operation.qubits[1]._index,
@@ -4202,6 +4206,15 @@ class QrackSimulator:
4202
4206
  float(operation.params[1]),
4203
4207
  float(operation.params[2]),
4204
4208
  )
4209
+ elif name == "cu":
4210
+ self._sim.mcu(
4211
+ [q._index for q in operation.qubits[0:1]],
4212
+ operation.qubits[1]._index,
4213
+ float(operation.params[0]),
4214
+ float(operation.params[1]),
4215
+ float(operation.params[2]),
4216
+ float(operation.params[3]),
4217
+ )
4205
4218
  elif name == "cx":
4206
4219
  self._sim.mcx([q._index for q in operation.qubits[0:1]], operation.qubits[1]._index)
4207
4220
  elif name == "cy":
@@ -629,6 +629,7 @@ class QrackSystem:
629
629
  c_double,
630
630
  c_double,
631
631
  c_double,
632
+ c_double,
632
633
  ]
633
634
 
634
635
  self.qrack_lib.MCMtrx.restype = None
@@ -715,6 +716,7 @@ class QrackSystem:
715
716
  c_double,
716
717
  c_double,
717
718
  c_double,
719
+ c_double,
718
720
  ]
719
721
 
720
722
  self.qrack_lib.MACMtrx.restype = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.1.2
3
+ Version: 2.2.1
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 = "2.1.2"
10
+ VERSION = "2.2.1"
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')