pyqrack-cpu-complex128 2.26.0__tar.gz → 2.26.6__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.26.0/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.26.6}/PKG-INFO +1 -1
  2. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_ace_backend.py +114 -194
  3. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/setup.py +1 -1
  5. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/LICENSE +0 -0
  6. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/MANIFEST.in +0 -0
  7. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/Makefile +0 -0
  8. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/README.md +0 -0
  9. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyproject.toml +0 -0
  10. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/__init__.py +0 -0
  11. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/pauli.py +0 -0
  13. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  15. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_neuron.py +0 -0
  16. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  17. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_simulator.py +0 -0
  18. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.26.0 → pyqrack_cpu_complex128-2.26.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.26.0
3
+ Version: 2.26.6
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -385,20 +385,17 @@ class QrackAceBackend:
385
385
  tot_qubits += 1
386
386
 
387
387
  # Error-detection gadget (IBM-style detect-and-post-select, not
388
- # correction): two shared ancillae per simulator -- one per patch,
388
+ # correction): one shared ancilla per simulator -- one per patch,
389
389
  # "a single ancilla per patch, overall" -- reused across every real
390
390
  # (same-simulator) coupling
391
391
  # gate that touches it, never allocated per-gate or per-qubit.
392
392
  # See _apply_coupling for the actual gadget; this block only does
393
393
  # the qubit-index bookkeeping, in the same style as every other
394
394
  # per-simulator allocation above.
395
- self._detect_ancilla1 = []
396
- self._detect_ancilla2 = []
395
+ self._detect_ancilla = []
397
396
  if self.is_error_detection:
398
397
  for i in range(len(sim_counts)):
399
- self._detect_ancilla1.append(sim_counts[i])
400
- sim_counts[i] += 1
401
- self._detect_ancilla2.append(sim_counts[i])
398
+ self._detect_ancilla.append(sim_counts[i])
402
399
  sim_counts[i] += 1
403
400
 
404
401
  # Logical-qubit wrapper around each patch's detection ancilla,
@@ -419,30 +416,25 @@ class QrackAceBackend:
419
416
  # specific physical replica directly (an earlier version of
420
417
  # this) isn't a valid invariant, since _correct() can
421
418
  # legitimately need to change any single replica's own value.
422
- self._detect_ancilla1_lq = []
423
- self._detect_ancilla2_lq = []
419
+ self._detect_ancilla_lq = []
424
420
  if self.is_error_detection:
425
- for sim_id, phys_idx in enumerate(self._detect_ancilla1):
426
- lq_idx = len(self._qubits)
427
- self._qubits.append([(sim_id, phys_idx)])
428
- self._detect_ancilla1_lq.append(lq_idx)
429
- for sim_id, phys_idx in enumerate(self._detect_ancilla2):
421
+ for sim_id, phys_idx in enumerate(self._detect_ancilla):
430
422
  lq_idx = len(self._qubits)
431
423
  self._qubits.append([(sim_id, phys_idx)])
432
- self._detect_ancilla2_lq.append(lq_idx)
424
+ self._detect_ancilla_lq.append(lq_idx)
433
425
  # Boundary repetition code, on-demand design: only the couplers
434
426
  # are actually noisy here -- single-qubit gates are already
435
427
  # exactly transversal, per-replica, with zero error, so there's
436
428
  # no need to maintain a permanently-synced repetition code across
437
429
  # them at all. Instead: TWO dedicated, reusable scratch ancillae
438
- # per patch (separate from _detect_ancilla1/2 above, to avoid any
430
+ # per patch (separate from _detect_ancilla above, to avoid any
439
431
  # contention between this mechanism and that one within the same
440
432
  # coupling event), allocated ONCE, and reused across every
441
433
  # boundary qubit that patch ever protects -- not one allocation
442
434
  # per boundary qubit the way the earlier, always-on version did.
443
435
  # See _cpauli for the actual encode/couple/decode/correct cycle
444
436
  # built around these; this block is just the qubit-index
445
- # bookkeeping, in the same style as _detect_ancilla1/2 above.
437
+ # bookkeeping, in the same style as _detect_ancilla above.
446
438
  self._rep_code_ancilla = []
447
439
  if self.is_boundary_repetition_code:
448
440
  for i in range(len(sim_counts)):
@@ -1384,6 +1376,14 @@ class QrackAceBackend:
1384
1376
  def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1=None, lq2=None):
1385
1377
  shadow_targets = []
1386
1378
 
1379
+ if len(hq1) == len(hq2) and all(hq1[i][0] == hq2[i][0] for i in range(len(hq1))):
1380
+ for i in range(len(hq1)):
1381
+ sim_id, idx1 = hq1[i]
1382
+ gate_fn, shadow_fn = self._get_gate(pauli, anti, sim_id)
1383
+ _, idx2 = hq2[i]
1384
+ gate_fn([idx1], idx2)
1385
+ return
1386
+
1387
1387
  # Logical-level error-detection gadget: an earlier version of this
1388
1388
  # also captured lq1's own logical value into a dedicated ancilla
1389
1389
  # (via a nested cx()), on the theory that the shadow-coupling
@@ -1409,24 +1409,28 @@ class QrackAceBackend:
1409
1409
  # population never legitimately changes either, so this doubles as
1410
1410
  # a lq2-invariant check for that gate family, same reasoning as
1411
1411
  # the single-replica CZ dual-check elsewhere.
1412
- anc2 = None
1413
- if self.is_error_detection and not self._in_gadget_capture and (len(hq1) > 1):
1412
+ anc = None
1413
+ if self.is_error_detection and (not self._in_gadget_capture) and (len(hq2) > 1):
1414
1414
  anc_sim = hq1[0][0]
1415
1415
  t_sims = {y[0] for y in hq2 if y[0] >= 0}
1416
1416
  for x in hq1:
1417
1417
  if x[0] in t_sims:
1418
1418
  anc_sim = x[0]
1419
1419
  break
1420
- anc2 = self._detect_ancilla2_lq[anc_sim]
1420
+ anc = self._detect_ancilla_lq[anc_sim]
1421
1421
  self._in_gadget_capture = True
1422
1422
  # XOR on target
1423
- self.cx(lq2, anc2)
1423
+ self.cx(lq2, anc)
1424
1424
  if anti:
1425
- self.acx(lq1, anc2)
1425
+ self.acx(lq1, anc)
1426
1426
  else:
1427
- self.cx(lq1, anc2)
1427
+ self.cx(lq1, anc)
1428
1428
  self._in_gadget_capture = False
1429
1429
 
1430
+ if not self._in_gadget_capture:
1431
+ self._correct(lq1)
1432
+ self._correct(lq2)
1433
+
1430
1434
  # A target replica (q2) that shares a simulator with ANY control
1431
1435
  # replica in qb1 is going to receive a REAL, exact gate from that
1432
1436
  # control at some point in the loop below (the `b1[0] == b2[0]`
@@ -1479,19 +1483,25 @@ class QrackAceBackend:
1479
1483
  shadow_fn(b1, b2)
1480
1484
  shadow_targets.append(b2)
1481
1485
 
1482
- if anc2 is not None:
1486
+ if not self._in_gadget_capture:
1487
+ if pauli != Pauli.PauliZ:
1488
+ self._correct(lq2, False, pauli != Pauli.PauliX)
1489
+ if pauli != Pauli.PauliX:
1490
+ self._correct(lq2, True)
1491
+
1492
+ if anc is not None:
1483
1493
  self._in_gadget_capture = True
1484
1494
  # XOR check
1485
- self.cx(lq2, anc2)
1486
- anc_sim, anc_idx = self._qubits[anc2][0]
1495
+ self.cx(lq2, anc)
1496
+ anc_sim, anc_idx = self._qubits[anc][0]
1487
1497
  p = self.sim[anc_sim].prob(anc_idx)
1488
1498
  p = 1.0 - p
1489
1499
  if self._ps_epsilon >= p:
1490
- b = self.m(anc2)
1500
+ b = self.m(anc)
1491
1501
  else:
1492
- b = self.force_m(anc2, False)
1502
+ b = self.force_m(anc, False)
1493
1503
  if b:
1494
- self.x(anc2)
1504
+ self.x(anc)
1495
1505
  self.x(lq2)
1496
1506
  self._in_gadget_capture = False
1497
1507
 
@@ -1525,19 +1535,21 @@ class QrackAceBackend:
1525
1535
  lq1_lr = len(hq1) == 1
1526
1536
  lq2_lr = len(hq2) == 1
1527
1537
 
1528
- if not self._in_gadget_capture:
1529
- self._correct(lq1)
1530
- self._correct(lq2)
1531
-
1532
1538
  t2 = [lq2]
1533
- anc1 = None
1539
+ anc, anc_sim = None, None
1534
1540
  if self.is_boundary_repetition_code and (not self._in_gadget_capture) and lq1_lr and (not lq2_lr):
1535
1541
  # Encode:
1536
- anc1 = self._rep_code_ancilla_lq[hq1[0][0]]
1542
+ anc_sim = hq1[0][0]
1543
+ t_sims = {y[0] for y in hq2 if y[0] >= 0}
1544
+ for x in hq1:
1545
+ if x[0] in t_sims:
1546
+ anc_sim = x[0]
1547
+ break
1548
+ anc = self._rep_code_ancilla_lq[anc_sim]
1537
1549
  self._in_gadget_capture = True
1538
- self.cx(lq2, anc1)
1550
+ self.cx(lq2, anc)
1539
1551
  self._in_gadget_capture = False
1540
- t2.append(anc1)
1552
+ t2.append(anc)
1541
1553
 
1542
1554
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
1543
1555
 
@@ -1553,50 +1565,24 @@ class QrackAceBackend:
1553
1565
  ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
1554
1566
  _cpauli_lhv(ctrl_prob, self._lhv[lq2], pauli, anti)
1555
1567
 
1556
- if anc1 is not None:
1557
- anc2 = self._detect_ancilla1_lq[hq1[0][0]]
1558
- self._in_gadget_capture = True
1559
- self.cx(lq2, anc2)
1560
- self.cx(anc1, anc2)
1561
- self._in_gadget_capture = False
1562
-
1563
- # If anc2 shows a mismatch, it's more likely
1564
- # that the boundary qubit is the one in error.
1565
-
1566
- anc_sim, anc_idx = self._qubits[anc2][0]
1567
- p = self.sim[anc_sim].prob(anc_idx)
1568
- if self._ps_epsilon >= (1.0 - p):
1569
- b = self.m(anc2)
1570
- else:
1571
- b = self.force_m(anc2, False)
1572
- if b:
1573
- self.x(anc2)
1574
- self.x(lq2)
1575
-
1568
+ if anc is not None:
1576
1569
  # Decode:
1577
1570
  self._in_gadget_capture = True
1578
- self.cx(lq2, anc1)
1571
+ self.cx(lq2, anc)
1579
1572
  self._in_gadget_capture = False
1580
1573
 
1581
1574
  # After we corrected any mismatch above, it's unlikely
1582
1575
  # that we can't post-select no-error on this ancilla.
1583
1576
 
1584
- anc_sim, anc_idx = self._qubits[anc1][0]
1577
+ anc_sim, anc_idx = self._qubits[anc][0]
1585
1578
  p = self.sim[anc_sim].prob(anc_idx)
1586
1579
  if self._ps_epsilon >= (1.0 - p):
1587
- b = self.m(anc1)
1580
+ b = self.m(anc)
1588
1581
  else:
1589
- b = self.force_m(anc1, False)
1582
+ b = self.force_m(anc, False)
1590
1583
  if b:
1591
- self.x(anc1)
1592
-
1593
- if self._in_gadget_capture:
1594
- return
1595
-
1596
- if pauli != Pauli.PauliZ:
1597
- self._correct(lq2, False, pauli != Pauli.PauliX)
1598
- if pauli != Pauli.PauliX:
1599
- self._correct(lq2, True)
1584
+ self.x(anc)
1585
+ self.x(lq2)
1600
1586
 
1601
1587
  def cx(self, lq1, lq2):
1602
1588
  self._cpauli(lq1, lq2, False, Pauli.PauliX)
@@ -1810,42 +1796,6 @@ class QrackAceBackend:
1810
1796
  hq1 = self._unpack(lq1)
1811
1797
  hq2 = self._unpack(lq2)
1812
1798
 
1813
- sims1 = {r[0] for r in hq1}
1814
- sims2 = {r[0] for r in hq2}
1815
-
1816
- if sims1.isdisjoint(sims2):
1817
- self._correct(lq1)
1818
- self._correct(lq2)
1819
-
1820
- # Once fully resolved, the swap itself is nothing but a
1821
- # reference exchange: self._qubits[lq] is a reference to the
1822
- # replica-list object, so swapping which index points to
1823
- # which object IS the swap -- no separate content to copy.
1824
- self._qubits[lq1], self._qubits[lq2] = self._qubits[lq2], self._qubits[lq1]
1825
-
1826
- lhv1 = self._lhv.pop(lq1, None)
1827
- lhv2 = self._lhv.pop(lq2, None)
1828
- if lhv2 is not None:
1829
- self._lhv[lq1] = lhv2
1830
- if lhv1 is not None:
1831
- self._lhv[lq2] = lhv1
1832
-
1833
- # Boundary repetition code needs no bookkeeping here, unlike
1834
- # self._qubits/self._lhv just above: it's on-demand now, not
1835
- # a permanent, per-logical-index dict of pre-allocated
1836
- # partners -- eligibility is just len(self._qubits[lq]) > 1,
1837
- # computed fresh on every coupling event, so the reference
1838
- # swap of self._qubits above already, automatically carries
1839
- # the right eligibility with it. Nothing else to move.
1840
- return
1841
-
1842
- # Boundary-to-boundary handling
1843
- if (len(hq1) > 1) and (len(hq2) > 1):
1844
- self.cx(lq1, lq2)
1845
- self.cx(lq2, lq1)
1846
- self.cx(lq1, lq2)
1847
- return
1848
-
1849
1799
  # Fast/exact path: every replica of lq1 lines up, position-for-
1850
1800
  # position, with the corresponding replica of lq2 on the SAME
1851
1801
  # underlying simulator. A native swap() there is pure index
@@ -1872,8 +1822,6 @@ class QrackAceBackend:
1872
1822
  # two sides can land on different simulators (the partial-match
1873
1823
  # branches below) needs separate, careful treatment -- not
1874
1824
  # reused as-is from _cpauli's helper.
1875
- self._correct(lq1)
1876
- self._correct(lq2)
1877
1825
 
1878
1826
  if len(hq1) == len(hq2) and all(hq1[i][0] == hq2[i][0] for i in range(len(hq1))):
1879
1827
  for i in range(len(hq1)):
@@ -1887,20 +1835,46 @@ class QrackAceBackend:
1887
1835
  # with or without this call).
1888
1836
  return
1889
1837
 
1890
- anc1, anc2 = None, None
1891
- if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
1892
- t_sims = {y[0] for y in hq2 if y[0] >= 0}
1893
- anc_sim = hq1[0][0]
1894
- for x in hq1:
1895
- if x[0] in t_sims:
1896
- anc_sim = x[0]
1897
- break
1898
- anc1 = self._detect_ancilla1_lq[anc_sim]
1899
- anc2 = self._detect_ancilla2_lq[anc_sim]
1900
- self._in_gadget_capture = True
1901
- self.cx(lq1, anc1)
1902
- self.cx(lq2, anc2)
1903
- self._in_gadget_capture = False
1838
+ sims1 = {r[0] for r in hq1}
1839
+ sims2 = {r[0] for r in hq2}
1840
+
1841
+ if sims1.isdisjoint(sims2):
1842
+ # Once fully resolved, the swap itself is nothing but a
1843
+ # reference exchange: self._qubits[lq] is a reference to the
1844
+ # replica-list object, so swapping which index points to
1845
+ # which object IS the swap -- no separate content to copy.
1846
+ self._qubits[lq1], self._qubits[lq2] = self._qubits[lq2], self._qubits[lq1]
1847
+
1848
+ lhv1 = self._lhv.pop(lq1, None)
1849
+ lhv2 = self._lhv.pop(lq2, None)
1850
+ if lhv2 is not None:
1851
+ self._lhv[lq1] = lhv2
1852
+ if lhv1 is not None:
1853
+ self._lhv[lq2] = lhv1
1854
+
1855
+ # Boundary repetition code needs no bookkeeping here, unlike
1856
+ # self._qubits/self._lhv just above: it's on-demand now, not
1857
+ # a permanent, per-logical-index dict of pre-allocated
1858
+ # partners -- eligibility is just len(self._qubits[lq]) > 1,
1859
+ # computed fresh on every coupling event, so the reference
1860
+ # swap of self._qubits above already, automatically carries
1861
+ # the right eligibility with it. Nothing else to move.
1862
+ return
1863
+
1864
+ # Boundary-to-boundary handling
1865
+ if self.is_error_detection and ((len(hq1) > 1) or (len(hq2) > 1)):
1866
+ if len(hq1) > 1:
1867
+ self.cx(lq1, lq2)
1868
+ self.cx(lq2, lq1)
1869
+ self.cx(lq1, lq2)
1870
+ else:
1871
+ self.cx(lq2, lq1)
1872
+ self.cx(lq1, lq2)
1873
+ self.cx(lq2, lq1)
1874
+ return
1875
+
1876
+ self._correct(lq1)
1877
+ self._correct(lq2)
1904
1878
 
1905
1879
  # Partial-match cases: one side is a simple (single-replica) qubit,
1906
1880
  # the other has multiple replicas. For the multi-replica side's
@@ -1956,30 +1930,6 @@ class QrackAceBackend:
1956
1930
  self.cx(lq2, lq1)
1957
1931
  self.cx(lq1, lq2)
1958
1932
 
1959
- if anc1 is not None:
1960
- self._in_gadget_capture = True
1961
- self.cx(lq1, anc2)
1962
- self.cx(lq2, anc1)
1963
- anc_sim, anc_idx = self._qubits[anc2][0]
1964
- p = self.sim[anc_sim].prob(anc_idx)
1965
- if self._ps_epsilon >= (1.0 - p):
1966
- b = self.m(anc2)
1967
- else:
1968
- b = self.force_m(anc2, False)
1969
- if b:
1970
- self.x(anc2)
1971
- self.x(lq1)
1972
- anc_sim, anc_idx = self._qubits[anc1][0]
1973
- p = self.sim[anc_sim].prob(anc_idx)
1974
- if self._ps_epsilon >= (1.0 - p):
1975
- b = self.m(anc1)
1976
- else:
1977
- b = self.force_m(anc1, False)
1978
- if b:
1979
- self.x(anc1)
1980
- self.x(lq2)
1981
- self._in_gadget_capture = False
1982
-
1983
1933
  def iswap(self, lq1, lq2):
1984
1934
  self.swap(lq1, lq2)
1985
1935
  self.cz(lq1, lq2)
@@ -2001,19 +1951,6 @@ class QrackAceBackend:
2001
1951
  self.mcx([c1, c2], t)
2002
1952
  return
2003
1953
 
2004
- if not self._in_gadget_capture:
2005
- self._correct(c1)
2006
- self._correct(c2)
2007
-
2008
- # Control-protection ancillas (anc1/anc1b/anc2/anc2b, checking
2009
- # whether c1/c2's own values held still) removed entirely, same
2010
- # basis as _apply_coupling: instrumented and stress-tested
2011
- # directly -- 3 of the 4 (anc1, anc1b, anc2) never fired across
2012
- # ~21,600 opportunities in a circuit built to exercise them hard,
2013
- # and the 4th (anc2b) only fired at a modest ~1.5% rate. Dropped
2014
- # regardless of that last one, on the same call as
2015
- # _apply_coupling's equivalent mechanism.
2016
- #
2017
1954
  # AND-invariant, generalized from _apply_coupling's CX/CZ XOR
2018
1955
  # check to the Toffoli's actual relationship: t should end up as
2019
1956
  # t_before XOR (c1 AND c2), not just t_before XOR c1 or XOR c2
@@ -2028,7 +1965,7 @@ class QrackAceBackend:
2028
1965
  # bulk on this path (the len(hqt) > 1 case returns earlier,
2029
1966
  # above) and nothing else in this method uses that array anymore.
2030
1967
  anc_and = None
2031
- if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1) or (len(hqt) > 1)):
1968
+ if self.is_error_detection and not self._in_gadget_capture and (len(hqt) > 1):
2032
1969
  found = False
2033
1970
  anc_sim = hq1[0][0]
2034
1971
  t_sims = {y[0] for y in hqt if y[0] >= 0}
@@ -2045,7 +1982,7 @@ class QrackAceBackend:
2045
1982
  if found:
2046
1983
  break
2047
1984
  self._in_gadget_capture = True
2048
- anc_and = self._detect_ancilla2_lq[anc_sim]
1985
+ anc_and = self._detect_ancilla_lq[anc_sim]
2049
1986
  self.cx(t, anc_and)
2050
1987
  # self.mcx() only supports a single control at the logical
2051
1988
  # level (a syntax-convenience wrapper, confirmed directly --
@@ -2070,6 +2007,9 @@ class QrackAceBackend:
2070
2007
  self.ccx(c1, c2, anc_and)
2071
2008
  self._in_gadget_capture = False
2072
2009
 
2010
+ self._correct(c1)
2011
+ self._correct(c2)
2012
+
2073
2013
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
2074
2014
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
2075
2015
  qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
@@ -2136,6 +2076,8 @@ class QrackAceBackend:
2136
2076
  for (b1, b2, bt) in witnesses:
2137
2077
  self.sim[b1[0]].mcx([b1[1], b2[1]], bt[1])
2138
2078
 
2079
+ self._correct(t)
2080
+
2139
2081
  self._in_gadget_capture = True
2140
2082
 
2141
2083
  if anc_and is not None:
@@ -2153,9 +2095,6 @@ class QrackAceBackend:
2153
2095
 
2154
2096
  self._in_gadget_capture = False
2155
2097
 
2156
- if not self._in_gadget_capture:
2157
- self._correct(t)
2158
-
2159
2098
  def ccz(self, c1, c2, t):
2160
2099
  self.h(t)
2161
2100
  self.ccx(c1, c2, t)
@@ -2868,7 +2807,7 @@ class QrackAceBackend:
2868
2807
  return tgt
2869
2808
 
2870
2809
  # Designed by Dan, Elara (ChatGPT), and (Anthropic) Claude:
2871
- def create_noise_model(self, x=0.5, y=0.5):
2810
+ def create_noise_model(self, x=0.25, y=0.25):
2872
2811
  if not _IS_QISKIT_AER_AVAILABLE:
2873
2812
  raise RuntimeError(
2874
2813
  "Before trying to run_qiskit_circuit() with QrackAceBackend, you must install Qiskit Aer!"
@@ -2876,17 +2815,10 @@ class QrackAceBackend:
2876
2815
  noise_model = NoiseModel()
2877
2816
 
2878
2817
  def _uncommon_sim_fraction(lq1, lq2):
2879
- sims1 = [qb[0] for qb in self._qubits[lq1]]
2880
- sims2 = [qb[0] for qb in self._qubits[lq2]]
2881
- n = 0
2882
- for s in sims1:
2883
- if s not in sims2:
2884
- n += 1
2885
- for s in sims2:
2886
- if s not in sims1:
2887
- n += 1
2888
-
2889
- return n / (len(sims1) + len(sims2))
2818
+ sims1 = {qb[0] for qb in self._qubits[lq1]}
2819
+ sims2 = {qb[0] for qb in self._qubits[lq2]}
2820
+
2821
+ return len(sims1 ^ sims2) / len(sims1.union(sims2))
2890
2822
 
2891
2823
  coupling_map = self.get_logical_coupling_map()
2892
2824
 
@@ -2942,6 +2874,7 @@ class QrackAceBackend:
2942
2874
 
2943
2875
  sp = (1 - self._sdrp / 2) ** c
2944
2876
  p = (1 - x) ** u
2877
+ d = 1 - ((1 - y) ** u)
2945
2878
 
2946
2879
  # cx/cy/cz: single-qubit, TARGET-only error, with the gate's
2947
2880
  # own generator as the Pauli type -- derived directly from
@@ -2966,7 +2899,7 @@ class QrackAceBackend:
2966
2899
  # anc2 verifying b) are structurally live exactly when a
2967
2900
  # itself is boundary -- see the comment above this loop.
2968
2901
  # Damp accordingly.
2969
- p_shadow_ccz *= y
2902
+ p_shadow_ccz *= d
2970
2903
  noise_model.add_quantum_error(
2971
2904
  pauli_error([("ZI", p_shadow_ccz), ("II", 1 - p_shadow_ccz)]), "cz", [a, b]
2972
2905
  )
@@ -3042,17 +2975,11 @@ class QrackAceBackend:
3042
2975
  sims_b = [qb[0] for qb in self._qubits[b]]
3043
2976
  has_match = any(s in sims_b for s in sims_a)
3044
2977
 
3045
- # Mirrors swap()'s own guard: the sandwich path (and its
3046
- # Z-type error model) only applies when a matching replica
3047
- # actually anchors it. A simple/simple pair with no shared
3048
- # sim routes through the general 3-CNOT fallback in swap()
3049
- # itself now (verified: the sandwich path gave 100/100 wrong
3050
- # deterministic results there), so the noise model needs the
3051
- # same condition to stay consistent with what the gate does.
3052
2978
  if (is_a_simple != is_b_simple) and has_match:
3053
2979
  p_net_swap = 2 * p * (1 - p)
3054
2980
  if self.is_error_detection:
3055
- p_net_swap *= y
2981
+ p_net_swap *= d
2982
+ p_net_swap *= p_net_swap
3056
2983
  if is_a_simple:
3057
2984
  # a is bulk, b is boundary -> error lands on b
3058
2985
  noise_model.add_quantum_error(
@@ -3094,16 +3021,7 @@ class QrackAceBackend:
3094
3021
  if (is_a_simple != is_b_simple) and has_match:
3095
3022
  p_cz = 1 - sp * p # same per-call formula as standalone cz
3096
3023
  if self.is_error_detection and len(self._qubits[a]) > 1:
3097
- # this cz component is routed through cx() when
3098
- # is_error_detection is on (see cz()'s own wrapper),
3099
- # so it's the SAME kind of shadow-driven term, gated
3100
- # the SAME way, as the main cx/cy/cz loop above (see
3101
- # that comment for the mechanism); p_net_swap was
3102
- # already damped above, independently, since it comes
3103
- # from a different gate (the now-protected native
3104
- # swap, not
3105
- # this cz component).
3106
- p_cz *= y
3024
+ p_cz *= d
3107
3025
  p_net_iswap = p_net_swap * (1 - p_cz) + p_cz * (1 - p_net_swap)
3108
3026
  if is_a_simple:
3109
3027
  terms = [("II", 1 - p_net_iswap), ("ZI", p_net_iswap)]
@@ -3117,3 +3035,5 @@ class QrackAceBackend:
3117
3035
  )
3118
3036
 
3119
3037
  return noise_model
3038
+
3039
+ return noise_model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.26.0
3
+ Version: 2.26.6
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.26.0"
10
+ VERSION = "2.26.6"
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')