pyqrack-complex128 2.29.2__tar.gz → 2.29.4__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.29.2/pyqrack_complex128.egg-info → pyqrack_complex128-2.29.4}/PKG-INFO +1 -1
  2. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_ace_backend.py +139 -85
  3. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/setup.py +1 -1
  5. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/LICENSE +0 -0
  6. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/MANIFEST.in +0 -0
  7. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/Makefile +0 -0
  8. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/README.md +0 -0
  9. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyproject.toml +0 -0
  10. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/__init__.py +0 -0
  11. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/pauli.py +0 -0
  13. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_ace_mps_backend.py +0 -0
  14. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_circuit.py +0 -0
  15. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  16. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_neuron.py +0 -0
  17. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  18. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_simulator.py +0 -0
  19. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_stabilizer.py +0 -0
  20. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_system/__init__.py +0 -0
  21. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/qrack_system/qrack_system.py +0 -0
  22. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/quimb_circuit_type.py +0 -0
  23. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/stats/__init__.py +0 -0
  24. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/stats/load_quantized_data.py +0 -0
  25. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack/stats/quantize_by_range.py +0 -0
  26. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack_complex128.egg-info/SOURCES.txt +0 -0
  27. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
  28. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
  29. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack_complex128.egg-info/requires.txt +0 -0
  30. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/pyqrack_complex128.egg-info/top_level.txt +0 -0
  31. {pyqrack_complex128-2.29.2 → pyqrack_complex128-2.29.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.29.2
3
+ Version: 2.29.4
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -262,7 +262,7 @@ class QrackAceBackend:
262
262
  is_torus=True,
263
263
  is_1d_chain=False,
264
264
  is_error_detection=True,
265
- is_boundary_repetition_code=False,
265
+ is_boundary_repetition_code=True,
266
266
  use_crossbars=True,
267
267
  to_clone=None,
268
268
  ):
@@ -471,31 +471,39 @@ class QrackAceBackend:
471
471
  lq_idx = len(self.qubits)
472
472
  self.qubits.append([(sim_id, phys_idx)])
473
473
  self.detect_ancilla_lq.append(lq_idx)
474
- # Boundary repetition code, on-demand design: only the couplers
475
- # are actually noisy here -- single-qubit gates are already
476
- # exactly transversal, per-replica, with zero error, so there's
477
- # no need to maintain a permanently-synced repetition code across
478
- # them at all. Instead: TWO dedicated, reusable scratch ancillae
479
- # per patch (separate from _detect_ancilla above, to avoid any
480
- # contention between this mechanism and that one within the same
481
- # coupling event), allocated ONCE, and reused across every
482
- # boundary qubit that patch ever protects -- not one allocation
483
- # per boundary qubit the way the earlier, always-on version did.
484
- # See _cpauli for the actual encode/couple/decode/correct cycle
485
- # built around these; this block is just the qubit-index
486
- # bookkeeping, in the same style as _detect_ancilla above.
487
- rep_code_ancilla = []
488
- if self.is_boundary_repetition_code:
489
- for i in range(len(sim_counts)):
490
- rep_code_ancilla.append(sim_counts[i])
491
- sim_counts[i] += 1
492
-
474
+ # Boundary repetition code (zero-ancilla, "authority" decoder).
475
+ #
476
+ # A boundary qubit's replicas ARE already a repetition code: 2-5
477
+ # copies of one logical qubit, one per simulator. Single-qubit gates
478
+ # act on every copy exactly, so the only place the copies can
479
+ # diverge is a coupling with a qubit on some other simulator. In
480
+ # that coupling, exactly the replica(s) sharing a simulator with the
481
+ # partner receive the REAL gate (and any real entanglement it
482
+ # creates); every other copy receives only the classical shadow.
483
+ # So the decoder has side information a hardware repetition code
484
+ # doesn't: it knows WHICH copy is fresh. That is the lesson of the
485
+ # Shor-9 layout experiments (ace_shor_9_one_side.py): a code works
486
+ # on ACE when the state lives in the one simulator that shares the
487
+ # couplings, and the far-side copies are what needs reconciling.
488
+ #
489
+ # With this option on, every multi-replica coupling records which
490
+ # replica slot(s) of each boundary participant were real-gated
491
+ # ("authority"). correct() then treats the authority copy as the
492
+ # decoder's ground truth -- flipping stale copies to match it when
493
+ # it is decisive, rotating them toward its Bloch direction when it
494
+ # is still separable, and never touching the authority copy itself
495
+ # -- instead of the fixed home-slot-first cascade (which is right
496
+ # only when the partner happened to share the home simulator).
497
+ # prob() reads the authority copy's own marginal. No qubits are
498
+ # allocated: qubit overhead is zero. The authority record clears
499
+ # whenever every replica is fresh again (a fully matched coupling,
500
+ # or a measurement).
501
+ self._authority = (
502
+ {k: set(v) for k, v in to_clone._authority.items()} if to_clone else {}
503
+ )
504
+ # Kept (always empty) for compatibility with code that inspected the
505
+ # old per-patch scratch ancillae of the previous on-demand design.
493
506
  self.rep_code_ancilla_lq = []
494
- if self.is_boundary_repetition_code:
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)
499
507
 
500
508
  # capture calls (see _apply_coupling): those calls go through
501
509
  # the ordinary _cpauli -> _apply_coupling path themselves, which
@@ -892,7 +900,10 @@ class QrackAceBackend:
892
900
  if lq in self.lhv:
893
901
  self.lhv[lq].h()
894
902
 
895
- if len(hq) == 4:
903
+ fresh = self._authority.get(lq) if self.is_boundary_repetition_code else None
904
+ if fresh:
905
+ self._correct_by_authority(lq, hq, fresh, skip_rotation)
906
+ elif len(hq) == 4:
896
907
  # Corner-boundary qubit: 4 real replicas remain (home,
897
908
  # adjacent-patch, and the 2 diagonal-patch shadows) now that
898
909
  # the crossbar replica that used to sit at old-index-2 is
@@ -1592,6 +1603,52 @@ class QrackAceBackend:
1592
1603
 
1593
1604
  return connected, boundary
1594
1605
 
1606
+ def _set_authority(self, lq, hq, partner_hq):
1607
+ # Record which replica slot(s) of boundary qubit lq just received a
1608
+ # REAL coupling gate (they share a simulator with the partner). See
1609
+ # the constructor for the decoder this feeds. Skipped for the
1610
+ # detection gadget's own internal captures, which must not move it.
1611
+ if (not self.is_boundary_repetition_code) or self._in_gadget_capture or (lq is None) or (len(hq) < 2):
1612
+ return
1613
+ partner_sims = {b[0] for b in partner_hq}
1614
+ fresh = {i for i, b in enumerate(hq) if b[0] in partner_sims}
1615
+ if not fresh:
1616
+ # Shadow-only coupling: no copy is fresher than before.
1617
+ return
1618
+ if len(fresh) == len(hq):
1619
+ # Every copy got the real gate: no copy is privileged.
1620
+ self._authority.pop(lq, None)
1621
+ else:
1622
+ self._authority[lq] = fresh
1623
+
1624
+ def _correct_by_authority(self, lq, hq, fresh, skip_rotation):
1625
+ # Decode boundary qubit lq's replica repetition code with the
1626
+ # fresh copy (or copies) as ground truth. See the constructor.
1627
+ auth = sorted(fresh)
1628
+ p_auth = sum(self.sim[hq[i][0]].prob(hq[i][1]) for i in auth) / len(auth)
1629
+ if abs(p_auth - 0.5) > self._epsilon:
1630
+ result = p_auth >= 0.5
1631
+ for q, b in enumerate(hq):
1632
+ if q in fresh:
1633
+ continue
1634
+ p = self.sim[b[0]].prob(b[1])
1635
+ if (1 - p if result else p) > (0.5 + self._epsilon):
1636
+ self.sim[b[0]].x(b[1])
1637
+ if skip_rotation:
1638
+ return
1639
+ a0, i0, r0 = self._get_bloch_angles(hq[auth[0]])
1640
+ if r0 > self._rot_epsilon:
1641
+ # The fresh copy is itself entangled with its partner: it has no
1642
+ # well-defined direction to copy, and the stale copies cannot
1643
+ # hold that entanglement anyway. prob() reads the fresh copy.
1644
+ return
1645
+ for q, b in enumerate(hq):
1646
+ if q in fresh:
1647
+ continue
1648
+ a, i, r = self._get_bloch_angles(b)
1649
+ if r <= self._rot_epsilon:
1650
+ self._rotate_to_bloch(b, a0 - a, i0 - i)
1651
+
1595
1652
  def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1=None, lq2=None):
1596
1653
  shadow_targets = []
1597
1654
 
@@ -1601,6 +1658,8 @@ class QrackAceBackend:
1601
1658
  gate_fn, shadow_fn = self._get_gate(pauli, anti, sim_id)
1602
1659
  _, idx2 = hq2[i]
1603
1660
  gate_fn([idx1], idx2)
1661
+ self._set_authority(lq1, hq1, hq2)
1662
+ self._set_authority(lq2, hq2, hq1)
1604
1663
  return
1605
1664
 
1606
1665
  # Logical-level error-detection gadget: an earlier version of this
@@ -1629,7 +1688,7 @@ class QrackAceBackend:
1629
1688
  # a lq2-invariant check for that gate family, same reasoning as
1630
1689
  # the single-replica CZ dual-check elsewhere.
1631
1690
  anc = None
1632
- if self.is_error_detection and (not self._in_gadget_capture) and ((len(hq1) > 1) or (len(hq2) > 1)):
1691
+ if (not self.is_boundary_repetition_code) and self.is_error_detection and (not self._in_gadget_capture) and ((len(hq1) > 1) or (len(hq2) > 1)):
1633
1692
  anc_sim = hq1[0][0]
1634
1693
  t_sims = {y[0] for y in hq2 if y[0] >= 0}
1635
1694
  for x in hq1:
@@ -1702,6 +1761,9 @@ class QrackAceBackend:
1702
1761
  shadow_fn(b1, b2)
1703
1762
  shadow_targets.append(b2)
1704
1763
 
1764
+ self._set_authority(lq1, hq1, hq2)
1765
+ self._set_authority(lq2, hq2, hq1)
1766
+
1705
1767
  if not self._in_gadget_capture:
1706
1768
  if pauli != Pauli.PauliZ:
1707
1769
  self.correct(lq2, False, pauli != Pauli.PauliX)
@@ -1754,55 +1816,18 @@ class QrackAceBackend:
1754
1816
  lq1_lr = len(hq1) == 1
1755
1817
  lq2_lr = len(hq2) == 1
1756
1818
 
1757
- t2 = [lq2]
1758
- anc, anc_sim = None, None
1759
- if self.is_boundary_repetition_code and (not self._in_gadget_capture) and lq1_lr and (not lq2_lr):
1760
- # Encode:
1761
- anc_sim = hq1[0][0]
1762
- t_sims = {y[0] for y in hq2 if y[0] >= 0}
1763
- for x in hq1:
1764
- if x[0] in t_sims:
1765
- anc_sim = x[0]
1766
- break
1767
- anc = self.rep_code_ancilla_lq[anc_sim]
1768
- self._in_gadget_capture = True
1769
- self.cx(lq2, anc)
1770
- self._in_gadget_capture = False
1771
- t2.append(anc)
1772
-
1773
1819
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
1774
1820
 
1775
1821
  # Apply cross coupling on every real replica in hq1/h2 (the LHV
1776
1822
  # proxy is coupled separately, just below, via _cpauli_lhv -- it
1777
- # is not one of hq's indices). Also apply across target repetition.
1778
- for l2 in t2:
1779
- h2 = self._unpack(l2)
1780
- qb2, _ = QrackAceBackend._get_qb_lhv_indices(h2)
1781
- self._apply_coupling(pauli, anti, qb1, hq1, qb2, h2, lq1_lr, lq1, l2)
1823
+ # is not one of hq's indices).
1824
+ qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
1825
+ self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1, lq2)
1782
1826
 
1783
1827
  if lq2 in self.lhv:
1784
1828
  ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
1785
1829
  _cpauli_lhv(ctrl_prob, self.lhv[lq2], pauli, anti)
1786
1830
 
1787
- if anc is not None:
1788
- # Decode:
1789
- self._in_gadget_capture = True
1790
- self.cx(lq2, anc)
1791
- self._in_gadget_capture = False
1792
-
1793
- # After we corrected any mismatch above, it's unlikely
1794
- # that we can't post-select no-error on this ancilla.
1795
-
1796
- anc_sim, anc_idx = self.qubits[anc][0]
1797
- p = self.sim[anc_sim].prob(anc_idx)
1798
- if self._ps_epsilon >= (1.0 - p):
1799
- b = self.m(anc)
1800
- else:
1801
- b = self.force_m(anc, False)
1802
- if b:
1803
- self.x(anc)
1804
- self.x(lq2)
1805
-
1806
1831
  def cx(self, lq1, lq2):
1807
1832
  self._cpauli(lq1, lq2, False, Pauli.PauliX)
1808
1833
 
@@ -2071,6 +2096,13 @@ class QrackAceBackend:
2071
2096
  if lhv1 is not None:
2072
2097
  self.lhv[lq2] = lhv1
2073
2098
 
2099
+ auth1 = self._authority.pop(lq1, None)
2100
+ auth2 = self._authority.pop(lq2, None)
2101
+ if auth2 is not None:
2102
+ self._authority[lq1] = auth2
2103
+ if auth1 is not None:
2104
+ self._authority[lq2] = auth1
2105
+
2074
2106
  # Boundary repetition code needs no bookkeeping here, unlike
2075
2107
  # self.qubits/self.lhv just above: it's on-demand now, not
2076
2108
  # a permanent, per-logical-index dict of pre-allocated
@@ -2161,6 +2193,23 @@ class QrackAceBackend:
2161
2193
  self.cz(lq1, lq2)
2162
2194
  self.swap(lq1, lq2)
2163
2195
 
2196
+ def _ccx_decomp(self, c1, c2, t):
2197
+ self.h(t)
2198
+ self.cx(c2, t)
2199
+ self.adjt(t)
2200
+ self.cx(c1, t)
2201
+ self.t(t)
2202
+ self.cx(c2, t)
2203
+ self.adjt(t)
2204
+ self.cx(c1, t)
2205
+ self.t(t)
2206
+ self.h(t)
2207
+ self.t(c2)
2208
+ self.cx(c1, c2)
2209
+ self.t(c1)
2210
+ self.adjt(c2)
2211
+ self.cx(c1, c2)
2212
+
2164
2213
  def ccx(self, c1, c2, t):
2165
2214
  hq1 = self._unpack(c1)
2166
2215
  hq2 = self._unpack(c2)
@@ -2170,6 +2219,11 @@ class QrackAceBackend:
2170
2219
  self.mcx([c1, c2], t)
2171
2220
  return
2172
2221
 
2222
+ if (len(hq1) > 1) or (len(hq2) > 1):
2223
+ self._ccx_decomp(c1, c2, t)
2224
+
2225
+ return
2226
+
2173
2227
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
2174
2228
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
2175
2229
  qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
@@ -2194,26 +2248,12 @@ class QrackAceBackend:
2194
2248
  witnesses.append(witness)
2195
2249
 
2196
2250
  if needs_shadow:
2197
- self.h(t)
2198
- self.cx(c2, t)
2199
- self.adjt(t)
2200
- self.cx(c1, t)
2201
- self.t(t)
2202
- self.cx(c2, t)
2203
- self.adjt(t)
2204
- self.cx(c1, t)
2205
- self.t(t)
2206
- self.h(t)
2207
- self.t(c2)
2208
- self.cx(c1, c2)
2209
- self.t(c1)
2210
- self.adjt(c2)
2211
- self.cx(c1, c2)
2251
+ self._ccx_decomp(c1, c2, t)
2212
2252
 
2213
2253
  return
2214
2254
 
2215
2255
  anc_and = None
2216
- if self.is_error_detection and not self._in_gadget_capture and ((len(hqt) > 1) or (len(hq1) > 1) or (len(hq2) > 1)):
2256
+ if (not self.is_boundary_repetition_code) and self.is_error_detection and (not self._in_gadget_capture):
2217
2257
  found = False
2218
2258
  anc_sim = hq1[0][0]
2219
2259
  t_sims = {y[0] for y in hqt if y[0] >= 0}
@@ -2229,6 +2269,7 @@ class QrackAceBackend:
2229
2269
  break
2230
2270
  if found:
2231
2271
  break
2272
+ self._in_gadget_capture = True
2232
2273
  anc_and = self.detect_ancilla_lq[anc_sim]
2233
2274
  self.cx(t, anc_and)
2234
2275
  c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
@@ -2237,9 +2278,8 @@ class QrackAceBackend:
2237
2278
  _, anc_and_idx = self.qubits[anc_and][0]
2238
2279
  self.sim[anc_sim].mcx([c1_idx, c2_idx], anc_and_idx)
2239
2280
  else:
2240
- self._in_gadget_capture = True
2241
2281
  self.ccx(c1, c2, anc_and)
2242
- self._in_gadget_capture = False
2282
+ self._in_gadget_capture = False
2243
2283
 
2244
2284
  self.correct(c1)
2245
2285
  self.correct(c2)
@@ -2250,6 +2290,7 @@ class QrackAceBackend:
2250
2290
  self.correct(t)
2251
2291
 
2252
2292
  if anc_and is not None:
2293
+ self._in_gadget_capture = True
2253
2294
  self.cx(t, anc_and)
2254
2295
  anc_sim, anc_idx = self.qubits[anc_and][0]
2255
2296
  p = self.sim[anc_sim].prob(anc_idx)
@@ -2261,6 +2302,7 @@ class QrackAceBackend:
2261
2302
  if b:
2262
2303
  self.x(anc_and)
2263
2304
  self.x(t)
2305
+ self._in_gadget_capture = False
2264
2306
 
2265
2307
  def ccz(self, c1, c2, t):
2266
2308
  self.h(t)
@@ -2290,6 +2332,12 @@ class QrackAceBackend:
2290
2332
  # re-weighting any particular replica specially here would
2291
2333
  # reintroduce the same center-dragging distortion the cascade in
2292
2334
  # correct() was built to avoid.
2335
+ fresh = self._authority.get(lq) if self.is_boundary_repetition_code else None
2336
+ if fresh:
2337
+ # The fresh copy carries the real marginal (and any real
2338
+ # entanglement); the stale copies were just reconciled to it.
2339
+ return sum(self.sim[hq[i][0]].prob(hq[i][1]) for i in fresh) / len(fresh)
2340
+
2293
2341
  p = [self.sim[b[0]].prob(b[1]) for b in hq]
2294
2342
  n = len(p)
2295
2343
  prms = math.sqrt(sum(x**2 for x in p) / n)
@@ -2323,6 +2371,9 @@ class QrackAceBackend:
2323
2371
  if result:
2324
2372
  lhv.x()
2325
2373
 
2374
+ # Every replica now agrees on a definite value: none is fresher.
2375
+ self._authority.pop(lq, None)
2376
+
2326
2377
  return result
2327
2378
 
2328
2379
  def force_m(self, lq, result):
@@ -2350,6 +2401,9 @@ class QrackAceBackend:
2350
2401
  if result:
2351
2402
  lhv.x()
2352
2403
 
2404
+ # Every replica now agrees on a definite value: none is fresher.
2405
+ self._authority.pop(lq, None)
2406
+
2353
2407
  return result
2354
2408
 
2355
2409
  def m_all(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-complex128
3
- Version: 2.29.2
3
+ Version: 2.29.4
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.29.2"
10
+ VERSION = "2.29.4"
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')