pyqrack-complex128 2.28.2__tar.gz → 2.29.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 (31) hide show
  1. {pyqrack_complex128-2.28.2/pyqrack_complex128.egg-info → pyqrack_complex128-2.29.1}/PKG-INFO +1 -1
  2. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_ace_backend.py +122 -207
  3. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/setup.py +1 -1
  5. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/LICENSE +0 -0
  6. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/MANIFEST.in +0 -0
  7. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/Makefile +0 -0
  8. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/README.md +0 -0
  9. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyproject.toml +0 -0
  10. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/__init__.py +0 -0
  11. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/pauli.py +0 -0
  13. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_ace_mps_backend.py +0 -0
  14. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_circuit.py +0 -0
  15. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  16. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_neuron.py +0 -0
  17. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  18. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_simulator.py +0 -0
  19. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_stabilizer.py +0 -0
  20. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_system/__init__.py +0 -0
  21. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_system/qrack_system.py +0 -0
  22. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/quimb_circuit_type.py +0 -0
  23. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/__init__.py +0 -0
  24. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/load_quantized_data.py +0 -0
  25. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/quantize_by_range.py +0 -0
  26. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/SOURCES.txt +0 -0
  27. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
  28. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
  29. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/requires.txt +0 -0
  30. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/top_level.txt +0 -0
  31. {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.28.2
3
+ Version: 2.29.1
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -218,8 +218,8 @@ def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
218
218
  class QrackAceBackend:
219
219
  """A back end for elided quantum error correction
220
220
 
221
- This back end uses elided repetition code on a nearest-neighbor topology to emulate
222
- a utility-scale superconducting chip quantum computer in very little memory.4
221
+ This back end uses elided repetition code on a nearest-neighbor topology (but fully-connected
222
+ within "patches") to emulate a utility-scale superconducting chip quantum computer in very little memory.
223
223
 
224
224
  The backend was originally designed assuming an (orbifolded) 2D qubit grid like 2019 Sycamore.
225
225
  However, it quickly became apparent that users can basically design their own connectivity topologies,
@@ -229,6 +229,17 @@ class QrackAceBackend:
229
229
  (If you have 3+ patches, maybe your discrete GPU can do multiple patches in the time it takes an Intel HD
230
230
  to do one patch worth of work!)
231
231
 
232
+ Raw state-vector qubits can be directly manipulated through "sim" and "qubits" properties (with boundary
233
+ "tie-breaker" local hidden variables qubits in "lhv"). "sim" entries in the "qubits" map expose the full
234
+ QrackSimulator gate API.
235
+
236
+ For irregularly shaped "cutout geometries," allocate a super set of the geometric layout with this class,
237
+ and simply do not use unneeded qubits (and strike them from the Qiskit coupling map, for transpilation).
238
+ Qrack will not allocate space for any qubit that never actually becomes involved in any entangling gate.
239
+
240
+ For expected XEB across different 2D nearest-neighbor experiments, see the data set, regression model,
241
+ and calculator at https://tinyurl.com/qrackacexeb.
242
+
232
243
  Attributes:
233
244
  sim(QrackSimulator): Array of simulators corresponding to "patches" between boundary rows.
234
245
  long_range_columns(int): How many ideal rows between QEC boundary rows?
@@ -306,7 +317,7 @@ class QrackAceBackend:
306
317
  # between "more separable" (rotation toward consensus is real,
307
318
  # useful reconciliation) and "more entangled" (rotation only
308
319
  # scrambles a real correlation with another logical qubit's
309
- # replica -- see _correct). Default is the point where a single
320
+ # replica -- see correct). Default is the point where a single
310
321
  # qubit's linear entropy is half its maximum.
311
322
  self._rot_epsilon = (1.0 - 1.0 / math.sqrt(2)) / 2
312
323
 
@@ -329,7 +340,7 @@ class QrackAceBackend:
329
340
  # rather than surgically suppressing the wrap step further
330
341
  # down (which was tried and reverted -- it produced replica
331
342
  # counts, e.g. 2, that the rest of the class was never built
332
- # to handle, via _get_qb_lhv_indices/_correct, which assume
343
+ # to handle, via _get_qb_lhv_indices/correct, which assume
333
344
  # counts of 1, 3, or 5 only), means every code path that fires
334
345
  # for is_torus=False is one that already exists and is already
335
346
  # tested for is_torus=True; only WHICH positions take which
@@ -360,17 +371,17 @@ class QrackAceBackend:
360
371
  # grows with total boundary-qubit count rather than patch count,
361
372
  # so it doesn't scale -- off by default, and worth turning on only
362
373
  # for a handful of patches. Every boundary qubit ALSO still gets
363
- # its classical LHVQubit proxy (self._lhv, below) regardless, same
374
+ # its classical LHVQubit proxy (self.lhv, below) regardless, same
364
375
  # as when this option is off: with the crossbar on, the proxy
365
- # drops to being only the last-resort tie-breaker _correct() falls
376
+ # drops to being only the last-resort tie-breaker correct() falls
366
377
  # back to when even the crossbar replica is itself ambiguous; with
367
378
  # it off, the proxy is promoted to cover the crossbar's old
368
379
  # tie-breaking role directly.
369
380
  boundary_sim_id = sim_count
370
381
  boundary_count = 0
371
382
 
372
- self._qubits = []
373
- self._lhv = {}
383
+ self.qubits = []
384
+ self.lhv = {}
374
385
  sim_counts = [0] * sim_count
375
386
  sim_id = 0
376
387
  tot_qubits = 0
@@ -388,9 +399,9 @@ class QrackAceBackend:
388
399
  qubit.append((boundary_sim_id, boundary_count))
389
400
  boundary_count += 1
390
401
 
391
- if (not to_clone) or (tot_qubits in to_clone._lhv):
392
- self._lhv[tot_qubits] = LHVQubit(
393
- to_clone=(to_clone._lhv[tot_qubits] if to_clone else None)
402
+ if (not to_clone) or (tot_qubits in to_clone.lhv):
403
+ self.lhv[tot_qubits] = LHVQubit(
404
+ to_clone=(to_clone.lhv[tot_qubits] if to_clone else None)
394
405
  )
395
406
 
396
407
  if (not c) and (not r):
@@ -405,8 +416,8 @@ class QrackAceBackend:
405
416
  if not c:
406
417
  sim_id = (sim_id + 1) % sim_count
407
418
 
408
- self._qubits.append(
409
- list(to_clone._qubits[tot_qubits]) if to_clone else qubit
419
+ self.qubits.append(
420
+ list(to_clone.qubits[tot_qubits]) if to_clone else qubit
410
421
  )
411
422
  tot_qubits += 1
412
423
 
@@ -430,10 +441,10 @@ class QrackAceBackend:
430
441
  # See _apply_coupling for the actual gadget; this block only does
431
442
  # the qubit-index bookkeeping, in the same style as every other
432
443
  # per-simulator allocation above.
433
- self._detect_ancilla = []
444
+ detect_ancilla = []
434
445
  if self.is_error_detection:
435
446
  for i in range(len(sim_counts)):
436
- self._detect_ancilla.append(sim_counts[i])
447
+ detect_ancilla.append(sim_counts[i])
437
448
  sim_counts[i] += 1
438
449
 
439
450
  # Logical-qubit wrapper around each patch's detection ancilla,
@@ -441,25 +452,25 @@ class QrackAceBackend:
441
452
  # (self.cx(lq1, anc)), not just raw (sim, idx) tuples. Appended
442
453
  # PAST the user-facing qubit range (num_qubits() is computed
443
454
  # independently from the grid dimensions, not from len(
444
- # self._qubits), so these extra entries are never visible or
455
+ # self.qubits), so these extra entries are never visible or
445
456
  # reachable through the normal API) -- a simple, single-replica
446
457
  # (bulk-style) logical qubit per simulator, one entry in
447
- # self._qubits pointing at the same physical qubit _detect_
458
+ # self.qubits pointing at the same physical qubit _detect_
448
459
  # ancilla already allocated there. See _apply_coupling for why
449
460
  # this needs to be a genuine logical qubit and not just a raw
450
461
  # physical capture: capturing lq1's LOGICAL value via a real,
451
- # nested cx(lq1, anc) call goes through the full _correct()/
462
+ # nested cx(lq1, anc) call goes through the full correct()/
452
463
  # shadow-coupling machinery robustly, the same way any other
453
464
  # logical qubit's value would be determined -- watching one
454
465
  # specific physical replica directly (an earlier version of
455
- # this) isn't a valid invariant, since _correct() can
466
+ # this) isn't a valid invariant, since correct() can
456
467
  # legitimately need to change any single replica's own value.
457
- self._detect_ancilla_lq = []
468
+ self.detect_ancilla_lq = []
458
469
  if self.is_error_detection:
459
- for sim_id, phys_idx in enumerate(self._detect_ancilla):
460
- lq_idx = len(self._qubits)
461
- self._qubits.append([(sim_id, phys_idx)])
462
- self._detect_ancilla_lq.append(lq_idx)
470
+ for sim_id, phys_idx in enumerate(detect_ancilla):
471
+ lq_idx = len(self.qubits)
472
+ self.qubits.append([(sim_id, phys_idx)])
473
+ self.detect_ancilla_lq.append(lq_idx)
463
474
  # Boundary repetition code, on-demand design: only the couplers
464
475
  # are actually noisy here -- single-qubit gates are already
465
476
  # exactly transversal, per-replica, with zero error, so there's
@@ -473,18 +484,18 @@ class QrackAceBackend:
473
484
  # See _cpauli for the actual encode/couple/decode/correct cycle
474
485
  # built around these; this block is just the qubit-index
475
486
  # bookkeeping, in the same style as _detect_ancilla above.
476
- self._rep_code_ancilla = []
487
+ rep_code_ancilla = []
477
488
  if self.is_boundary_repetition_code:
478
489
  for i in range(len(sim_counts)):
479
- self._rep_code_ancilla.append(sim_counts[i])
490
+ rep_code_ancilla.append(sim_counts[i])
480
491
  sim_counts[i] += 1
481
492
 
482
- self._rep_code_ancilla_lq = []
493
+ self.rep_code_ancilla_lq = []
483
494
  if self.is_boundary_repetition_code:
484
- for sim_id, phys_idx in enumerate(self._rep_code_ancilla):
485
- lq_idx = len(self._qubits)
486
- self._qubits.append([(sim_id, phys_idx)])
487
- self._rep_code_ancilla_lq.append(lq_idx)
495
+ for sim_id, phys_idx in enumerate(rep_code_ancilla):
496
+ lq_idx = len(self.qubits)
497
+ self.qubits.append([(sim_id, phys_idx)])
498
+ self.rep_code_ancilla_lq.append(lq_idx)
488
499
 
489
500
  # capture calls (see _apply_coupling): those calls go through
490
501
  # the ordinary _cpauli -> _apply_coupling path themselves, which
@@ -772,7 +783,7 @@ class QrackAceBackend:
772
783
  self._cx_shadow(c1, c2)
773
784
 
774
785
  def _unpack(self, lq):
775
- return self._qubits[lq]
786
+ return self.qubits[lq]
776
787
 
777
788
  @staticmethod
778
789
  def _get_qb_lhv_indices(hq):
@@ -783,7 +794,7 @@ class QrackAceBackend:
783
794
  # diagonal-patch); with use_crossbars=True, 3 (home + adjacent-
784
795
  # patch + shared crossbar) or 5 (home + adjacent-patch + shared
785
796
  # crossbar + 2 diagonal-patch). None of them are virtual/LHV-
786
- # backed slots -- the classical LHVQubit proxy (self._lhv) is
797
+ # backed slots -- the classical LHVQubit proxy (self.lhv) is
787
798
  # tracked entirely separately from hq, not as one of its indices
788
799
  # -- so qb is simply every index, in order, regardless of which
789
800
  # of those counts applies. We keep this helper's name and
@@ -866,7 +877,7 @@ class QrackAceBackend:
866
877
 
867
878
  sim.mtrx([m00, m01, m10, m11])
868
879
 
869
- def _correct(self, lq, phase=False, skip_rotation=False):
880
+ def correct(self, lq, phase=False, skip_rotation=False):
870
881
  hq = self._unpack(lq)
871
882
 
872
883
  if len(hq) == 1:
@@ -878,8 +889,8 @@ class QrackAceBackend:
878
889
  for q in qb:
879
890
  b = hq[q]
880
891
  self.sim[b[0]].h(b[1])
881
- if lq in self._lhv:
882
- self._lhv[lq].h()
892
+ if lq in self.lhv:
893
+ self.lhv[lq].h()
883
894
 
884
895
  if len(hq) == 4:
885
896
  # Corner-boundary qubit: 4 real replicas remain (home,
@@ -894,7 +905,7 @@ class QrackAceBackend:
894
905
  p1 = self.sim[hq[1][0]].prob(hq[1][1])
895
906
  p2 = self.sim[hq[2][0]].prob(hq[2][1])
896
907
  p3 = self.sim[hq[3][0]].prob(hq[3][1])
897
- lhv = self._lhv.get(lq)
908
+ lhv = self.lhv.get(lq)
898
909
 
899
910
  end_caps = [p0, p1, p2, p3]
900
911
  # Classify each end-cap as decisively high / decisively low /
@@ -1010,7 +1021,7 @@ class QrackAceBackend:
1010
1021
  p2 = self.sim[hq[2][0]].prob(hq[2][1])
1011
1022
  p3 = self.sim[hq[3][0]].prob(hq[3][1])
1012
1023
  p4 = self.sim[hq[4][0]].prob(hq[4][1])
1013
- lhv = self._lhv.get(lq)
1024
+ lhv = self.lhv.get(lq)
1014
1025
 
1015
1026
  end_caps = [p0, p1, p3, p4]
1016
1027
  high_count = sum(1 for x in end_caps if x > (0.5 + self._epsilon))
@@ -1076,7 +1087,7 @@ class QrackAceBackend:
1076
1087
  p0 = self.sim[hq[0][0]].prob(hq[0][1])
1077
1088
  p1 = self.sim[hq[1][0]].prob(hq[1][1])
1078
1089
  p2 = self.sim[hq[2][0]].prob(hq[2][1])
1079
- lhv = self._lhv.get(lq)
1090
+ lhv = self.lhv.get(lq)
1080
1091
 
1081
1092
  p1_high = p1 > (0.5 + self._epsilon)
1082
1093
  p1_low = p1 < (0.5 - self._epsilon)
@@ -1159,7 +1170,7 @@ class QrackAceBackend:
1159
1170
  # sit at old-index-2 is gone. The classical LHV proxy takes
1160
1171
  # over the crossbar's old role as "slot2" in the conditional
1161
1172
  # tie-breaking cascade below.
1162
- lhv = self._lhv.get(lq)
1173
+ lhv = self.lhv.get(lq)
1163
1174
  p0 = self.sim[hq[0][0]].prob(hq[0][1])
1164
1175
  p1 = self.sim[hq[1][0]].prob(hq[1][1])
1165
1176
 
@@ -1292,8 +1303,8 @@ class QrackAceBackend:
1292
1303
  for q in qb:
1293
1304
  b = hq[q]
1294
1305
  self.sim[b[0]].h(b[1])
1295
- if lq in self._lhv:
1296
- self._lhv[lq].h()
1306
+ if lq in self.lhv:
1307
+ self.lhv[lq].h()
1297
1308
 
1298
1309
  def apply_magnetic_bias(self, q, b):
1299
1310
  if b == 0:
@@ -1322,11 +1333,11 @@ class QrackAceBackend:
1322
1333
  b = hq[q]
1323
1334
  self.sim[b[0]].u(b[1], th, ph, lm)
1324
1335
 
1325
- lhv = self._lhv.get(lq)
1336
+ lhv = self.lhv.get(lq)
1326
1337
  if lhv is not None:
1327
1338
  lhv.u(th, ph, lm)
1328
1339
 
1329
- # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
1340
+ # Correction deferred to next 2-qubit gate (_cpauli calls correct)
1330
1341
 
1331
1342
  def r(self, p, th, lq):
1332
1343
  hq = self._unpack(lq)
@@ -1341,7 +1352,7 @@ class QrackAceBackend:
1341
1352
  b = hq[q]
1342
1353
  self.sim[b[0]].r(p, th, b[1])
1343
1354
 
1344
- lhv = self._lhv.get(lq)
1355
+ lhv = self.lhv.get(lq)
1345
1356
  if lhv is not None:
1346
1357
  if p == Pauli.PauliX:
1347
1358
  lhv.rx(th)
@@ -1350,7 +1361,7 @@ class QrackAceBackend:
1350
1361
  elif p == Pauli.PauliZ:
1351
1362
  lhv.rz(th)
1352
1363
 
1353
- # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
1364
+ # Correction deferred to next 2-qubit gate (_cpauli calls correct)
1354
1365
 
1355
1366
  def h(self, lq):
1356
1367
  hq = self._unpack(lq)
@@ -1365,11 +1376,11 @@ class QrackAceBackend:
1365
1376
  b = hq[q]
1366
1377
  self.sim[b[0]].h(b[1])
1367
1378
 
1368
- lhv = self._lhv.get(lq)
1379
+ lhv = self.lhv.get(lq)
1369
1380
  if lhv is not None:
1370
1381
  lhv.h()
1371
1382
 
1372
- # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
1383
+ # Correction deferred to next 2-qubit gate (_cpauli calls correct)
1373
1384
 
1374
1385
  def s(self, lq):
1375
1386
  hq = self._unpack(lq)
@@ -1384,7 +1395,7 @@ class QrackAceBackend:
1384
1395
  b = hq[q]
1385
1396
  self.sim[b[0]].s(b[1])
1386
1397
 
1387
- lhv = self._lhv.get(lq)
1398
+ lhv = self.lhv.get(lq)
1388
1399
  if lhv is not None:
1389
1400
  lhv.s()
1390
1401
 
@@ -1401,7 +1412,7 @@ class QrackAceBackend:
1401
1412
  b = hq[q]
1402
1413
  self.sim[b[0]].adjs(b[1])
1403
1414
 
1404
- lhv = self._lhv.get(lq)
1415
+ lhv = self.lhv.get(lq)
1405
1416
  if lhv is not None:
1406
1417
  lhv.adjs()
1407
1418
 
@@ -1418,7 +1429,7 @@ class QrackAceBackend:
1418
1429
  b = hq[q]
1419
1430
  self.sim[b[0]].sx(b[1])
1420
1431
 
1421
- lhv = self._lhv.get(lq)
1432
+ lhv = self.lhv.get(lq)
1422
1433
  if lhv is not None:
1423
1434
  lhv.sx()
1424
1435
 
@@ -1435,7 +1446,7 @@ class QrackAceBackend:
1435
1446
  b = hq[q]
1436
1447
  self.sim[b[0]].adjsx(b[1])
1437
1448
 
1438
- lhv = self._lhv.get(lq)
1449
+ lhv = self.lhv.get(lq)
1439
1450
  if lhv is not None:
1440
1451
  lhv.adjsx()
1441
1452
 
@@ -1452,7 +1463,7 @@ class QrackAceBackend:
1452
1463
  b = hq[q]
1453
1464
  self.sim[b[0]].x(b[1])
1454
1465
 
1455
- lhv = self._lhv.get(lq)
1466
+ lhv = self.lhv.get(lq)
1456
1467
  if lhv is not None:
1457
1468
  lhv.x()
1458
1469
 
@@ -1469,7 +1480,7 @@ class QrackAceBackend:
1469
1480
  b = hq[q]
1470
1481
  self.sim[b[0]].y(b[1])
1471
1482
 
1472
- lhv = self._lhv.get(lq)
1483
+ lhv = self.lhv.get(lq)
1473
1484
  if lhv is not None:
1474
1485
  lhv.y()
1475
1486
 
@@ -1486,7 +1497,7 @@ class QrackAceBackend:
1486
1497
  b = hq[q]
1487
1498
  self.sim[b[0]].z(b[1])
1488
1499
 
1489
- lhv = self._lhv.get(lq)
1500
+ lhv = self.lhv.get(lq)
1490
1501
  if lhv is not None:
1491
1502
  lhv.z()
1492
1503
 
@@ -1503,7 +1514,7 @@ class QrackAceBackend:
1503
1514
  b = hq[q]
1504
1515
  self.sim[b[0]].t(b[1])
1505
1516
 
1506
- lhv = self._lhv.get(lq)
1517
+ lhv = self.lhv.get(lq)
1507
1518
  if lhv is not None:
1508
1519
  lhv.t()
1509
1520
 
@@ -1520,7 +1531,7 @@ class QrackAceBackend:
1520
1531
  b = hq[q]
1521
1532
  self.sim[b[0]].adjt(b[1])
1522
1533
 
1523
- lhv = self._lhv.get(lq)
1534
+ lhv = self.lhv.get(lq)
1524
1535
  if lhv is not None:
1525
1536
  lhv.adjt()
1526
1537
 
@@ -1625,7 +1636,7 @@ class QrackAceBackend:
1625
1636
  if x[0] in t_sims:
1626
1637
  anc_sim = x[0]
1627
1638
  break
1628
- anc = self._detect_ancilla_lq[anc_sim]
1639
+ anc = self.detect_ancilla_lq[anc_sim]
1629
1640
  self._in_gadget_capture = True
1630
1641
  # XOR on target
1631
1642
  self.cx(lq2, anc)
@@ -1636,8 +1647,8 @@ class QrackAceBackend:
1636
1647
  self._in_gadget_capture = False
1637
1648
 
1638
1649
  if not self._in_gadget_capture:
1639
- self._correct(lq1)
1640
- self._correct(lq2)
1650
+ self.correct(lq1)
1651
+ self.correct(lq2)
1641
1652
 
1642
1653
  # A target replica (q2) that shares a simulator with ANY control
1643
1654
  # replica in qb1 is going to receive a REAL, exact gate from that
@@ -1651,7 +1662,7 @@ class QrackAceBackend:
1651
1662
  # invert the correlation sign the real CNOT is about to
1652
1663
  # establish. Confirmed directly: for a boundary-to-boundary
1653
1664
  # coupling, measuring the raw physical replica pairs that DO
1654
- # share a simulator (bypassing all of _correct/m's classical
1665
+ # share a simulator (bypassing all of correct/m's classical
1655
1666
  # reconciliation, and with is_error_detection off) showed near-
1656
1667
  # perfect agreement for the one pair untouched by any earlier
1657
1668
  # shadow write, but only ~50-60% agreement (chance level) for
@@ -1681,7 +1692,7 @@ class QrackAceBackend:
1681
1692
  # `(len(qb1) == 2) and (b1[1] == (b2[1] & 1))` as a
1682
1693
  # parity-matched fallback. _get_qb_lhv_indices only ever
1683
1694
  # returns length 1, 3, or 5 (bulk / edge-boundary /
1684
- # corner-boundary), matching self._qubits' own replica
1695
+ # corner-boundary), matching self.qubits' own replica
1685
1696
  # counts by construction -- len(qb1) == 2 is therefore
1686
1697
  # unreachable, and that branch was permanently dead code
1687
1698
  # (harmless, but confusing). Removed rather than kept:
@@ -1693,15 +1704,15 @@ class QrackAceBackend:
1693
1704
 
1694
1705
  if not self._in_gadget_capture:
1695
1706
  if pauli != Pauli.PauliZ:
1696
- self._correct(lq2, False, pauli != Pauli.PauliX)
1707
+ self.correct(lq2, False, pauli != Pauli.PauliX)
1697
1708
  if pauli != Pauli.PauliX:
1698
- self._correct(lq2, True)
1709
+ self.correct(lq2, True)
1699
1710
 
1700
1711
  if anc is not None:
1701
1712
  self._in_gadget_capture = True
1702
1713
  # XOR check
1703
1714
  self.cx(lq2, anc)
1704
- anc_sim, anc_idx = self._qubits[anc][0]
1715
+ anc_sim, anc_idx = self.qubits[anc][0]
1705
1716
  p = self.sim[anc_sim].prob(anc_idx)
1706
1717
  p = 1.0 - p
1707
1718
  if self._ps_epsilon >= p:
@@ -1753,7 +1764,7 @@ class QrackAceBackend:
1753
1764
  if x[0] in t_sims:
1754
1765
  anc_sim = x[0]
1755
1766
  break
1756
- anc = self._rep_code_ancilla_lq[anc_sim]
1767
+ anc = self.rep_code_ancilla_lq[anc_sim]
1757
1768
  self._in_gadget_capture = True
1758
1769
  self.cx(lq2, anc)
1759
1770
  self._in_gadget_capture = False
@@ -1769,9 +1780,9 @@ class QrackAceBackend:
1769
1780
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(h2)
1770
1781
  self._apply_coupling(pauli, anti, qb1, hq1, qb2, h2, lq1_lr, lq1, l2)
1771
1782
 
1772
- if lq2 in self._lhv:
1783
+ if lq2 in self.lhv:
1773
1784
  ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
1774
- _cpauli_lhv(ctrl_prob, self._lhv[lq2], pauli, anti)
1785
+ _cpauli_lhv(ctrl_prob, self.lhv[lq2], pauli, anti)
1775
1786
 
1776
1787
  if anc is not None:
1777
1788
  # Decode:
@@ -1782,7 +1793,7 @@ class QrackAceBackend:
1782
1793
  # After we corrected any mismatch above, it's unlikely
1783
1794
  # that we can't post-select no-error on this ancilla.
1784
1795
 
1785
- anc_sim, anc_idx = self._qubits[anc][0]
1796
+ anc_sim, anc_idx = self.qubits[anc][0]
1786
1797
  p = self.sim[anc_sim].prob(anc_idx)
1787
1798
  if self._ps_epsilon >= (1.0 - p):
1788
1799
  b = self.m(anc)
@@ -2011,10 +2022,10 @@ class QrackAceBackend:
2011
2022
  # decomposition for SDRP to truncate. This is the case the noise
2012
2023
  # model's common-fraction term assumes is error-free.
2013
2024
 
2014
- # _cpauli normally wraps every coupling gate with _correct() before
2025
+ # _cpauli normally wraps every coupling gate with correct() before
2015
2026
  # and after, to reconcile multi-replica consensus. This method
2016
2027
  # calls the underlying simulators (and _cx_shadow) directly,
2017
- # bypassing that wrapper entirely -- _correct() is a no-op for
2028
+ # bypassing that wrapper entirely -- correct() is a no-op for
2018
2029
  # single-replica (bulk) qubits, so this is always safe to call, and
2019
2030
  # only actually matters (and was being silently skipped) whenever
2020
2031
  # either side is a multi-replica boundary qubit.
@@ -2048,24 +2059,24 @@ class QrackAceBackend:
2048
2059
 
2049
2060
  if sims1.isdisjoint(sims2):
2050
2061
  # Once fully resolved, the swap itself is nothing but a
2051
- # reference exchange: self._qubits[lq] is a reference to the
2062
+ # reference exchange: self.qubits[lq] is a reference to the
2052
2063
  # replica-list object, so swapping which index points to
2053
2064
  # which object IS the swap -- no separate content to copy.
2054
- self._qubits[lq1], self._qubits[lq2] = self._qubits[lq2], self._qubits[lq1]
2065
+ self.qubits[lq1], self.qubits[lq2] = self.qubits[lq2], self.qubits[lq1]
2055
2066
 
2056
- lhv1 = self._lhv.pop(lq1, None)
2057
- lhv2 = self._lhv.pop(lq2, None)
2067
+ lhv1 = self.lhv.pop(lq1, None)
2068
+ lhv2 = self.lhv.pop(lq2, None)
2058
2069
  if lhv2 is not None:
2059
- self._lhv[lq1] = lhv2
2070
+ self.lhv[lq1] = lhv2
2060
2071
  if lhv1 is not None:
2061
- self._lhv[lq2] = lhv1
2072
+ self.lhv[lq2] = lhv1
2062
2073
 
2063
2074
  # Boundary repetition code needs no bookkeeping here, unlike
2064
- # self._qubits/self._lhv just above: it's on-demand now, not
2075
+ # self.qubits/self.lhv just above: it's on-demand now, not
2065
2076
  # a permanent, per-logical-index dict of pre-allocated
2066
- # partners -- eligibility is just len(self._qubits[lq]) > 1,
2077
+ # partners -- eligibility is just len(self.qubits[lq]) > 1,
2067
2078
  # computed fresh on every coupling event, so the reference
2068
- # swap of self._qubits above already, automatically carries
2079
+ # swap of self.qubits above already, automatically carries
2069
2080
  # the right eligibility with it. Nothing else to move.
2070
2081
  return
2071
2082
 
@@ -2081,8 +2092,8 @@ class QrackAceBackend:
2081
2092
  self.cx(lq2, lq1)
2082
2093
  return
2083
2094
 
2084
- self._correct(lq1)
2085
- self._correct(lq2)
2095
+ self.correct(lq1)
2096
+ self.correct(lq2)
2086
2097
 
2087
2098
  # Partial-match cases: one side is a simple (single-replica) qubit,
2088
2099
  # the other has multiple replicas. For the multi-replica side's
@@ -2109,12 +2120,12 @@ class QrackAceBackend:
2109
2120
  for _hq2 in non_matching:
2110
2121
  self._cx_shadow(_hq1, _hq2)
2111
2122
  # No correction on lq1 (bulk, single-replica): already a
2112
- # structural no-op via _correct's own len(hq)==1 guard.
2123
+ # structural no-op via correct's own len(hq)==1 guard.
2113
2124
  # lq2 (boundary) DOES take real error here (X-type, verified),
2114
2125
  # so it keeps a correction -- phase=False, not True, since the
2115
2126
  # error is already Z-basis-population-visible and doesn't need
2116
2127
  # an H-rotation first to become detectable.
2117
- self._correct(lq2)
2128
+ self.correct(lq2)
2118
2129
  elif len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
2119
2130
  _hq2 = hq2[0]
2120
2131
  non_matching = [h for h in hq1 if h[0] != _hq2[0]]
@@ -2125,14 +2136,14 @@ class QrackAceBackend:
2125
2136
  self.sim[_hq2[0]].swap(_hq2[1], _hq1[1])
2126
2137
  for _hq1 in non_matching:
2127
2138
  self._cx_shadow(_hq2, _hq1)
2128
- self._correct(lq1)
2139
+ self.correct(lq1)
2129
2140
  else:
2130
2141
  # General case: no shared simulator at all (e.g. two simple
2131
2142
  # qubits on entirely different sims -- nothing to anchor an exact
2132
2143
  # swap on, verified the sandwich branches above give WRONG,
2133
2144
  # deterministic results here: 100/100 failures vs 0/15 for this
2134
2145
  # fallback), or both sides multi-replica and not fully matched:
2135
- # cx() already wraps itself with _correct() via _cpauli, so no
2146
+ # cx() already wraps itself with correct() via _cpauli, so no
2136
2147
  # extra wrapping needed here.
2137
2148
  self.cx(lq1, lq2)
2138
2149
  self.cx(lq2, lq1)
@@ -2159,65 +2170,6 @@ class QrackAceBackend:
2159
2170
  self.mcx([c1, c2], t)
2160
2171
  return
2161
2172
 
2162
- # AND-invariant, generalized from _apply_coupling's CX/CZ XOR
2163
- # check to the Toffoli's actual relationship: t should end up as
2164
- # t_before XOR (c1 AND c2), not just t_before XOR c1 or XOR c2
2165
- # individually. mcx is the native AND-conditioned flip, so it
2166
- # plays the same role here that a plain cx(control, anc) played
2167
- # in the 2-qubit case: capture t's pre-gate value, then fold in
2168
- # c1 AND c2 via a genuine nested mcx() (which goes through the
2169
- # same shadow/_correct machinery as any other logical gate, so
2170
- # this is exposed to the same class of intrinsic approximation
2171
- # error the CX/CZ check catches, not a raw single-replica read).
2172
- # Uses t's own patch ancilla2 array, since t itself is guaranteed
2173
- # bulk on this path (the len(hqt) > 1 case returns earlier,
2174
- # above) and nothing else in this method uses that array anymore.
2175
- anc_and = None
2176
- if self.is_error_detection and not self._in_gadget_capture and ((len(hqt) > 1) or (len(hq1) > 1) or (len(hq2) > 1)):
2177
- found = False
2178
- anc_sim = hq1[0][0]
2179
- t_sims = {y[0] for y in hqt if y[0] >= 0}
2180
- for x in hq1:
2181
- if (x[0] < 0) or (x[0] not in t_sims):
2182
- continue
2183
- anc_sim = x[0]
2184
- for y in hq2:
2185
- if (y[0] < 0) or (y[0] not in t_sims):
2186
- continue
2187
- if anc_sim == y[0]:
2188
- found = True
2189
- break
2190
- if found:
2191
- break
2192
- self._in_gadget_capture = True
2193
- anc_and = self._detect_ancilla_lq[anc_sim]
2194
- self.cx(t, anc_and)
2195
- # self.mcx() only supports a single control at the logical
2196
- # level (a syntax-convenience wrapper, confirmed directly --
2197
- # it raises for 2 controls), so it can't do the AND capture
2198
- # itself. When c1 and c2 both happen to have a physical
2199
- # replica in t's own home-patch simulator (the same one
2200
- # anc_and lives in), use a genuine native mcx there directly
2201
- # -- exact, and a clean, independent reference to compare the
2202
- # real Toffoli against. Otherwise, fall back to a recursive
2203
- # ccx(c1, c2, anc_and): safe from infinite recursion here
2204
- # specifically because _in_gadget_capture is already True, so
2205
- # the recursive call's own "not self._in_gadget_capture" gate
2206
- # skips setting up a second anc_and, and just performs the
2207
- # ordinary native-witness-or-shadow Toffoli computation on
2208
- # anc_and instead.
2209
- c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
2210
- c2_idx = next((r[1] for r in hq2 if r[0] == anc_sim), None)
2211
- if (c1_idx is not None) and (c2_idx is not None):
2212
- _, anc_and_idx = self._qubits[anc_and][0]
2213
- self.sim[anc_sim].mcx([c1_idx, c2_idx], anc_and_idx)
2214
- else:
2215
- self.ccx(c1, c2, anc_and)
2216
- self._in_gadget_capture = False
2217
-
2218
- self._correct(c1)
2219
- self._correct(c2)
2220
-
2221
2173
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
2222
2174
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
2223
2175
  qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
@@ -2242,28 +2194,6 @@ class QrackAceBackend:
2242
2194
  witnesses.append(witness)
2243
2195
 
2244
2196
  if needs_shadow:
2245
- # Confirmed directly: whenever even one replica of t lacks a
2246
- # native witness shared with c1 and c2, the per-replica
2247
- # fallback to _ccx_shadow craters fidelity specifically when
2248
- # either control is in genuine superposition -- 0.64-0.68
2249
- # vs. 0.999+ for every other input tested, stable from 100 to
2250
- # 2500 shots, so not shot noise. Diverting the WHOLE
2251
- # operation (not just the unwitnessed replica) to the general
2252
- # 6-CX decomposition instead: every step here is a genuine
2253
- # logical cx()/h()/t()/adjt() call, which updates every
2254
- # replica of t through the same _apply_coupling boundary-
2255
- # crossing machinery already validated elsewhere in this
2256
- # file, rather than the lower-level per-replica _cx_shadow
2257
- # calls _ccx_shadow uses for just the unwitnessed ones.
2258
- #
2259
- # Wrapped in _in_gadget_capture, same as established earlier:
2260
- # without it, each individual cx() here would independently
2261
- # trigger its own XOR-invariant gadget setup, which corrupts
2262
- # the precise T/Tdg phase relationships this decomposition
2263
- # depends on (confirmed directly, the first time this
2264
- # decomposition was used unconditionally -- corrupted even
2265
- # c1/c2, which the per-replica path never touched at all).
2266
- self._in_gadget_capture = True
2267
2197
  self.h(t)
2268
2198
  self.cx(c2, t)
2269
2199
  self.adjt(t)
@@ -2279,29 +2209,14 @@ class QrackAceBackend:
2279
2209
  self.t(c1)
2280
2210
  self.adjt(c2)
2281
2211
  self.cx(c1, c2)
2282
- self._in_gadget_capture = False
2283
2212
  else:
2213
+ self.correct(c1)
2214
+ self.correct(c2)
2215
+
2284
2216
  for (b1, b2, bt) in witnesses:
2285
2217
  self.sim[b1[0]].mcx([b1[1], b2[1]], bt[1])
2286
2218
 
2287
- self._correct(t)
2288
-
2289
- self._in_gadget_capture = True
2290
-
2291
- if anc_and is not None:
2292
- self.cx(t, anc_and)
2293
- anc_sim, anc_idx = self._qubits[anc_and][0]
2294
- p = self.sim[anc_sim].prob(anc_idx)
2295
- p = 1.0 - p
2296
- if self._ps_epsilon >= p:
2297
- b = self.m(anc_and)
2298
- else:
2299
- b = self.force_m(anc_and, False)
2300
- if b:
2301
- self.x(anc_and)
2302
- self.x(t)
2303
-
2304
- self._in_gadget_capture = False
2219
+ self.correct(t)
2305
2220
 
2306
2221
  def ccz(self, c1, c2, t):
2307
2222
  self.h(t)
@@ -2319,9 +2234,9 @@ class QrackAceBackend:
2319
2234
  b = hq[0]
2320
2235
  return self.sim[b[0]].prob(b[1])
2321
2236
 
2322
- self._correct(lq)
2237
+ self.correct(lq)
2323
2238
  # The real replicas are already mutually consistent here (the
2324
- # _correct() call above already ran the appropriate tie-breaking
2239
+ # correct() call above already ran the appropriate tie-breaking
2325
2240
  # cascade for however many replicas hq has -- 2, 3, 4, or 5,
2326
2241
  # depending on self.use_crossbars and whether this is an edge or
2327
2242
  # corner boundary qubit -- using the LHV proxy and/or the
@@ -2330,7 +2245,7 @@ class QrackAceBackend:
2330
2245
  # correct, already-decided answer, regardless of replica count:
2331
2246
  # re-weighting any particular replica specially here would
2332
2247
  # reintroduce the same center-dragging distortion the cascade in
2333
- # _correct() was built to avoid.
2248
+ # correct() was built to avoid.
2334
2249
  p = [self.sim[b[0]].prob(b[1]) for b in hq]
2335
2250
  n = len(p)
2336
2251
  prms = math.sqrt(sum(x**2 for x in p) / n)
@@ -2358,7 +2273,7 @@ class QrackAceBackend:
2358
2273
  else:
2359
2274
  self.sim[b[0]].force_m(b[1], result)
2360
2275
 
2361
- lhv = self._lhv.get(lq)
2276
+ lhv = self.lhv.get(lq)
2362
2277
  if lhv is not None:
2363
2278
  lhv.reset()
2364
2279
  if result:
@@ -2372,7 +2287,7 @@ class QrackAceBackend:
2372
2287
  b = hq[0]
2373
2288
  return self.sim[b[0]].force_m(b[1], result)
2374
2289
 
2375
- self._correct(lq)
2290
+ self.correct(lq)
2376
2291
 
2377
2292
  qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
2378
2293
 
@@ -2385,7 +2300,7 @@ class QrackAceBackend:
2385
2300
  else:
2386
2301
  self.sim[b[0]].force_m(b[1], result)
2387
2302
 
2388
- lhv = self._lhv.get(lq)
2303
+ lhv = self.lhv.get(lq)
2389
2304
  if lhv is not None:
2390
2305
  lhv.reset()
2391
2306
  if result:
@@ -2782,7 +2697,7 @@ class QrackAceBackend:
2782
2697
  # keep patching individual arithmetic mistakes in a function whose
2783
2698
  # intended row/col semantics were genuinely unclear on inspection,
2784
2699
  # this is grounded directly in the one thing that's true by
2785
- # construction: self._qubits, the actual replica structure built
2700
+ # construction: self.qubits, the actual replica structure built
2786
2701
  # during __init__. Two logical qubits are coupled if and only if
2787
2702
  # they share ANY simulator id among their own replicas -- this
2788
2703
  # cannot disagree with the real adjacency, because it doesn't
@@ -2791,7 +2706,7 @@ class QrackAceBackend:
2791
2706
  n = self.num_qubits()
2792
2707
  sim_to_qubits = {}
2793
2708
  for lq in range(n):
2794
- for sim_id, _ in self._qubits[lq]:
2709
+ for sim_id, _ in self.qubits[lq]:
2795
2710
  sim_to_qubits.setdefault(sim_id, []).append(lq)
2796
2711
  for sim_id, qubits_here in sim_to_qubits.items():
2797
2712
  for a in qubits_here:
@@ -2822,7 +2737,7 @@ class QrackAceBackend:
2822
2737
  # are untouched -- a bulk qubit's single simulator membership
2823
2738
  # was never ambiguous.
2824
2739
  def is_boundary(lq):
2825
- return len(self._qubits[lq]) > 1
2740
+ return len(self.qubits[lq]) > 1
2826
2741
 
2827
2742
  def same_single_side(idx_a, idx_b, long_range_arr, length):
2828
2743
  if idx_a == idx_b:
@@ -3008,8 +2923,8 @@ class QrackAceBackend:
3008
2923
  noise_model = NoiseModel()
3009
2924
 
3010
2925
  def _uncommon_sim_fraction(lq1, lq2):
3011
- sims1 = {qb[0] for qb in self._qubits[lq1]}
3012
- sims2 = {qb[0] for qb in self._qubits[lq2]}
2926
+ sims1 = {qb[0] for qb in self.qubits[lq1]}
2927
+ sims2 = {qb[0] for qb in self.qubits[lq2]}
3013
2928
 
3014
2929
  return len(sims1 ^ sims2) / len(sims1.union(sims2))
3015
2930
 
@@ -3022,7 +2937,7 @@ class QrackAceBackend:
3022
2937
  # _cpauli's own convention) to be a multi-replica (boundary)
3023
2938
  # qubit -- see _cpauli: anc1 (the control-invariance capture) is
3024
2939
  # unconditional but structurally trivial for a single-replica
3025
- # control (_correct() is already a no-op there, nothing for the
2940
+ # control (correct() is already a no-op there, nothing for the
3026
2941
  # gadget to protect against), and anc2 (the target-value
3027
2942
  # verification against the CX/ACX-predicted output) is
3028
2943
  # explicitly gated on len(hq1) > 1. Both halves are gated the
@@ -3039,7 +2954,7 @@ class QrackAceBackend:
3039
2954
  # nested cx(lq1, anc) capture goes through the full logical
3040
2955
  # coupling machinery (shadow or real, whichever applies) and
3041
2956
  # works the same way regardless. So this is checked directly,
3042
- # per pair, from len(self._qubits[a]) -- no precomputed set
2957
+ # per pair, from len(self.qubits[a]) -- no precomputed set
3043
2958
  # needed.
3044
2959
 
3045
2960
  # This factor is a theory-derived placeholder, not an empirical
@@ -3085,7 +3000,7 @@ class QrackAceBackend:
3085
3000
  # Pauli-string convention is big-endian relative to the qubit
3086
3001
  # list: the LAST qubit corresponds to the LEFTMOST character).
3087
3002
  p_shadow_ccz = 1 - sp * p
3088
- if self.is_error_detection and len(self._qubits[a]) > 1:
3003
+ if self.is_error_detection and len(self.qubits[a]) > 1:
3089
3004
  # a is the control for this pair (_cpauli(lq1=a, lq2=b,
3090
3005
  # ...) is always called with a as the coupling source).
3091
3006
  # Both gadget halves that protect this pair (anc1 on a,
@@ -3119,13 +3034,13 @@ class QrackAceBackend:
3119
3034
  # Error type re-verified directly against THIS file (the
3120
3035
  # gadget mechanics changed substantially since the type was
3121
3036
  # first derived -- swap()'s own cross-check gadget, the
3122
- # _correct()-skip-during-capture fix, and the post-selection
3037
+ # correct()-skip-during-capture fix, and the post-selection
3123
3038
  # floor threshold are all new), to make sure none of that
3124
3039
  # changed the underlying picture. It hasn't: _cx_shadow
3125
3040
  # itself is untouched by any of it. Reading the LOGICAL
3126
3041
  # prob(lq) still tells you nothing (0.00000 deviation in
3127
3042
  # both bases on a single isolated swap, gadget on or off --
3128
- # it calls _correct() internally and reconciles across every
3043
+ # it calls correct() internally and reconciles across every
3129
3044
  # replica, the same masking this file has run into more than
3130
3045
  # once). Reading the RAW, non-matching replica directly
3131
3046
  # (bypassing that reconciliation) gives a clean, deterministic
@@ -3160,12 +3075,12 @@ class QrackAceBackend:
3160
3075
  # error character isn't captured by this specific derivation;
3161
3076
  # that case keeps the previous symmetric depolarizing
3162
3077
  # treatment below as a fallback.
3163
- len_a = len(self._qubits[a])
3164
- len_b = len(self._qubits[b])
3078
+ len_a = len(self.qubits[a])
3079
+ len_b = len(self.qubits[b])
3165
3080
  is_a_simple = len_a == 1
3166
3081
  is_b_simple = len_b == 1
3167
- sims_a = [qb[0] for qb in self._qubits[a]]
3168
- sims_b = [qb[0] for qb in self._qubits[b]]
3082
+ sims_a = [qb[0] for qb in self.qubits[a]]
3083
+ sims_b = [qb[0] for qb in self.qubits[b]]
3169
3084
  has_match = any(s in sims_b for s in sims_a)
3170
3085
 
3171
3086
  if (is_a_simple != is_b_simple) and has_match:
@@ -3213,7 +3128,7 @@ class QrackAceBackend:
3213
3128
  # 4-term (I,X,Z,Y) channel down to a plain 2-term (I,Z) one.
3214
3129
  if (is_a_simple != is_b_simple) and has_match:
3215
3130
  p_cz = 1 - sp * p # same per-call formula as standalone cz
3216
- if self.is_error_detection and len(self._qubits[a]) > 1:
3131
+ if self.is_error_detection and len(self.qubits[a]) > 1:
3217
3132
  p_cz *= d
3218
3133
  p_net_iswap = p_net_swap * (1 - p_cz) + p_cz * (1 - p_net_swap)
3219
3134
  if is_a_simple:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.28.2
3
+ Version: 2.29.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.28.2"
10
+ VERSION = "2.29.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')